学破解第103天,《攻防世界reverse练习区Windows_Reverse1》分析
前言:
一直对黑客充满了好奇,觉得黑客神秘,强大,无所不能,来论坛两年多了,天天看各位大佬发帖,自己只能做一个伸手党。也看了官方的入门视频教程,奈何自己基础太差,看不懂。自我反思之下,决定从今天(2019年6月17日)开始定下心来,从简单的基础教程开始学习,希望能从照抄照搬,到能独立分析,能独立破解。
不知不觉学习了好几个月,发现自己离了教程什么都不会,不懂算法,不懂编程。随着破解学习的深入,楼主这个半吊子迷失了自我,日渐沉迷水贴装X,不能自拔。
==========申明:从第71天楼主开始水贴装X,帖子不再具有连续性,仅供参考,后续帖子为楼主YY专用贴!!!==========
立帖为证!--------记录学习的点点滴滴
0x1查壳
1.程序下载后,查壳UPX 0.89 - 3.xx,ESP定律脱壳。
2.运行程序,提示:please input code:要我们输入正确的字符串。
0x2动态调试分析(OD)
1.搜索字符串,定位到关键跳。
004010FB /75 27 jnz short dumped_.00401124
004010FD |8D4C24 04 lea ecx,dword ptr ss:[esp+0x4]
00401101 |51 push ecx
00401102 |68 20214000 push dumped_.00402120 ; You've got it!!%s\n
00401107 |FFD6 call esi
00401109 |83C4 08 add esp,0x8
0040110C |33C0 xor eax,eax
0040110E |5E pop esi ; kernel32.7C817077
0040110F |8B8C24 00080000 mov ecx,dword ptr ss:[esp+0x800]
00401116 |33CC xor ecx,esp
00401118 |E8 29000000 call dumped_.00401146
0040111D |81C4 04080000 add esp,0x804
00401123 |C3 retn
00401124 \68 34214000 push dumped_.00402134 ; Try again later.\n
2.当然了,咋们的目的是追码,爆破这里毫无意义。关键跳上面必有关键call,找到它:
0040109D 68 08214000 push dumped_.00402108 ; please input code:
004010A2 FFD6 call esi
004010A4 8D9424 20040000 lea edx,dword ptr ss:[esp+0x420]
004010AB 52 push edx ; ntdll.KiFastSystemCallRet
004010AC 68 1C214000 push dumped_.0040211C ; %s
004010B1 FF15 A4204000 call dword ptr ds:[<&msvcr90.#1343>] ; msvcr90.scanf
004010B7 8D8424 28040000 lea eax,dword ptr ss:[esp+0x428]
004010BE 50 push eax
004010BF 8D4C24 2C lea ecx,dword ptr ss:[esp+0x2C]
004010C3 E8 38FFFFFF call dumped_.00401000
3.我F4到004010C3这里,程序运行起来,随便输入一个字符串123456,可以清晰的看到,此时eax就是我输入的123456,作为参数传递给call dumped_.00401000。
4.接下来就F7进去,开始F8单步向下走,给它都分析出来
00401000 51 push ecx
00401001 55 push ebp
00401002 8B6C24 0C mov ebp,dword ptr ss:[esp+0xC] ; 123456赋值给ebp
00401006 56 push esi ; msvcr90.printf
00401007 8BC5 mov eax,ebp ; ebp(123456)赋值给eax
00401009 57 push edi ; dumped_.004033D4
0040100A 33FF xor edi,edi ; dumped_.004033D4
0040100C 8D70 01 lea esi,dword ptr ds:[eax+0x1] ; 将eax+1后指向的地址给esi
0040100F 90 nop
00401010 8A10 mov dl,byte ptr ds:[eax] ; 从eax中低位取一个字节,也就是1
00401012 40 inc eax ; eax+1,指针右移一位
00401013 84D2 test dl,dl ; 判断dl是否为NULL
00401015 ^ 75 F9 jnz short dumped_.00401010 ; 循环判断
00401017 2BC6 sub eax,esi ; eax-esi,计算123456的长度,保存在eax中
00401019 74 26 je short dumped_.00401041 ; 相等就跳,也就是字符串长度为0
0040101B 53 push ebx
0040101C 8BDD mov ebx,ebp ; ebp(123456)赋值给ebx
0040101E 2BD9 sub ebx,ecx ; ECX的值哪来的?相减后ebx为400
00401020 0FBE040B movsx eax,byte ptr ds:[ebx+ecx] ; ebx+ecx那不就是还原ebp(123456),从中取一个字节(1)给eax
00401024 8A90 F82F4000 mov dl,byte ptr ds:[eax+0x402FF8] ; 从常量值中取出m赋值为edx的低八位给dl
0040102A 8BC5 mov eax,ebp ; ebp(123456)赋值给eax
0040102C 8811 mov byte ptr ds:[ecx],dl ; 将dl(m)存入ecx指向的内存中
0040102E 47 inc edi ; edi+1=1
0040102F 41 inc ecx ; ecx内存地址+1
00401030 8D70 01 lea esi,dword ptr ds:[eax+0x1] ; 将eax+1后指向的地址给esi
00401033 8A10 mov dl,byte ptr ds:[eax] ; 从eax中低位取一个字节,也就是1
00401035 40 inc eax ; eax+1,指针右移一位
00401036 84D2 test dl,dl ; 判断dl是否为NULL
00401038 ^ 75 F9 jnz short dumped_.00401033 ; 循环判断
0040103A 2BC6 sub eax,esi ; eax-esi,计算123456的长度,保存在eax中
0040103C 3BF8 cmp edi,eax ; edi=1,eax=6
0040103E ^ 72 E0 jb short dumped_.00401020 ; edi < eax则跳转
00401040 5B pop ebx ; dumped_.004010C8
00401041 5F pop edi ; dumped_.004010C8
00401042 5E pop esi ; dumped_.004010C8
00401043 5D pop ebp ; dumped_.004010C8
00401044 59 pop ecx ; 此时ecx值为mlkjih,0040102C 这一行循环写入
00401045 C3 retn
5.从这个call出来后,继续往下跟,详细分析如下:
004010C3 E8 38FFFFFF call dumped_.00401000
004010C8 83C4 28 add esp,0x28
004010CB B9 F4204000 mov ecx,dumped_.004020F4 ; DDCTF{reverseME}字符串赋值给eax
004010D0 8D4424 04 lea eax,dword ptr ss:[esp+0x4] ; esp+4就是ecx(mlkjih)
004010D4 8A10 mov dl,byte ptr ds:[eax] ; 从eax中取出第一个字节m存放在dl
004010D6 3A11 cmp dl,byte ptr ds:[ecx] ; 判断m == D
004010D8 75 1A jnz short dumped_.004010F4
004010DA 84D2 test dl,dl
004010DC 74 12 je short dumped_.004010F0
004010DE 8A50 01 mov dl,byte ptr ds:[eax+0x1]
004010E1 3A51 01 cmp dl,byte ptr ds:[ecx+0x1] ; 比较第二个字符
004010E4 75 0E jnz short dumped_.004010F4
004010E6 83C0 02 add eax,0x2
004010E9 83C1 02 add ecx,0x2
004010EC 84D2 test dl,dl
004010EE ^ 75 E4 jnz short dumped_.004010D4
004010F0 33C0 xor eax,eax ; 清空eax
004010F2 EB 05 jmp short dumped_.004010F9
004010F4 1BC0 sbb eax,eax
004010F6 83D8 FF sbb eax,-0x1
004010F9 85C0 test eax,eax ; 判断eax是否为NULL
004010FB 75 27 jnz short dumped_.00401124
004010FD 8D4C24 04 lea ecx,dword ptr ss:[esp+0x4]
00401101 51 push ecx ; msvcr90.7855215C
00401102 68 20214000 push dumped_.00402120 ; You've got it!!%s\n
00401107 FFD6 call esi ; msvcr90.printf
显然必须要让ecx等于DDCTF{reverseME}它,才能成功,所以要从关键call中ecx的赋值寻找突破口。
6.然而我没学过汇编,依靠百度而来的意思,并不能让我知道ecx的每一次取值之间的关系,那么就运用数学知识,多测试几组数据,观察变量之间的关系。
输入:123456
输出:mlkjih
输入:654321
输出:hijklm
输入:abcdef
输出:=<;:98
输入:fedbca
输出:89:;<=
观察上面,我可以猜测字符串输入的顺序与输出的字符串没有关系,每一个输入的字符有唯一对应的输出。
7.接下来显然就是要找到密码表和key,找出对应关系,重新跑一边程序,梳理前面的整理出来的注释,发现决定ecx值的为这一行:00401024 8A90 F82F4000 mov dl,byte ptr ds:[eax+0x402FF8]
密码表显然就是0x402FF8处的字符串了。
输入:123456
循环取每一个字符
第一次取1的的ASCII 0x31 + 0x402FF8处字符就是m,如图所示!
8.现在知道key了,将“DDCTF{reverseME}”字符串一个个还原也能得出密码了吧!
D = 0x403052, C = 0x403053, T = 0x403042, F = 0x403050
{ = 0x40301B, r = 0x403024, e = 0x403031, v = 0x403020
s = 0x403023, M = 0x403049, E = 0x403051, } = 0x403019
然后将每一个地址与0x402FF8相减即可得到对应的ASCII:ZZ[JX#,9(9,+9QY!,成功得到flag。
0x3静态调试分析(IDA)
1.尝试完累死人的OD追码,现在试试IDA放松一下吧。
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@2
char v4; // [sp+4h] [bp-804h]@1
char v5; // [sp+5h] [bp-803h]@1
char v6; // [sp+404h] [bp-404h]@1
char Dst; // [sp+405h] [bp-403h]@1
v6 = 0;
memset(&Dst, 0, 0x3FFu);
v4 = 0;
memset(&v5, 0, 0x3FFu);
printf("please input code:");
scanf("%s", &v6);
sub_401000(&v6);
if ( !strcmp(&v4, "DDCTF{reverseME}") )
{
printf("You've got it!!%s\n", &v4);
result = 0;
}
else
{
printf("Try again later.\n");
result = 0;
}
return result;
}
很清晰的看到,v6是我们输入的值,调用00401000函数,接着比较v4和DDCTF{reverseME}是否相等。
2.那么我就进去看一下00401000函数内部啥样子。
unsigned int __cdecl sub_401000(const char *a1)//
{
_BYTE *v1; // ecx@0
unsigned int v2; // edi@1
unsigned int result; // eax@1
int v4; // ebx@2
v2 = 0;
result = strlen(a1);//计算输入字符串的长度
if ( result )
{
v4 = a1 - v1;
do
{
*v1 = byte_402FF8[v1[v4]];
++v2;
++v1;
result = strlen(a1);
}
while ( v2 < result );
}
return result;//返回字符串长度
}
然后有点懵,外面v4没有传进来,里面怎么改变它的值???
3.不过我可是已经OD把它掀了个底朝天,重点看循环部分。
do
{
*v1 = byte_402FF8[v1[v4]];这个v1其实就是V4了,看注释是ecx,OD调试时我就知道了ecx保存计算后的字符串
++v2;//+1控制循环
++v1;//+1赋值
result = strlen(a1);//a1也就是输入字符串的长度
}
while ( v2 < result );//循环控制条件
byte402FF8是个常量字符串:前面部分不知道是啥,从00403018开始为:
~}|{zyxwvutsrqponmlkjihgfedcba`^][ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!
然后出去这里面的v1(外面的&v4)与DDCTF{reverseME}比较就完事了。
0x3逆算法
1.因为字符数组有部分显示不过来,我就不用上面那个字符串了,直接二进制复制。
00 00 00 00 00 00 00 00 64 CC 3D CC 9B 33 C2 33 FF FF FF FF FF FF FF FF FE FF FF FF 01 00 00 00
7E 7D 7C 7B 7A 79 78 77 76 75 74 73 72 71 70 6F 6E 6D 6C 6B 6A 69 68 67 66 65 64 63 62 61 60 5F
5E 5D 5C 5B 5A 59 58 57 56 55 54 53 52 51 50 4F 4E 4D 4C 4B 4A 49 48 47 46 45 44 43 42 41 40 3F
3E 3D 3C 3B 3A 39 38 37 36 35 34 33 32 31 30 2F 2E 2D 2C 2B 2A 29 28 27 26 25 24 23 22 21
2.整理一下,逆算法如下:
int main()
{
//密码表
int arr[126] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0xCC, 0x3D, 0xCC, 0x9B, 0x33,
0xC2, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0x01, 0x00,
0x00, 0x00, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71,
0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61,
0x60, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51,
0x50, 0x4F, 0x4E, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41,
0x40, 0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31,
0x30, 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21 };
char ch[20] = "DDCTF{reverseME}";//密码
for (int i = 0; ch[i] != 0;i++)//DDCTF{reverseME},逐个比较
{
for (int j = 0; j < 126; j++)//查找下标
{
if (arr[j] == ch[i])//找到下标,替换成ASCII字符
{
ch[i] = j;
break;//开始下一次循环
}
}
}
printf("%s",ch);//输出解密后的字符串
system("pause");
return 0;
}
成功解密字符串:ZZ[JX#,9(9,+9QY!
0x4总结
1.我个人不太会用IDA,因为一开始我是从论坛零基础学脱壳教程入门,所以习惯先OD。
2.因为看不懂汇编,很多时候只能靠百度,解密的时候习惯通过输入和输出寻找变量之间的关系,建立数学模型,也就是靠猜了。
3.建议坛友们也可以练练手,这个程序就一个函数,OD反汇编出来也就那么几行代码,一句一句的对照百度解释的指令含义,既能学习汇编知识,又能锻炼调试能力。
4.虽然花了大半天很累,但是很值得,最终提交的flag:flag{ZZ[JX#,9(9,+9QY!}