Reverse 攻防世界合集

基础

1※ 1000Click

  • 考点:动态调试

  • exeinfope 日常查壳,发现是 window 32 程序,无壳:

    image

  • IDA 32 打开,等待加载一会儿,发现一大堆函数名,在左侧搜索 main 发现一个加粗加黑的 WinMain,这是带窗口的 windows 32程序的入口:

    image

  • 但是稍微看代码发现,这个函数只是实现窗口的维护,功能的实现并不在这里,那么我们把程序在 gdb 中运行起来(按几下 F9 直到出现窗口):

    image

  • 我们在点击 Click 时,出现了一个弹窗,有一个字符串提示 Error!!!

    image

  • 那么此时我们在 gdb 中搜索字符串 Error!!!

    image

  • 点进去往上找一点,就能找到一个 flag,提交就能通过:

    image



1※ xxxorrr

  • 考点:基础异或

  • exeinfope 日常查壳,发现是 window 64 程序,无壳:

    image

  • IDA 64 打开,将 main 内的函数根据英文名称提示简单逆向一下:

    image

  • 程序先将 check 函数设置为 exit 执行前调用的 callback 函数,这会在程序 exit 时执行,也就是所有加密结束后。

  • 查看检查逻辑,将 enc 和一个 aim 全局字符串比较:

    image

  • 我们对 aim 按 x 交叉引用,发现没有其他加密,那么这就是最终的加密结果,我们扒下来:

    str_ = "56 4E 57 58 51 51 09 46 17 46 54 5A 59 59 1F 48 32 5B 6B 7C 75 6E 7E 6E 2F 77 4F 7A 71 43 2B 26 89 FE "
    aim = []
    for i in range(0,len(str_),3):
    	aim.append(int("0x" + str_[i:i+3],16))
    
  • 对 enc 按 x 交叉引用,发现还有一个函数在 init 函数中调用对 enc 进行加密了:

    image

  • 那么我们先将 enc 扒下来:

    enc = [ord(x) for x in "qasxcytgsasxcvrefghnrfghnjedfgbhn"] + [0x0]
    
  • 按照逻辑进行两次异或:

    for i in range(len(enc)):
    	enc[i] ^= 2*i + 65
    for i in range(len(enc)):
    	enc[i] = enc[i] ^ aim[i]
    
  • 便得到最后的 flag,完整 exp:

    enc = [ord(x) for x in "qasxcytgsasxcvrefghnrfghnjedfgbhn"] + [0x0]
    print(len(enc))
    str_ = "56 4E 57 58 51 51 09 46 17 46 54 5A 59 59 1F 48 32 5B 6B 7C 75 6E 7E 6E 2F 77 4F 7A 71 43 2B 26 89 FE "
    aim = []
    for i in range(0,len(str_),3):
    	aim.append(int("0x" + str_[i:i+3],16))
    print(len(aim))
    for i in range(len(enc)):
    	enc[i] ^= 2*i + 65
    for i in range(len(enc)):
    	enc[i] = enc[i] ^ aim[i]
    for x in enc:
    	print(chr(x),end='')
    # flag{c0n5truct0r5_functi0n_in_41f}
    


3※ crackme

  • 考点:esp 定理脱壳

  • exeinfo 查壳,发现有 nsPack,是 32位程序:

    image

  • IDA 32 直接打不开的,拖进 x32dbg 动态调试:

    image

  • 按两下 F9,看到一句标志性 pushfd,全程不要滚动鼠标滚轮,会影响代码反编译:

    image

  • F8 几下,执行完两个 push 保存环境后,右键 ESP,在内存中查看:

    image

  • 此时左下方的内存视图中,ESP 的起始地址已经高亮显示了,在上面右键下一个硬件断点。注意 ESP 为 4字节 寄存器,下 Dword 类型的断点后,只要访问这个位置的保存的 4字节 数据,就会触发断点:

    image

  • 接下来按一下 F9,就会断在再次访问内存该位置,也就是还原现场的时候。可以向上滚动滚轮,看到一个 popad,一个 popfd,在 popfd 下方的 jmp 就是跳转回原先要执行的未加壳函数地址,我们在 jmp 上下一个断点方便下次调试:

    image

  • 我们按 2下 F7 步入,直到进入 jmp 里,看到一句 call,此时已经跳出了壳里面的代码,这里就是原先未加壳的程序逻辑了。有的题解要进入 call 里面,但是真正的源代码是从这里就开始的,脱壳也是从这个地址开始:

    image

  • 快捷键 Ctrl+I 打开自带的 scylla 脱壳,有时候上方的插件里面不显示这个 自带脱壳插件,直接使用快捷键打开:

    image

  • 先点击 IAT Autosearch 自动查找可能得 IAT 表,点确定:

    image

  • 然后点 Get Imports 获取导入库:

    image

  • 接着点 Dump,保存为一个文件:

    image

  • 然后点 PE Rebuild 重建刚才导出的文件:

    image

  • 最后点 Fix Dump 修复重建后的文件:

    image

  • 此时一共新生成了 3 个文件,其中 dumped_SCY.exe 这个文件就是我们脱完壳的文件:

    image

  • 将其拖入 IDA 32打开,此时便可以看到程序的代码了:

    image

  • 这题的考点是手动脱壳,可以发现原来的代码逻辑比较简单,将输入的长度为 42 的字符串 与一个固定字符串异或,最后结果为一个保存的字符串:

    image

  • 那么我们将里面的两个字符串扒下来,注意目标数组的数据类型为 dd,所以扒下来的时候每 4个字节为一个数字:

    image

  • 最后写出 python 脚本解密:

    xor = [ord(x) for x in "this_is_not_flag"]
    aim = [0x12, 0x4 , 0x8 , 0x14, 0x24, 0x5C, 0x4A, 0x3D, 0x56, 0x0A,
    	   0x10, 0x67, 0x0 , 0x41, 0x0 , 0x1 , 0x46, 0x5A, 0x44, 0x42,
    	   0x6E, 0x0C, 0x44, 0x72, 0x0C, 0x0D, 0x40, 0x3E, 0x4B, 0x5F,
    	   0x2 , 0x1 , 0x4C, 0x5E, 0x5B, 0x17, 0x6E, 0x0C, 0x16, 0x68,
    	   0x5B, 0x12]
    flag = []
    for i in range(len(aim)):
    	flag.append(xor[i%16]^aim[i])
    for x in flag:
    	print(chr(x),end='')
    
  • 运行得到 flag:

    image



Maze

3※ easy_Maze

  • 考点:迷宫基础

  • 日常查壳,无壳,发现是 64位 elf 文件:

    image

  • IDA 64 打开查看,发现一大堆初始化:

    image

  • 往下看程序,主要经过三个函数,Step_0,Step_1 和 Step_2:

    image

  • 结合传入 Step_0 的参数,发现是将 v6 后半部分的内容,映射到前半部分:

    image

  • Step_1 也是同理,将 v6 前半部分的 7*7 数组,在经过 getA 和 getAStart 两个函数变换后,映射到数组 v5 中:

    image

  • 我们查看 getA 函数,发现只是进行了 递归 和 数组上的映射,具体细节不比较麻烦,暂时放着不管:

    image

  • 查看 getAStart 函数,也并没有看到使用其他变量和数组,也只是将输入的二维数组变换一下返回:

    image

  • 那么我们就可以放心地不去管这两个初始化函数了,只需要通过动态调试在初始化后直接获得数组的最后值即可,我们发现 Step_2 的函数传入经过初始化后的 v5 数组:

    image

  • 查看汇编代码,发现此时作为参数,v5 的地址存在了 rdi 中:

    image

  • 那么我们在 Step_2 函数处下一个断点,然后查看 rdi 存储的地址里面的数组,需要注意的是由于是 int 数组,需要用 x/49wd 查看,表示 4个字节 为一个数字,查看 49个数字(我这里使用 pwndbg 工具进行动态调试):

    image

  • 然后 run 运行起来,发现断在了刚进入 Step_2 函数的地方:

    image

  • 那么此时我们可以使用 x/49wd $rdi 查看 rdi 存储的数组的值:

    image

  • 查看 Step_2 函数的逻辑,初始化为 (0,0) 要走到 (6,6) 也就是右下角的位置:

    image

  • 将几个 ASCII 转为字符,发现通过 w,s,a,d 进行移动,并且每一步都需要走在数字为 1 的格子上:

    image

  • 那么我们瞪眼法走出一条路径 ssddwdwdddssaasasaaassddddwdds,然后输出长度是否小于等于 30:

    image

  • 那么最后包裹得到 flag:

    UNCTF{ssddwdwdddssaasasaaassddddwdds}



posted @ 2024-11-22 22:05  浅叶梦缘  阅读(2)  评论(0编辑  收藏  举报