随笔 - 8  文章 - 0  评论 - 1  阅读 - 1039

BUUCTF Reverse题解:第一部分(已完结)(纯零基础)

Welcome to C12AK's Re journal !



题目传送门


前言

现在是我大一的暑假,这篇文章将记录我在这个假期做的题。

我上大学之前,只对网安有一些模糊的认识,第一次听说“CTF”这个东西,还是和一起转专业的同学闲聊的时候听他说的。直到开始写这篇文章的前一周,我还:
· 没学过关于开发的任何知识(前后端、Web等)
· 有一本CTF书,完全看不懂
· 对各种所谓“入门课”里提到的概念和工具一无所知(不仅没用过,根本就没听说过)
· 上网搜索入门教程,除了“CTF”,不知道该搜什么关键词
· 不知道常用的练习平台(就比如BUUCTF)

完全零基础入门真是太难了。于是我写了这篇文章,供自己整理思路,也希望能帮大家轻松入门。


1. easyre

下载IDA,把题目文件拖进去,在左侧函数栏找到main函数,按F5反编译,直接拿到flag。
具体操作看这里,如果还是不会,再看这里

回到顶部


2. reverse1

进入main函数,发现嵌套了一层main_0。

双击进入main_0:

分析这段代码,意思是,首先有一个字符串Str2,把Str2中所有ascii值为111的位置改为ascii值为48,也就是把所有的'o'替换为'0'(第17到24行)。然后输入Str1,如果Str1和修改后的Str2相同,那么Str1就是flag。
双击Str2查看:

可以看到Str2是hello_world。把o换成0,变成hell0_w0rld。这就是flag。

回到顶部


3. reverse2

还是先查看main,发现代码逻辑和reverse1基本相同,也是一个字符串经过字符替换得到flag。于是采取相同操作直接通过。

回到顶部


4. 内涵的软件

进入main_0,眼前直接就是一个字符串v5,下面有一堆操作。

把这些sub函数挨个点进去看,好像与flag无关啊!由于v5形似flag,但大括号外并非“flag”,于是猜测大括号内就是base64加密过的flag。
使用base64加解码器将大括号内的内容解码:

这啥啊!于是把v5直接包上flag{}提交,还是不对。最后尝试把“DBAPP”换成“flag”直接提交。
过了。6。

[补充]:这道题可能是搬运的,“DBAPP”是原比赛的flag格式,所以要改成“flag”。

回到顶部


5. 新年快乐

首先查壳,发现文件带壳:

用upx脱壳

再查一次壳,果然没了。好神奇
然后把文件拖入IDA并查看main函数:

显然Str2就是flag。

回到顶部


6. xor

查看main函数,发现,如果一个字符串v5经过“从前到后,每一位都异或上它的前一位”的操作之后,与golbal相同,那么v5就是flag。

由于异或的性质 a^b^b = a^0 = a,对global从后往前再跑一遍异或就能得到flag。
查看global:

此处还有一点:global的第一位是f,证明目前的判断没有错。

然后写脚本进行上述操作,得到flag:

回到顶部


7. reverse3

小蒟蒻被这题卡了一整天,后来才惊奇地发现,前面放那个视频的最后一段就是这个题……

在IDA里查看主函数:

依次检查每个sub函数,大致梳理代码逻辑如下:
1.清空Destination(19~24行);
2.对输入的Str进行一些操作,将操作后的字符串赋值给v4,然后又将v4赋值给Destination(25~29行);
3.Destination中每个字符的ascii值都加上它的下标(32~33行);
4.如果此时Destination与Str2相同,那么它就是flag。

然后查看Str2:

也就是说,将这个东西按上述过程逆向操作,就能得到flag。那就先把Str2的每个位置减去下标:

然后看看28行的函数是什么操作。点开:

int __fastcall sub_411AB0(int a1, int a2, _BYTE *a3, unsigned int a4, int *a5)
{
  int v5; // edx
  int v6; // ecx
  int v8; // [esp+D4h] [ebp-38h]
  int v9; // [esp+D4h] [ebp-38h]
  int v10; // [esp+D4h] [ebp-38h]
  int v11; // [esp+D4h] [ebp-38h]
  int i; // [esp+E0h] [ebp-2Ch]
  unsigned int v13; // [esp+ECh] [ebp-20h]
  int v14; // [esp+ECh] [ebp-20h]
  int v15; // [esp+ECh] [ebp-20h]
  void *v16; // [esp+F8h] [ebp-14h]
  _BYTE *v17; // [esp+104h] [ebp-8h]

  if ( a3 && a4 )
  {
    v13 = a4 / 3;
    if ( (int)(a4 / 3) % 3 )
      ++v13;
    v14 = 4 * v13;
    *a5 = v14;
    malloc(v14 + 1);
    v16 = (void *)sub_411127(v6, v5);
    if ( v16 )
    {
      j_memset(v16, 0, v14 + 1);
      v17 = a3;
      v15 = a4;
      v8 = 0;
      while ( v15 > 0 )
      {
        byte_41A144[2] = 0;
        a1 = 1;
        byte_41A144[1] = 0;
        a2 = 1;
        byte_41A144[0] = 0;
        for ( i = 0; i < 3 && v15 >= 1; ++i )
        {
          a1 = (int)v17;
          LOBYTE(a2) = *v17;
          byte_41A144[i] = *v17;
          --v15;
          ++v17;
        }
        if ( !i )
          break;
        switch ( i )
        {
          case 1:
            *((_BYTE *)v16 + v8) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
            v9 = v8 + 1;
            *((_BYTE *)v16 + v9) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
            a2 = ++v9;
            *((_BYTE *)v16 + v9) = aAbcdefghijklmn[64];
            a1 = (int)v16 + ++v9;
            LOBYTE(a2) = aAbcdefghijklmn[64];
            *((_BYTE *)v16 + v9) = a2;
            v8 = v9 + 1;
            break;
          case 2:
            *((_BYTE *)v16 + v8) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
            v10 = v8 + 1;
            *((_BYTE *)v16 + v10) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
            a2 = (int)v16 + ++v10;
            *((_BYTE *)v16 + v10) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
            a1 = (int)v16 + ++v10;
            LOBYTE(a2) = aAbcdefghijklmn[64];
            *((_BYTE *)v16 + v10) = a2;
            v8 = v10 + 1;
            break;
          case 3:
            *((_BYTE *)v16 + v8) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
            v11 = v8 + 1;
            *((_BYTE *)v16 + v11) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
            *((_BYTE *)v16 + ++v11) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
            a2 = (int)v16 + ++v11;
            *((_BYTE *)v16 + v11) = aAbcdefghijklmn[byte_41A144[2] & 0x3F];
            a1 = v11 + 1;
            v8 = v11 + 1;
            break;
        }
      }
      *((_BYTE *)v16 + v8) = 0;
    }
  }
  return sub_411127(a1, a2);
}

我去,这么多。不过它用了很多次 aAbcdefghijklmn 这个数组,那就看看它又是什么。点开:

原来是base64码表。看来把刚才得到的字符串进行base64解码就能拿到flag。

我也爱你!

回到顶部


8. helloworld

这是一道android逆向,可以用apkide或者apktool反编译。下图是用apkide打开题目文件后,输出框显示的内容:

正常来讲在apkide中直接可以查看.smali输出,但打开.smali时出现了错误,那就按照输出目录查看主函数,在这个位置:

就是这个MainActivity.smali,可以用各种方式打开,比如记事本,然后就看到了flag:

回到顶部


9. 不一样的flag

打开一看确实不太一样:

“选择一个方向,向上下左右行走”,合理猜测是一个东西在地图上行走,且v3[25]表示行数,v4表示列数。按照这个猜测,再看看具体的行走细节:

“如果v3[25] >= 5或者v3[29] >= 5,那就退出;如果v7[ 5*v3[25] - 41 + v4 ]是'1',就退出;如果v7[同上]是'#',就输出正确并退出。”

v3[29]是什么呢?查看前面声明变量的部分,可以看到v3是一个长为28的数组,所以v3[29]其实就是v4(“v4”这个名字就是IDA给命名的,之所以叫v4,是因为它在内存中的地址紧跟在v3后面)。如果上面的猜想成立,那么这部分意思就是“如果行数或列数不在0到4,那么就退出”。再加上v7下标中的“5*v3[25]”和“+v4”(尽管没看懂“v7”和“-41”),以及v3有值的长度正好是25,基本就可以确定 本题是在一个5*5的地图中行走,并且 flag是一个由1234组成的字符串,按照它可以从v3[0]开始,经过若干个非'1'的位置,走到一个值为'#'的位置

那就将v3写成5*5的形式,从左上角走到'#',依此写出flag。

*1111
01000
01010
00010
1111#

flag{222441144222}

回到顶部


10. SimpleRev

进入主函数分析代码:

意思是,如果输入D或者d就执行Decry函数,否则退出程序。
然后分析Decry函数:

由于大小端存储问题[待补],伪代码窗口下显示的字符串都是实际的字符串经过反转得到的,而汇编窗口下显示的字符串与实际的相同,也就是说src和v9[0]要进行反转。这样我们就通过Decry的前半部分得到了两个字符串text和key。接下来看后半部分:

这部分就是输入一个字符串,它经过一系列处理得到str2,如果str2和text相同,那么它就是flag。既然已知text,我们可以直接反推出flag。

回到顶部


11. luck_guy

就不细说了,直接快进到能直接接触到flag的地方:

这是循环执行操作,每次执行一系列操作中的某一个,看来如果按顺序执行了某几个操作,就可以拿到flag。case1显然是拿到flag的最后一步,分析case1代码可知,flag是f1和f2拼接得到的。点击查看f1,它是“GXY{do_not_”,但f2看不到,看来需要通过其他操作得到。case2和case3显然没什么用,case4是在f2后面拼接一个s,这里s和上一题一样需要反转,是“icug`of0x7F”(长度为8)。再看case5,它是对f2进行一个操作,且操作的下标恰好是0到7。那就可以猜测case4应该是在case5之前进行的。对case4得到的f2(也就是s)进行case5的操作,得到:

很好,这很像flag的后半部分。所以把f1和这个东西拼起来,然后把原比赛格式的“GXY”改为此题要求的“flag”,提交,通过。

回到顶部


12. Java逆向解密

下载文件后发现后缀名是.class,是java文件,要用jd-gui反编译。
在jd-gui中查看java代码:

flag的每一位都加上64^0x20可以得到key。即可编写脚本解出此题:

回到顶部


13. JustRE

看到题名里大写的RE,我才意识到RE还可以表示Runtime Error来着……
我不要RE!!!qwq

言归正传,打开文件……

这啥啊?完全看不懂,要不先点开这个东西看看?

原来这里没有flag啊。那不做了。
再看看捷径?

6。那看看getflag?

6。要不看看Help能不能帮我?

铭记在心!
现在大概知道这软件是干啥的了,再来看代码……还是看不懂。要不查看一下字符串,看看有没有和flag相关的?

翻到最下面,发现一个疑似flag的字符串。双击定位,再按X跳转到引用位置,再按F5反编译包含这个字符串的函数:

没学过这些东西,但大致可以看出sprintf输出的就是flag。与printf同理,把两个“%d”分别换成19999和0,应该就是flag。当然,“BJD”也要换成“flag”,尽管BJD万岁
然后一提交就过了。

回到顶部


14. 刮开有奖

用IDA打开并进入WinMain函数。

再双击进入DialogFunc:

INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4)
{
  const char *v4; // esi
  const char *v5; // edi
  int v7[2]; // [esp+8h] [ebp-20030h] BYREF
  int v8; // [esp+10h] [ebp-20028h]
  int v9; // [esp+14h] [ebp-20024h]
  int v10; // [esp+18h] [ebp-20020h]
  int v11; // [esp+1Ch] [ebp-2001Ch]
  int v12; // [esp+20h] [ebp-20018h]
  int v13; // [esp+24h] [ebp-20014h]
  int v14; // [esp+28h] [ebp-20010h]
  int v15; // [esp+2Ch] [ebp-2000Ch]
  int v16; // [esp+30h] [ebp-20008h]
  CHAR String[65536]; // [esp+34h] [ebp-20004h] BYREF
  char v18[65536]; // [esp+10034h] [ebp-10004h] BYREF

  if ( a2 == 272 )
    return 1;
  if ( a2 != 273 )
    return 0;
  if ( a3 == 1001 )
  {
    memset(String, 0, 0xFFFFu);
    GetDlgItemTextA(hDlg, 1000, String, 0xFFFF);
    if ( strlen(String) == 8 )
    {
      v7[0] = 90;
      v7[1] = 74;
      v8 = 83;
      v9 = 69;
      v10 = 67;
      v11 = 97;
      v12 = 78;
      v13 = 72;
      v14 = 51;
      v15 = 110;
      v16 = 103;
      sub_4010F0(v7, 0, 10);
      memset(v18, 0, 0xFFFFu);
      v18[0] = String[5];
      v18[2] = String[7];
      v18[1] = String[6];
      v4 = sub_401000(v18, strlen(v18));
      memset(v18, 0, 0xFFFFu);
      v18[1] = String[3];
      v18[0] = String[2];
      v18[2] = String[4];
      v5 = sub_401000(v18, strlen(v18));
      if ( String[0] == v7[0] + 34
        && String[1] == v10
        && 4 * String[2] - 141 == 3 * v8
        && String[3] / 4 == 2 * (v13 / 9)
        && !strcmp(v4, "ak1w")
        && !strcmp(v5, "V1Ax") )
      {
        MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
      }
    }
    return 0;
  }
  if ( a3 != 1 && a3 != 2 )
    return 0;
  EndDialog(hDlg, a3);
  return 1;
}

发现其中有一行输出“You get it!”,往上看一点,又发现被比较的字符串是String,那么String就应该是flag了。而判断当前的String是否为flag,需要用到v7[0], v10, v8, v13, v4, v5,那就再往上寻找如何得到这些值。观察到两个函数sub_4010F0和sub_401000。点击查看sub_4010F0,发现这是对v7的前11个元素的加密,且加密方式比较难以分析。但由于v7[0]到v7[11]都已给出(根据IDA的变量命名规则,v8就是v7[2],v9就是v7[3],依此类推),我们可以直接在本地复现这个函数,得到加密后的v7。
这里要注意两点:
1.IDA对函数形参的类型判断有误,通过分析比较容易发现sub_4010F0的参数a1应该是int*类型。
2.IDA中*(a1+i)是指从a1开始偏移i个字节,所以IDA中的*(a1+4*i)实际上相当于a1[i],在本地复现时需要去掉函数中所有的“ 4 * ”(我在经历了无数次错误提交后,看了别人的题解才发现这一点)。

然后分析sub_401000,发现是base64加密,且返回值是v18的起始地址(关于这个返回值,本蒟蒻分析了好久……),所以,结合v18的赋值,可以确认v4和v5分别是经过base64加密的String[5 : 7]和String[2 : 4]。对“ak1w”和“V1Ax”进行解密,可得v4为“jMp”,v5为“WP1”。最后用v7求出String[0 : 1]即可。

本地总代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int sub_4010F0(int *a1, int a2, int a3)
{
  int result; // eax
  int i; // esi
  int v5; // ecx
  int v6; // edx

  result = a3;
  for ( i = a2; i <= a3; a2 = i )
  {
    v5 = i;
    v6 = *(i + a1);
    if ( a2 < result && i < result )
    {
      do
      {
        if ( v6 > *(a1 + result) )
        {
          if ( i >= result )
            break;
          ++i;
          *(v5 + a1) = *(a1 + result);
          if ( i >= result )
            break;
          while ( *(a1 + i) <= v6 )
          {
            if ( ++i >= result )
              goto LABEL_13;
          }
          if ( i >= result )
            break;
          v5 = i;
          *(a1 + result) = *(i + a1);
        }
        --result;
      }
      while ( i < result );
    }
LABEL_13:
    *(a1 + result) = v6;
    sub_4010F0(a1, a2, i - 1);
    result = a3;
    ++i;
  }
  return result;
}

int main(){
    //第一部分:解出v7
    int v7[11] = {90,74,83,69,67,97,78,72,51,110,103};
    sub_4010F0(v7, 0, 10);
    for(int i = 0; i < 11; i++) cout << (char)v7[i];
    cout << endl;

    //第二部分:解出String
    char str0 = v7[0] + 34;
    char str1 = v7[4];
    cout << str0 << str1 << "WP1" << "jMp" << endl;
    return 0;
}

回到顶部


15. 简单注册器

又是一道.apk的题,这次用jadx-gui备用下载地址)来做。

进入主函数,发现第60行输出了一个“flag{*}”格式的字符串,可知“public void onClick”这部分应当是得到flag的关键。这部分有一个字符串xx,如果满足一定条件,就会对另一个字符串x进行一些操作,得到flag。由于flag的得出与xx无关,那我们就不考虑xx。又因为x的原始形式已经给出,所以可以直接在本地写脚本求得flag:

回到顶部


结语

我要开学了!啊啊啊啊啊啊啊啊啊啊啊我不想开学qwq
这篇文章也有点长了,就先完结吧。敬请期待第二部分。

回到顶部


posted on   C12AK  阅读(693)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示