buuoj 逆向刷题之旅(三)
以下是本文涉及到的关键词:
- 架构:x86, mips, JVM
- 文件类型:exe, elf, apk
- 考点:angr, maze, base64, .NET, Unity游戏, sm4, rc4, 混淆, SMC, upx
PS. 用到的 python 脚本运行环境除特殊标注外均为 python2,如果内容有误或者图片缺失,欢迎联系我修正。
[FlareOn2]very_success | x86,exe,angr
ida 打开,start 直接调用了 sub_401000,判断程序应该是用汇编写的。sub_401000 由于栈不平衡反编译失败,打开栈指针,在导致分析错误的一行 Alt + k,将 sp 值设置为 0:
再 F5:
1 | BOOL __usercall sub_401000@<eax>(int a1@<ebp>) |
只有一个检验函数 sub_401084,这个函数的汇编指令里 5 个我有 4 个不认识(我很骄傲)。动调后发现检查的思路是每次取来输入的一个字符(输入有 37 个字符),经过一堆运算后,倒着从 0x401108 开始向低地址逐一比较。正好 start 函数的 call sub_401000
下面有 37 个字节:
具体咋算的我也不想弄明白了,上 angr 跑吧:
1 | # python3 |
[SCTF2019]babyre | x86,elf,maze,base64,sm4
main 函数有几处花指令,把它们 nop 掉以后反编译。逻辑很清晰,一共有三关,分别是:
- 5 * 5 * 5 的三维迷宫
- 将输入 base64 解码,得到的结果应该是 sctf_9102
- 对输入进行类似 sm4 加密算法的编码,结果与目标数组比较
三维迷宫提取出来:
每层 5 * 5,共 5 层,入口在 s,出口在 #,. 可以通行,* 不能通行。每次行动有 6 个方向可选,同一层内:上(w)、下(s)、左(a)、右(d),不同层间:下一层(x)、上一层(y)。走出来最短路径为 ddwwxxssxaxwwaasasyywwdd
第二个部分的输入应为 base64(“sctf_9102”) = c2N0Zl85MTAy
第三个部分需要分析 sub_400FFA,脚本:
1 | table = "D6 90 E9 FE CC E1 3D B7 16 B6 14 C2 28 FB 2C 05 2B 67 9A 76 2A BE 04 C3 AA 44 13 26 49 86 06 99 9C 42 50 F4 91 EF 98 7A 33 54 0B 43 ED CF AC 62 E4 B3 1C A9 C9 08 E8 95 80 DF 94 FA 75 8F 3F A6 47 07 A7 FC F3 73 17 BA 83 59 3C 19 E6 85 4F A8 68 6B 81 B2 71 64 DA 8B F8 EB 0F 4B 70 56 9D 35 1E 24 0E 5E 63 58 D1 A2 25 22 7C 3B 01 21 78 87 D4 00 46 57 9F D3 27 52 4C 36 02 E7 A0 C4 C8 9E EA BF 8A D2 40 C7 38 B5 A3 F7 F2 CE F9 61 15 A1 E0 AE 5D A4 9B 34 1A 55 AD 93 32 30 F5 8C B1 E3 1D F6 E2 2E 82 66 CA 60 C0 29 23 AB 0D 53 4E 6F D5 DB 37 45 DE FD 8E 2F 03 FF 6A 72 6D 6C 5B 51 8D 1B AF 92 BB DD BC 7F 11 D9 5C 41 1F 10 5A D8 0A C1 31 88 A5 CD 7B BD 2D 74 D0 12 B8 E5 B4 B0 89 69 97 4A 0C 96 77 7E 65 B9 F1 09 C5 6E C6 84 18 F0 7D EC 3A DC 4D 20 79 EE 5F 3E D7 CB 39 48 C6 BA B1 A3 50 33 AA 56 97 91 7D 67 DC 22 70 B2".split(' ') |
[QCTF2018]Xman-babymips | mips,elf
mips 架构下的 elf,mips 静态分析(反编译)的软件就那几个(IDA7.5, ghidra, jeb-mips, retdec),用 IDA7.5 的话事情将会非常简单,毕竟氪金的力量不容小觑。下面我使用 免费 的 ghidra 来做这道题
ghidra 打开程序,Functions 里没找见 main 函数,那就从 entry 开始看,上来就调用 SUB_00400694:
SUB_00400694 中调用 __libc_start_main 时,第一个参数就是 main 函数,这里是 FUN_004009a8:
然后就是对符号的还原:
1 | void main(void) |
逻辑很简单,先对长度为 32 的输入进行一个异或变换,验证前 5 个字节,然后调用 FUN_004007f0 验证剩余的 27 个字节:
1 | void FUN_004007f0(char *input) |
脚本:
1 | flag = '' |
[SCTF2019]Who is he | x86,exe,.NET,Unity游戏
dnSpy 打开 Assembly-CSharp.dll,在 TestClick 类中找到 OnClick 方法:
分析一下 Decrypt 方法,就是一个简单的 DES_CBC 解密,密文、key、iv 都能找到,所以上脚本:
1 | # python3 |
在程序中提交发现不对。于是尝试 patch Assembly-CSharp.dll,发现不论怎么改(比如直接弹出 flag、输入框的内容改为 flag),程序都没有变动,于是猜测按钮部分的功能在另外的 dll 中进行了实现。来到 Data/Managed 目录下,按修改日期对所有 dll 进行排序:
最新被修改的只有四个,用 CE 进行附加,选择 Mono -> 分析 mono
,把这 4 个模块都展开,发现 UnityEngine.UmbraModule 模块十分可疑,成员变量和方法与 TestClick 类基本相同:
在类 UnityEngine.UmbraModule.Main 处右键,选择 Fields -> Add Static Field Address
,回到 CE 主界面,激活 lua 脚本,浏览 encryptKey 附近内存,发现真正的 key 和 iv:
在类方法 Decrypt 处右键,选择 Jit
,在函数入口打个断点,点击按钮,程序断下来,第一个参数在 rdx 中,该参数就是成员变量 EncryptData:
提取出来替换上面脚本中的 encryptData 和 keyiv,得到真正的 flag:
1 | import base64 |
[FlareOn1]Javascrap | misc
走错片场的一道题。php 代码在图片 flare-on.png 的末尾,提取出来:
1 |
|
直接 echo 输出一下 $do_me
,得到:
1 | $_= 'aWYoaXNzZXQoJF9QT1NUWyJcOTdcNDlcNDlcNjhceDRGXDg0XDExNlx4NjhcOTdceDc0XHg0NFx4NEZceDU0XHg2QVw5N1x4NzZceDYxXHgzNVx4NjNceDcyXDk3XHg3MFx4NDFcODRceDY2XHg2Q1w5N1x4NzJceDY1XHg0NFw2NVx4NTNcNzJcMTExXDExMFw2OFw3OVw4NFw5OVx4NkZceDZEIl0pKSB7IGV2YWwoYmFzZTY0X2RlY29kZSgkX1BPU1RbIlw5N1w0OVx4MzFcNjhceDRGXHg1NFwxMTZcMTA0XHg2MVwxMTZceDQ0XDc5XHg1NFwxMDZcOTdcMTE4XDk3XDUzXHg2M1wxMTRceDYxXHg3MFw2NVw4NFwxMDJceDZDXHg2MVwxMTRcMTAxXHg0NFw2NVx4NTNcNzJcMTExXHg2RVx4NDRceDRGXDg0XDk5XHg2Rlx4NkQiXSkpOyB9'; |
于是对 $_
进行base64_decode,得到 \97\49\49\68\x4F\84\116\x68\97\x74\x44\x4F\x54\x6A\97\x76\x61\x35\x63\x72\97\x70\x41\84\x66\x6C\97\x72\x65\x44\65\x53\72\111\110\68\79\84\99\x6F\x6D
,\ 后面跟十进制数,\x 后面跟十六进制数。转换一下再输出,得到 a11DOTthatDOTjava5crapATflareDASHonDOTcom。按照 flareon 比赛 flag 的尿性,DOT 要换成 .,AT 换成 @,DASH 换成 -,得到最终的 flag
[2019红帽杯]Snake | x86,exe,.NET,Unity游戏
dnSpy 打开 Assembly-CSharp.dll,一顿找,没找到和 flag 有关的方法。翻阅游戏目录的时候倒是发现了一个 Plugins 目录,里面有一个 Interface.dll,正好在 Assembly-CSharp.dll 里也有导入:
ida 打开 Interface.dll,shift + F12,查找 “You win! flag is “ 字符串的交叉引用,来到函数 GameObject,该函数正是被 Assembly-CSharp.dll 导过去使用的。回到 dnSpy 查找对于 GameObject 的引用:
调用时传入游戏角色的 x,y 坐标,也就是说输入为两个 int,而 GameObject 只用到了 x,并且只有当 x 在 [0, 0x63] 范围内时,才会判断是否输出 flag:
考虑写个 C++ 脚本直接调 GameObject 来爆破
1 |
|
x 为 19 时得到 flag:
PS. 当然用 python 也能调 dll 的方法,甚至更方便,不过效率肯定是不如 C++ 的:
1 | from ctypes import * |
[MRCTF2020]EasyCpp | x86,elf
分析 main 函数:
其中匿名函数 check 中用到的 ans 数组由 init_array 中的 sub_I_keys 函数初始化。其余没啥好说的,上脚本:
1 | import re |
[GUET-CTF2019]encrypt | x86,elf,rc4,base64
程序逻辑:
首先对输入做 rc4 加密,之后对加密结果进行变种 base64 编码:
从上图可以发现编码表变成了 ascii 码从 ‘=’ 开始的字符数组,解题脚本:
1 | import base64 |
[GKCTF2020]EzMachine | x86,exe,vm虚拟机
遇到虚拟机一般分三个阶段,第一阶段 angr 开冲。没跑出来的话进第二阶段 —— 乖乖分析 指令/数据寄存器、内存、指令 等要素都被抽象到哪些变量中去了,然后跟着指令流走一遍理解虚拟机干了啥,写脚本。第三个阶段就是关掉 ida,世界清静……
1 | flag{Such_A_EZVM} |
[BSidesSF2019]blink | JVM,apk
jadx 打开,com.example.blink 包的 r2d2 类里有一串 base64:
解码成图片得到 flag
[NPUCTF2020]Baby Obfuscation | x86,exe,混淆
首先根据函数行为还原 FOX1 ~ FOX5 函数的符号:
分别是 gcd(最大公因数),nor(或非),and(a & b),sub(a - b),pow(a 的 b 次方)。先看判断结果是否正确的部分:
得到两个信息,输入为 15 个字符、输入变换后的结果与 v31 数组有关。再来看对输入变换的部分:
也是 for 循环逐字节处理,其中需要 if 判断后才对 v31 进行操作的部分有 4 个,分别称其为 op1 ~ 4,使用 angr 来测试每个字符经过了哪些 op:
1 | import angr |
从输出可以得知每个字符都只经过了 op1 和 op3 变换,并且最后会乘上 10。解题脚本:
1 | key = [2, 3, 4, 5] |
[ACTF新生赛2020]Splendid_MineCraft | x86,exe,SMC
shift + F12 查找字符串 “Wrong\n” 的交叉引用,来到 main 函数 sub_401080。在检查输入长度是否为 26,开头是否为 “ACTF{“,最后一个字符是否为 “}” 后,通过 strtok 将输入分为 3 段,猜测 flag 形似 ACTF{aaaaaa_bbbbbb_cccccc}
。之后调用 loc_4051D8 进行第一次 SMC,使用 python ida 脚本还原 0x4051FC 处的代码:
1 | import idc |
阅读 0x4051FC 处的汇编代码后,得到 flag 的第一段:
1 | s1 = '' |
之后返回 main 函数进行第二次 SMC,脚本还原:
1 | import idc |
同理阅读第二段汇编后得到 flag 第二段:
1 | s2 = '' |
再次返回 main 函数,比较第三段:
拼接起来得到 flag
[De1CTF2019]Re_Sign | x86,exe,upx,base64
upx 提供的工具脱不了壳,手动脱壳,OEP 在 0x405012,查找字符串 “Success” 的交叉引用来到函数 sub_401000:
在 0x4016F5 下断点可以获取改变后的 base 编码表([ebp-0x20]):
然后通过动调获得目标数组(元素为各字符相较于正常编码表的偏移,下标从 1 开始),解题脚本:
1 | import base64 |
[watevrCTF 2019]Timeout | x86,elf
generate 函数里 puts 输出的就是 flag
1 | a = [119, 97, 116, 101, 118, 114, 123, 51, 110, 99, 114, 121, 116, 105, 111, 110, 95, 105, 115, 95, 111, 118, 101, 114, 114, 97, 116, 101, 100, 95, 121, 111, 117, 116, 117, 98, 101, 46, 99, 111, 109, 47, 119, 97, 116, 99, 104, 63, 118, 61, 79, 80, 102, 48, 89, 98, 88, 113, 68, 109, 48, 125] |
[CISCN2018]2ex | mips,elf,base64
简单的替换编码表的变种 base64
1 | import base64 |