Reverse 攻防世界合集
基础
1※ 1000Click
-
考点:动态调试
-
exeinfope 日常查壳,发现是 window 32 程序,无壳:
-
IDA 32 打开,等待加载一会儿,发现一大堆函数名,在左侧搜索 main 发现一个加粗加黑的 WinMain,这是带窗口的 windows 32程序的入口:
-
但是稍微看代码发现,这个函数只是实现窗口的维护,功能的实现并不在这里,那么我们把程序在 gdb 中运行起来(按几下 F9 直到出现窗口):
-
我们在点击 Click 时,出现了一个弹窗,有一个字符串提示
Error!!!
: -
那么此时我们在 gdb 中搜索字符串
Error!!!
: -
点进去往上找一点,就能找到一个 flag,提交就能通过:
1※ xxxorrr
-
考点:基础异或
-
exeinfope 日常查壳,发现是 window 64 程序,无壳:
-
IDA 64 打开,将 main 内的函数根据英文名称提示简单逆向一下:
-
程序先将 check 函数设置为 exit 执行前调用的 callback 函数,这会在程序 exit 时执行,也就是所有加密结束后。
-
查看检查逻辑,将 enc 和一个 aim 全局字符串比较:
-
我们对 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 进行加密了:
-
那么我们先将 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位程序:
-
IDA 32 直接打不开的,拖进 x32dbg 动态调试:
-
按两下 F9,看到一句标志性 pushfd,全程不要滚动鼠标滚轮,会影响代码反编译:
-
F8 几下,执行完两个 push 保存环境后,右键 ESP,在内存中查看:
-
此时左下方的内存视图中,ESP 的起始地址已经高亮显示了,在上面右键下一个硬件断点。注意 ESP 为 4字节 寄存器,下 Dword 类型的断点后,只要访问这个位置的保存的 4字节 数据,就会触发断点:
-
接下来按一下 F9,就会断在再次访问内存该位置,也就是还原现场的时候。可以向上滚动滚轮,看到一个 popad,一个 popfd,在 popfd 下方的 jmp 就是跳转回原先要执行的未加壳函数地址,我们在 jmp 上下一个断点方便下次调试:
-
我们按 2下 F7 步入,直到进入 jmp 里,看到一句 call,此时已经跳出了壳里面的代码,这里就是原先未加壳的程序逻辑了。有的题解要进入 call 里面,但是真正的源代码是从这里就开始的,脱壳也是从这个地址开始:
-
快捷键 Ctrl+I 打开自带的 scylla 脱壳,有时候上方的插件里面不显示这个 自带脱壳插件,直接使用快捷键打开:
-
先点击 IAT Autosearch 自动查找可能得 IAT 表,点确定:
-
然后点 Get Imports 获取导入库:
-
接着点 Dump,保存为一个文件:
-
然后点 PE Rebuild 重建刚才导出的文件:
-
最后点 Fix Dump 修复重建后的文件:
-
此时一共新生成了 3 个文件,其中 dumped_SCY.exe 这个文件就是我们脱完壳的文件:
-
将其拖入 IDA 32打开,此时便可以看到程序的代码了:
-
这题的考点是手动脱壳,可以发现原来的代码逻辑比较简单,将输入的长度为 42 的字符串 与一个固定字符串异或,最后结果为一个保存的字符串:
-
那么我们将里面的两个字符串扒下来,注意目标数组的数据类型为 dd,所以扒下来的时候每 4个字节为一个数字:
-
最后写出 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:
Maze
3※ easy_Maze
-
考点:迷宫基础
-
日常查壳,无壳,发现是 64位 elf 文件:
-
IDA 64 打开查看,发现一大堆初始化:
-
往下看程序,主要经过三个函数,Step_0,Step_1 和 Step_2:
-
结合传入 Step_0 的参数,发现是将 v6 后半部分的内容,映射到前半部分:
-
Step_1 也是同理,将 v6 前半部分的 7*7 数组,在经过 getA 和 getAStart 两个函数变换后,映射到数组 v5 中:
-
我们查看 getA 函数,发现只是进行了 递归 和 数组上的映射,具体细节不比较麻烦,暂时放着不管:
-
查看 getAStart 函数,也并没有看到使用其他变量和数组,也只是将输入的二维数组变换一下返回:
-
那么我们就可以放心地不去管这两个初始化函数了,只需要通过动态调试在初始化后直接获得数组的最后值即可,我们发现 Step_2 的函数传入经过初始化后的 v5 数组:
-
查看汇编代码,发现此时作为参数,v5 的地址存在了 rdi 中:
-
那么我们在 Step_2 函数处下一个断点,然后查看 rdi 存储的地址里面的数组,需要注意的是由于是 int 数组,需要用
x/49wd
查看,表示 4个字节 为一个数字,查看 49个数字(我这里使用 pwndbg 工具进行动态调试): -
然后 run 运行起来,发现断在了刚进入 Step_2 函数的地方:
-
那么此时我们可以使用
x/49wd $rdi
查看 rdi 存储的数组的值: -
查看 Step_2 函数的逻辑,初始化为 (0,0) 要走到 (6,6) 也就是右下角的位置:
-
将几个 ASCII 转为字符,发现通过 w,s,a,d 进行移动,并且每一步都需要走在数字为 1 的格子上:
-
那么我们瞪眼法走出一条路径
ssddwdwdddssaasasaaassddddwdds
,然后输出长度是否小于等于 30: -
那么最后包裹得到 flag:
UNCTF{ssddwdwdddssaasasaaassddddwdds}