小记一下第一次成功的逆向,eshard培训作业ARE-4中获取key程序的序列号。
练习目标是通过使用panda-re工具(QEMU的套件),通过查找QEMU中寄存器状态,获得输入字符匹配时,PC指针的值,顺着找到代码中的程序行,接着找到程序验证序列号的代码,逆向后破解。
具体过程:
0x00 下载编译panda-re
根据官方repo可以在Ubuntu 16.04上轻松的编译成功。不过Ubuntu 18.04会因为GCC版本过高等问题出现兼容性问题(2018年8月)。编译需要大约10分钟,编的时候可以出去遛一遛。
0x01 获取QEMU运行快照
先玩一下,看看什么效果。规则是输入4段每段16字符,中间短杠作为分隔。
(panda_env) enigma001@ubuntu:~/eshard-re/4-ARE/workspace/key-analysis$ ./key 20E806AF-05376811-38D34527-638DD79A
serial bad
(panda_env) enigma001@ubuntu:~/eshard-re/4-ARE/workspace/key-analysis$ ./key 20E806AF-05376811-38D34527-638DD79B
serial bad
(panda_env) enigma001@ubuntu:~/eshard-re/4-ARE/workspace/key-analysis$ ./key 00000000-00000000-00000000-00000000
serial bad
返回的值都是serial bad,看起来应该就是在程序中校验了一下值,比对错误就返回错误信号。
玩完了开始用QEMU跑快照,序列号是瞎给的。
1 #!/bin/bash
2
3 ~/eshard-re/4-ARE/panda/panda/scripts/run_debian.py key 20E806AF-05376811-38D34527-638DD79A
方法是在 https://github.com/eshard/panda/blob/learn/panda/scripts/run_guest.py 的create_recording中添加:
qemu.run_console("echo 0 > /proc/sys/kernel/randomize_va_space")
- 必要文件: initrd.img-3.2.0-4-versatile, vmlinuz-3.2.0-4-versatile
- 结构相关文件: wheezy_panda2.qcow2, wheezy_x64.qcow2(需要在录制时加--arch x86_64
)
跑完之后在当前路径下出现replays
文件夹
(panda_env) enigma001@ubuntu:~/eshard-re/4-ARE/workspace/key-analysis$ tree replays
replays
└── key
├── cdrom
│ └── key
├── cdrom.iso
├── key-rr-nondet.log
└── key-rr-snp
接着就可以replay(使用panda-re版本r-18-06)
1 #!/bin/bash
2
3 ~/eshard-re/4-ARE/panda/build/i386-softmmu/qemu-system-i386 -replay replays/key/key -panda osi -os linux-32-. -panda osi_linux:kconf_group=debian-3.2.81-686-pae:32 -panda asidstory
4
5 mv asidstory asidstory_key
于是可以获得这样的结果
1 Count Pid Name Asid First Last
2 388 743 udevd c76e8000 260129 -> 67698264
3 97 2487 blkid c5182000 5947088 -> 13122162
4 66 2433 bash2 c7ae4000 248 -> 12824086
5 41 2488 key c62fa000 13363004 -> 19256272
6 40 123 kworker/0:2 0 145380 -> 15194483
7 35 2487 udevd2 c5bab000 294682 -> 4687204
8 13 2487 udevd3 c5182000 4687550 -> 5858025
9 12 337 flush-8:0 0 284803 -> 3062779
10 9 2487 blkid2 0 13204146 -> 13459162
11 6 2488 bash3 c5202000 12914158 -> 13055670
12 6 291 udevd4 c53bb000 19261480 -> 19334033
13 3 2488 bash4 c62fa000 13242438 -> 13362904
14
15 bash2 : [## ############ ]
16 kworker/0:2 : [# ## #### ]
17 udevd : [## #############################################################]
18 flush-8:0 : [## # ]
19 udevd2 : [## ### ]
20 udevd3 : [ ## ]
21 blkid : [ ######### ]
22 bash3 : [ # ]
23 blkid2 : [ # ]
24 bash4 : [ # ]
25 key : [ ### ## # ]
26 udevd4 : [ # ]
为了提高之后的字符串搜索效率,把key程序的运行时段截取出来,起始点在13363004,再跑一次replay
1 #!/bin/bash
2
3
4 ~/eshard-re/4-ARE/panda/build/i386-softmmu/qemu-system-i386 -replay replays/key/key -panda 'scissors:name=key-cut,start=13363000'
5
6
7 ~/eshard-re/4-ARE/panda/build/i386-softmmu/qemu-system-i386 -replay key-cut -panda osi -os linux-32-. -panda osi_linux:kconf_group=debian-3.2.81-686-pae:32 -panda asidstory
8
9 mv asidstory asidstory_key-cut2
获得新的运行结果
1 Count Pid Name Asid First Last
2 465 743 udevd c76e8000 96261 -> 54335263
3 43 2488 key c62fa000 3 -> 5893271
4 17 123 kworker/0:2 0 245453 -> 1831482
5 6 291 udevd2 c53bb000 5898479 -> 5971032
6 3 2487 blkid 0 78967 -> 96161
7
8 key : [### # # # ]
9 blkid : [# ]
10 udevd : [###########################################################################]
11 kworker/0:2 : [### ]
12 udevd2 : [ # ]
这下key程序的起始位置在3,基本就是开头位置了。其实也可以切后面,把start=1336300
写成start=1336300,end=xxxxx
就行,记得前后时间稍留一些空间比较好。
0x02 在运行快照里查找输入字符串
这一步感觉就像是用修改器改李逍遥的经验以至于在客栈带着残废赵灵儿打苗族黑脸就可以用天剑随便穿穿穿……
1 #!/bin/bash
2
3 ~/eshard-re/4-ARE/panda/build/i386-softmmu/qemu-system-i386 -replay key-cut -panda callstack_instr -panda stringsearch:name=key,callers=4
其中,-replay后面接之前record的快照名,stringsearch:name接程序名,callers的值代表需要输出的最多栈调用层次。搜索需要设定一个文本文件,名字以<程序名>_search_string.txt命名,里面的内容以双引号括起需要搜索的内容
1 "20E806AF-05376811-38D34527-638DD79A"
运行脚本后,会输出一个<程序名>_string_matches.txt的文件,如下
1 c10282ed 80012031 b7e70e46 8001267a 80012261 062fa000 1
2 c10282ed 80012031 b7e70e46 8001267a 80012264 062fa000 1
3 c10282ed c10fd883 c1165b8d 00000000 1
其中最后一列是搜索字符串击中的次数,倒数第二列是地址空间(不明含义),倒数第三列为需要找到的PC指针的值,再向前的列是栈调用使用的地址。
所以可以看到之前输入的字符串,出现在了PC指针为0x80012261和0x80012264的位置。默认基址0x80000000,因此现在就应该去二进制文件中找到0x12261和0x12264两行指令对应的内容。
0x03 二进制逆向
用IDA Pro打开二进制文件,相应地址附近的内容如下
.text:00012236 _sn_cpy proc near ; CODE XREF: sub_12275+117p
.text:00012236
.text:00012236 var_4 = dword ptr -4
.text:00012236 arg_0 = dword ptr 8
.text:00012236 arg_4 = dword ptr 0Ch
.text:00012236 arg_8 = dword ptr 10h
.text:00012236
.text:00012236 push ebp
.text:00012237 mov ebp, esp
.text:00012239 sub esp, 10h
.text:0001223C call sub_123D5
.text:00012241 add eax, 1CDDBFh
.text:00012246 mov [ebp+var_4], 0
.text:0001224D jmp short loc_1226A
.text:0001224F ; ---------------------------------------------------------------------------
.text:0001224F
.text:0001224F loc_1224F: ; CODE XREF: _sn_cpy+3Aj
.text:0001224F mov edx, [ebp+arg_4]
.text:00012252 lea eax, [edx+1]
.text:00012255 mov [ebp+arg_4], eax
.text:00012258 mov eax, [ebp+arg_0]
.text:0001225B lea ecx, (dword_1E0000+1 - 1E0000h)[eax]
.text:0001225E mov [ebp+arg_0], ecx
.text:00012261 movzx edx, byte ptr [edx]
.text:00012264 mov byte ptr ds:(dword_1E0000 - 1E0000h)[eax], dl
.text:00012266 add [ebp+var_4], 1
.text:0001226A
.text:0001226A loc_1226A: ; CODE XREF: _sn_cpy+17j
.text:0001226A mov eax, [ebp+var_4]
.text:0001226D cmp eax, [ebp+arg_8]
.text:00012270 jl short loc_1224F
.text:00012272 nop
.text:00012273 leave
.text:00012274 retn
.text:00012274 _sn_cpy endp
F5党当然还是看C代码舒服
int __cdecl sn_cpy(int a1, int a2, int a3)
{
int v3; // edx@2
int v4; // eax@2
int result; // eax@3
int i; // [sp+Ch] [bp-4h]@1
sub_123D5();
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
v3 = a2++;
v4 = a1;
a1 += (int)((char *)&dword_1E0000 + 0xFFE20001);
*(_BYTE *)v4 = *(_BYTE *)v3;
}
return result;
}
可以看到这个函数主要就是做了一个从a2到a1的复制,长度a3。按x
查找交叉调用,看到
BOOL __cdecl sub_12275(signed int a1, int a2)
{
int v2; // ST00_4@14
int v3; // eax@14
BOOL result; // eax@15
unsigned int i; // [sp+4h] [bp-14h]@1
unsigned int j; // [sp+8h] [bp-10h]@7
int v7; // [sp+Ch] [bp-Ch]@13
for ( i = 0; i <= 8; ++i )
{
if ( aVhuldoRn[i] - 3 > 96 && aVhuldoRn[i] - 3 <= 122 )
aVhuldoRn[i] -= 3;
}
for ( j = 0; j <= 9; ++j )
{
if ( aVhuldoEdg[j] - 3 > 96 && aVhuldoEdg[j] - 3 <= 122 )
aVhuldoEdg[j] -= 3;
}
v7 = 0;
if ( a1 > 1 )
{
v2 = *(_DWORD *)(a2 + 4);
v3 = local_strlen_(0x1E0000);
sn_cpy((int)&sn_cpy_dst, *(_DWORD *)(a2 + 4), v3);
v7 = cmp_sn_with_deadbeef_xor((int)&sn_cpy_dst);
}
puts(off_1E0060[v7 != 0]);
*(_DWORD *)&result = v7 == 0;
return result;
}
函数cmp_sn_with_deadbeef_xor
就是核心函数了。
int __cdecl cmp_sn_with_deadbeef_xor(int *a1)
{
int result; // eax@2
unsigned int v2; // ST38_4@6
signed int i; // [sp+1Ch] [bp-2Ch]@3
int v4[3]; // [sp+2Ch] [bp-1Ch]@4
int v5; // [sp+38h] [bp-10h]@6
int v6; // [sp+3Ch] [bp-Ch]@1
v6 = *MK_FP(__GS__, 20);
if ( local_strlen_(0x1E0000) == 35 ) //strlen of input is 35
{
for ( i = 0; i <= 3; ++i )
v4[i] = strtoll((const char *)a1 + 9 * i, 0, 16);// hex convert. a1=sn_cpy_dst
// convert 4 blocks of sn strings into hex value
v2 = sub_1213D(v4); // 0xdeadbeef xor
result = v2 == v5; //compare if 0 then win
}
else
{
result = 0;
}
if ( *MK_FP(__GS__, 20) != v6 )
sub_136430();
return result;
}
用sp寄存器寻址的话是
'[sp+18h]'
用bp寄存器寻址的话是'[bp-158h]'
C编译器有时候会编译成'[esp+ *** ]'
的访问局部变量代码。
@1表示是第一个使用这个地址的变量
由result = v2 == v5;
可知,当sub_1213D的结果为0时,校验通过。
unsigned int __cdecl sub_1213D(int *a1)
{
unsigned int v2; // [sp+8h] [bp-8h]@1
signed int i; // [sp+Ch] [bp-4h]@1
sub_123D5();
v2 = 0xDEADBEEF;
for ( i = 0; i <= 2; ++i )
v2 ^= a1[i];
return v2;
}
而sub_1213D为异或函数,由此反推只要每段序列号异或后为0xDEADBEEF即可。
测试证明
(panda_env) enigma001@ubuntu:~/eshard-re/4-ARE/workspace/key-analysis$ ./key deadbeef-00000000-00000001-00000001
serial ok
0x04 小trick
一般偷懒静态分析"比较-验证"模式的程序,最快的方法是找输出的信息字符串,比如此例中的"serial bad"或"serial ok"。用Linux中的strings
命令或IDA Pro中的Strings View可以进行此类检索。然而在本例的key程序中,结果信息字符串被进行了混淆,通过正常查找"serial bad"完全没有任何信息。
事后发现字符串做了一个简单的凯撒密码的替换,替换实现在上面的sub_12275函数,替换规则为3个字母差。原始字符存储如下:
.data:001E0048 aVhuldoRn db 'vhuldo rn',0 ; DATA XREF: sub_12275:loc_12290o
.data:001E0048 ; sub_12275+34o ...
.data:001E0052 align 4
.data:001E0054 aVhuldoEdg db 'vhuldo edg',0 ; DATA XREF: sub_12275:loc_122F7o
.data:001E0054 ; sub_12275+9Bo ...
.data:001E005F align 10h
有意思,真有意思。