BUUCTF-Re-wp(1-10)
本文最后更新于 8 天前,其中的信息可能已经有所发展或是发生改变。

PS:以下题目均在BUUCTF

1、easyre(学会简单使用逆向工具)

题目

做法

先自行下载Exeinfo PE这个软件

下载压缩包,解压,把解压后的文件拖进Exeinfo PE进行分析

64位

把文件进IDA(64位)

找到main,双击

按F5反编译成c语言

flag就这样水灵灵地出现了,去提交就OK了

2、reverse1(学会找信息、代码分析)

题目

做法

下载压缩包,解压,把解压后的文件拖进Exeinfo PE进行分析

64位,无壳

扔进IDA(64位),找main函数

但是我们并没有发现main函数,那我们先按Shift+F12打开string窗口,一键找出所有的字符串,去寻找它的后门函数

我们从上往下看一遍,发现有些字符串带有flag,因为我们最后交的东西也是flag,所以我们重点关注一下

这里有三个带有flag的:

第2行的wrong flag

第4行的this is the right flag

第7行的input the flag

翻译一下就显而易见了

我们双击this is the right flag

然后直接按Ctrl+x

确定

然后按F5反编译成c语言

进入到主函数(一般是main,一般我们直接找到main,然后双击,再F5反汇编成c语言就可以了),分析代码

因为逆向需要猜像sub_1400111D1这种的实际表达意义是啥,因此我们从我们点进来的this is the right flag这行代码开始往上看(当然,也可以直接从最上面分析到最下面)

sub_1400111D1("this is the right flag!\n");

直观地可以看出sub_1400111D1表示的是print(经验之谈,一般这种有“”且是绿色的都是程序打印给你的东西)

if ( !strncmp(&Str1, Str2, v3) )

比较两个字符串的前 v3 个字符是否相同。如果相同,条件表达式的结果为真,程序会执行 if 语句块内的代码

到这里,我们知道了它的成立条件,我们直接去找str1和str2

往上继续看

sub_14001128F("%20s", &Str1);

我们不难猜出sub_14001128F表示的是类似于 scanf 的函数,表示用户的输入会存放到&str1

因此,我们的输入值要等于str2,就可以得到flag

继续往上看,找到有str2或者跟它有关联的东西

  for ( j = 0; ; ++j )
  {
    v8 = j;
    v2 = j_strlen(Str2);
    if ( v8 > v2 )
      break;
    if ( Str2[j] == 111 )
      Str2[j] = 48;
  }

我们注意到这里有关于str2的东西,这是一个for循环

执行顺序总结

  1. 初始化 j = 0
  2. j 的值赋给 v8
  3. 调用 j_strlen 函数计算 Str2 的长度并存储在 v2 中。
  4. 检查 v8 > v2 是否成立,若成立则跳出循环;若不成立则继续。
  5. 检查 Str2[j] 是否等于 111,若相等则将其替换为 48。
  6. j 自增 1。
  7. 重复步骤 2 – 6,直至满足循环终止条件。

了解大概后,补充一个小知识点——我们可以对111,48这些数字单击后按r将数字转换为字符,eg.

    if ( Str2[j] == 'o' )
      Str2[j] = '0';

分析完这段代码,再往上也没有关于str2的信息了,我们尝试双击一下str2看看会不会有啥惊喜

诶,我们看到一个{}括起来的字符串,有点像我们要提交的flag格式,然后想到上面要把o换成0

我们把它复制一下,把o换成0,然后再最前面加上flag

回到题目提交看看

Yes,成功解出!给自己的进步点个小赞吧!

3、reverse2(学会找信息、代码分析)

题目

做法

下载压缩包,解压,把解压后的文件拖进Exeinfo PE进行分析

64位,无壳

扔进IDA(64位),找到main,F5反编译

老规矩,从得出flag那里开始往上看

if ( !strcmp(&flag, &s2) )
    result = puts("this is the right flag!");

如果&flag里的内容等于&s2里的内容,我们的flag就是对的

继续往上

  printf("input the flag:", argv);
  __isoc99_scanf("%20s", &s2);

不难发现,s2里的内容是用户输入的内容

for ( i = 0; i <= strlen(&flag); ++i )
    {
      if ( *(&flag + i) == 105 || *(&flag + i) == 114 )
        *(&flag + i) = 49;
    }

一个for循环,里面有关于&flag的内容

  1. 将循环变量 i 赋值为 0 。
  2. 每次循环时,调用 strlen 函数计算 flag 字符数组的长度(&flag 传递的是数组地址) 。
  3. 检查 i 是否小于等于计算得到的字符串长度,若成立则进入循环体,否则结束循环。
  4. 在循环体中,通过指针运算 *(&flag + i) 访问 flag 数组中索引为 i 的字符,判断该字符的 ASCII 码值是否为 105(字符 'i')或者 114(字符 'r')。若满足条件,则将该字符替换为 ASCII 码值为 49 的字符(字符 '1')。
  5. 循环体执行完毕后,将 i 的值自增 1,回到步骤 3 继续判断循环条件,重复上述过程直至循环结束。

看到数字,我们先按r把它变成字符

if ( *(&flag + i) == 'i' || *(&flag + i) == 'r' )
        *(&flag + i) = '1';

要把i/r转换成1

然后我们双击flag进去看看有没有东西

有个疑似flag格式的字符串

复制下来,补全格式,然后把i/r转换成1提交看看

成功解出

4、内涵的软件(眼神好+小经验)

题目

做法

下载文件,拖进Exeinfo PE进行分析

32位,无壳

扔进IDA(32位),找到main,F5反编译

一眼看到第9行{}括起来的内容,有点像flag的格式,复制下来,括号前加flag,返回题目试试

哦吼,成功了

5、新年快乐(upx脱壳)

题目

做法

下载压缩包,解压,把解压后的文件拖进Exeinfo PE进行分析

32位,有upx壳,需脱壳(自行下载upx脱壳工具)

返回桌面搜索cmd,以管理员身份运行命令提示符

cd命令切换到你下载的upx脱壳工具(总的,不是某一个文件)存放地址

然后输入代码

upx -d 要脱壳的文件地址

脱壳成功

再把文件拖入Exeinfo PE

已经没有壳了

这时再扔进IDA(32位),找到main,F5反编译(注:这里有俩main,对比一下就很清楚地知道该分析哪个)

那么好,现在我们开始从关键点从上往下进行分析

if ( !strncmp((const char *)&v5, &v4, strlen(&v4)) )
    result = puts("this is true flag!");

如果v5 的前 strlen(&v4) 个字符和 v4 相同,就会输出 "this is true flag!"

然后再往上看

scanf("%s", &v5);

v5是用户的输入内容储存地址

因此,我们需要找到v4的内容是什么

strcpy(&v4, "HappyNewYear!");
  v5 = 0;
  memset(&v6, 0, 0x1Eu);

继续往上看,关键内容就这些了

复制HappyNewYear!到v4…

诶,v4这不找到了嘛

结合上面的分析,我们把v4的内容打包一下就是这题的flag啦

6、xor(异或脚本编写)

题目

做法

下载压缩包,解压,把解压后的文件拖进Exeinfo PE进行分析

(注:当我们进行附件解压时会出现MACOX这一个文件夹,这个是属于mac端解压下来的垃圾文件,不必理会,你拖进Exeinfo PE也不会有什么信息给你)

64位,无壳

扔进IDA(64位),找到main,F5反编译

没有什么像flag这样的直接关键词,我们直接从上往下分析一下代码

上面都是一些无关痛痒的东西,简单会看懂就行,我们看到这里

for ( i = 1; i < 33; ++i )
    v6[i] ^= v6[i - 1];

代码功能:

这段代码是一个 for 循环,其功能是对数组 v6 进行按位异或(XOR)操作。

具体步骤如下:

  1. 循环从索引 i = 1 开始,到 i < 33 结束。也就是说,循环会对数组 v6 中索引从 1 到 32 的元素进行处理。
  2. 在每次循环中,使用按位异或运算符 ^ 将当前元素 v6[i] 与前一个元素 v6[i - 1] 进行异或操作,然后将结果重新赋值给 v6[i]

然后继续往下

if ( !strncmp(v6, global, 0x21uLL) )
    printf("Success", v3);

比较数组 v6global 的前 0x21uLL(十六进制,转换为十进制是 33)个字符。

若这前 33 个字符完全相同,就会输出 "Success"

结合全文进行分析,v3=global,我们的输入内容存放地址为v6

即我们输入内容和v3相等,就返回”成功”

先提取v3(global)的值,点v3点不进去,我们双击global

再双击红框圈起的地方

按Shift+E导出数据

根据global的值,再结合上面的for循环,我们就可以编写一个异或脚本,反向推出这道题的flag

(输入一个值进行异或操作后,再对异或结果使用相同的密钥进行一次异或操作,得到的是最初输入的值,也就是我们原本输入的还没有进行过异或操作的值)

exp(一般我们写脚本都是用Python写的,可以自行下载PyCharm等进行编写)

list1 = [0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11,
         0x78, 0x0D, 0x5A, 0x3B, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F,
         0x76, 0x22, 0x4D, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F,
         0x47, 0x32, 0x4F, 0x00]
flag = chr(list1[0])      #结果为f   因为异或不会处理数据中第一个的值,但它也是flag的一部分,保留
                          #chr的作用是将一个整数(代表 Unicode 码位)转换为对应的 Unicode 字符
                          #我们的flag通常不是数字,而是字符串形式,这样做的目的是构建出一个字符串形式的 flag,下同
# 使用 for 循环从第二个元素开始进行异或操作
for i in range(1, len(list1)):
  flag += chr(list1[i] ^ list1[i - 1])    #从输入的第二个数据开始,将其与前一位异或
print(flag)

运行,得出结果,去掉最后的0就是我们这道题的答案啦

7、reverse3(Base加密)

题目

做法

下载压缩包,解压,把解压后的文件拖进Exeinfo PE进行分析

32位,无壳

扔进IDA(32位),找到main,F5反编译

只是因为在人群中多看了你一眼——第31行的right flag,关键词找到,我们就从这里开始向上分析

if ( !strncmp(Dest, Str2, v2) )
    sub_41132F("rigth flag!\n");

如果Dest和Str2的前v2个字符相同,系统会打印一个right flag给我们,sub_41132F不难猜出是print

我们点进Dest和Str2看看有啥东西(Dest没啥东西,Str2点进去如下,得到Str2的值)

结合上面分析,得出Dest的值(即Str2的值)

往上继续分析

for ( j = 0; j < v8; ++j )
    Dest[j] += j;

一个for循环,Dest[0] = Dest[0]+0,Dest[1] = Dest[1]+1 ……这般规律为结果生成下去,直到j=v8(该循环对Dest做了变化)

再往上就没啥了

但是有一个点不清楚它的作用

点进sub_4110BE看看

继续点进去

遍历一下

(注:看到第16行的 3 和第19行的 4 ,这里就是base64对字符的二进制编码做的处理(经验之谈),要不确定的话我们可以继续往下看,这是定义了v9和v10来存放base64对字符的二进制变化;

Base64加密的核心逻辑(分组、补零、填充)必须通过if判断和循环来实现。下面还有if判断和while循环还有for循环,至此,我们基本可以判断本题是Base64加密题)

exp

import base64           #导入模块
Des = "e3nifIH9b_C@n@dH"    #定义目标字符串
flag = ""                   #初始化结果字符串
for i in range(len(Des)):
    flag += chr(ord(Des[i]) - i)    #Des[i]:取出字符串 Des 中索引为 i 的字符。
                                    #ord(Des[i]):使用 ord() 函数获取()内字符的 ASCII 码值。
                                    #ord(Des[i]) - i:将该 ASCII 码值减去其索引位置 i。
print(base64.b64decode(flag))       #进行 Base64 解码并输出结果

得出结果,’ ‘里的内容换成flag{}格式提交即可

8、helloword(安卓逆向工具简单利用)

题目

做法

下载,不要解压,直接拖入Exeinfo PE进行分析

文件后缀是apk,判断为安卓逆向题

拖进ApkIDE

先找主函数main函数,这题的flag直接出来了

(搜索内容不要习惯性空格之类,这样会找不出来)

9、不一样的flag(迷宫题)

题目

做法

下载压缩包,解压,把解压后的文件拖进Exeinfo PE进行分析

32位,无壳

扔进IDA(32位),找到main,F5反编译

没啥关键词,Shift+F12也找不到什么有用的点

从上往下分析吧

    puts("1 up");
    puts("2 down");
    puts("3 left");
    printf("4 right\n:");

选1234都有对应的上下左右选择,是迷宫题吗

scanf("%d", &v5);
if ( v5 == 2 )
    {
      ++*(_DWORD *)&v3[25];
    }
    else if ( v5 > 2 )
    {
      if ( v5 == 3 )
      {
        --v4;
      }
      else
      {
        if ( v5 != 4 )
LABEL_13:
          exit(1);
        ++v4;
      }
    }
    else
    {
      if ( v5 != 1 )
        goto LABEL_13;
      --*(_DWORD *)&v3[25];
    }

把我们输入的值放进v5,根据我们输入的数字不同,v3或v4会有不同变化,亦或是异常退出

      if ( *(_DWORD *)&v3[4 * i + 25] >= 5u )
        exit(1);
    }
    if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 49 )
      exit(1);
    if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 35 )
    {
      puts("\nok, the order you enter is the flag!");

我们输入的值让v3或v4变化,满足不同公式,系统会异常退出亦或是返回’好的,你输入的顺序就是标志‘

至此,就再无别的信息了

我们尝试双击运行解压后的文件

分别输入1234进行测试

尝试多次后,发现规律——每次弹出来的四个选项中,只有一个是正确答案,且这个正确答案不是固定的,要自己不断慢慢试出来

结合IDA的代码分析,我们要去算那个公式的话,有很多种不同组合方式,而且更繁杂

我们选择直接打开文件一个个测,测出一个正确的就记下来,直到返回\nok, the order you enter is the flag!

最后成果——222441144222,包上flag{}去提交吧!

10、SimpleRev(大小端序,算法分析)

题目

做法

下载文件,拖进Exeinfo PE

64位,无壳

扔进IDA(64位),找到main,F5反编译

有数字,我们按r转换为字符看看

若v4=d/D,我们就可以进入Decry函数

若v4=q/Q,我们就退出

输入除去这几个的其他字母,结果均是break

putchar函数呢,则是将用户输入的字符回显到屏幕,没啥作用

我们点进去Decry函数看看

找到真面目了

  if ( !strcmp(text, str2) )
    puts("Congratulation!\n");

看到这里的congratulation,结合上面的Please input your flag,我们就知道该从这里往上分析了

congratulation的条件是:让text等于str2

然后我们自上而下进行分析(以下只节选重要部分进行分析)

一、变量初始化与内存布局

v11 = __readfsqword(0x28u);
*(_QWORD *)src = 0x534C43444ELL;
v9[0] = 0x776F646168LL;
  1. 栈保护机制
    • v11 = __readfsqword(0x28u):读取线程局部存储(TLS)中的金丝雀值(canary),用于检测栈溢出攻击。
  2. 字符串初始化(小端序存储)
    • src 被初始化为 0x534C43444ELL(十进制 357761762382):
      • 按字节拆分:0x53('S') 0x4C('L') 0x43('C') 0x44('D') 0x4E('N')
      • 小端序存储后内存布局为:4E 44 43 4C 53 → 字符串为 "NDCLS"
    • v9[0] 被初始化为 0x776F646168LL
      • 按字节拆分:0x77('w') 0x6F('o') 0x64('d') 0x61('a') 0x68('h')
      • 小端序存储后内存布局为:68 61 64 6F 77 → 字符串为 "hadow"

二、密钥与目标字符串生成

text = (char *)join(key3, v9);
strcpy(key, key1);
strcat(key, src);
  1. 生成目标字符串 text

    • join(key3, v9) 将 key3 和 "hadow" 连接,结果存储在 text 中。 key1和key3都是一样,双击进去即可看到其值
  2. 生成加密密钥 key

    • key 初始化为 key1,再追加 "NDCLS"
  3. 密钥处理

    • 将 key 中的所有大写字母转换为小写:
        for (i = 0; i < v5; ++i) {
            if (key[v3 % v5] > 64 && key[v3 % v5] <= 90)
                key[i] = key[v3 % v5] + 32;  // 大写转小写
            ++v3;
        }
  • 条件判断key[v3 % v5] > 64 && key[v3 % v5] <= 90
    检查字符是否为大写字母(ASCII 范围 65-90)

  • 转换操作key[i] = key[v3 % v5] + 32
    将大写字母转换为小写(ASCII 中,小写字母比大写字母大 32)

当 v3 从 0 递增到 9 时,v3 % v5 的结果为 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 因此,v3 % v5 会循环遍历 key 的每个字符(0 到 v5-1) key[i]是数组,索引从0开始,因此,上面结果为0的key[0]即是key的第一个元素

%:百分号,表示求余运算,如5%4=1

注:

可能会有觉得上面的再扩展的话(比如v3=10,v5=10,v3%v5时结果为key[0]与v3=0,v5=10时也是等于key[0]这般重复的情况)会有俩个key[0],key[1]等 但是这种情况是不存在的,这种情况已经跳出了v5的范围,想法也是错误的 因为程序规定为< v5,这个想法已经不属于这个范畴了 v3不可能取值到10,若其取值到10,则与< v5冲突了 若v3取值到10,则说明v5有11个元素,但是v5只有10个元素

正确的:(假设v5为14)(与上面作出区分,便于观察) (一般规律,下次碰到一样的就不用继续算了)

迭代 i v3 v3 % 14 key[v3 % 14] 操作
1 0 0 0 ‘a’ (97) 不转换
2 1 1 1 ‘b’ (98) 不转换
14 13 13 13 ‘n’ (110) 不转换

上面说的有俩个key[0],key[1]这般重复的情况还有一种为v5被误设为较小值 这种情况的话一般会提前结束,若还是此题目的逻辑的话(< v5)则程序只会处理key的前v5个字符,而不会处理v5及其后字符

因此,无论何种情况,均不会有俩个key[0],key[1]这般重复的情况

补充: 在 C 语言中,字符本质上是以ASCII 码(或其他字符编码)的形式存储的。当你对字符进行数学运算时,C 语言会自动将字符转换为对应的整数值(即 ASCII 码值)

    char c = 'A';
    int num = c + 32;    // 'A'的ASCII码(65) + 32 = 97
    printf("%c\n", num); // 输出: 'a' (ASCII码97对应的字符)

三、用户输入处理与加密过程

printf("Please input your flag:");
while (1) {
    v1 = getchar();
    if (v1 == 10)  // 换行符
        break;
    if (v1 == 32)  // 空格
    {
      ++v2;
    }              //在此之下就是else开始加密,因此为过滤掉这俩

    // 加密逻辑
    if ( v1 <= 96 || v1 > 122 )   //非小写字母ascll码范畴
      {
        if ( v1 > 64 && v1 <= 90 )  //如果是大写字母
        {
          str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;
//任何整数除以 26 的余数必定在 0-25 之间
//最后+97是把ascll码范围回到小写字母的ascll码范围(即(0~25)+97对应)
          ++v3;
        }
    }
    else    //小写字母
    {
        str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;  //同上处理方式
        ++v3;
    }
    if ( !(v3 % v5) ) //如果v3是v5的整数倍(即处理完一轮密钥)
      //本来 % 是取余运算,算出结果是余数,这里前面加了!即为没有余数,则只能是整数
        putchar(32);   //输出一个空格
    ++v2;
  1. 输入过滤
    • 忽略换行符(\n)和空格。
  2. 加密算法

    • 对非小写字母进行处理:

        str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;
  3. 密钥循环使用
    • 密钥 key 循环使用(通过 v3 % v5 实现)
    • 每处理完一轮密钥(即 v3 是密钥长度的整数倍),输出一个空格。

四、验证与结果

if (!strcmp(text, str2))
    puts("Congratulation!\n");
else
    puts("Try again!\n");
  • 将用户输入加密后的结果(str2)与目标字符串(text)比较。
  • 若匹配,输出 "Congratulation!",否则输出 "Try again!"

五、解密方法

要逆向推导出原始 flag,需根据加密公式编写解密函数:

key = "adsfkndcls"  
text = "killshadow"  
v3 = 0 
for i in range(10):  #10代表我们要解密的字符数
    for j in range(128):  
        # 跳过非字母字符  
        if j < ord('A') or (j > ord('Z') and j < ord('a')) or j > ord('z'):  
            continue  
        # 这里的ord是让字符变成ascll码,这里说的是范围规定在英文大小写字母范围内
        # 核心解密公式  
        if (j - 39 - ord(key[v3 % 10]) + 97) % 26 + 97 == ord(text[i]):  
            print(chr(j), end='')  
            v3 += 1  
            break

得出flag,返回题目提交

补充

1、大小端序

eg.0x123456 0x12通常是高字节,然后0x56是低字节(一般都是这么个顺序,前高后低)

大端序:高字节存放在低地址,低字节存放在高地址 小端序:高字节存放在高地址,低字节存放在低地址

二者正好相反

读取顺序一般是从低地址开始读起

eg.0x123456 大端序:从0x12开始向右读 小端序:从0x56开始向左读

2、如何判断题目给的到底是大端序还是小端序

(1)查看方法

大小端序可以使用软件查看,比如:Detect It Easy
可以看到字节序(小LE)/(大BE)

(2)代码中隐含的端序:

当代码使用整数直接初始化字符串时(如*(QWORD*)src = ...),默认依赖于编译环境的端序(如 x86 为小端序)。

(3)显式判断端序

        int isLittleEndian() {
            int num = 1;
            return (*(char*)&num == 1); // 1为小端序,0为大端序
        }

(4)常见应用场景:

小端序:x86/AMD64 架构(Windows/Linux)、ARM 架构(移动端)。 大端序:网络协议(如 IP 地址)、Java 字节码、部分嵌入式系统(如 PowerPC)。 混合场景:文件格式(如 BMP 为小端序,PNG 为大端序)。

感谢阅读!如果你觉得这篇文章对你有帮助,欢迎扫码赞赏支持,你的鼓励是我持续创作的动力 ❤️

本文为原创内容,转载请注明出处。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇