系统 : Windows xp
程序 : 2015_nctf_re
程序下载地址 :http://pan.baidu.com/s/1hryN1Wg
要求 : 拆解Nag窗口 & 找出密钥
使用工具 :OD & IDA & PEID & Hex Workshop & CodeBlocks
可在看雪论坛中找到关于此程序的讨论:http://bbs.pediy.com/showthread.php?t=199609&highlight=%E8%8A%B1%E6%8C%87+%E6%8C%87%E4%BB%A4+%E4%BB%A4
花指令是一些专门用来迷惑反汇编软件的代码数据,而对于cpu来说,依旧还是会执行正确的代码。通过插入无效数据,干扰反汇编软件对PE文件的分析,导致其显示出很多无用的信息。
今天,我们逆向一个小程序,来加深对花指令的了解。
打开PEID拖入该程序,发现无壳。
双击运行2015_nctf_re,将会显示一个窗口:
单击关闭,发现又弹出一个相同的对话框,关闭N次无效后,只好打开任务管理器结束该进程。
OD载入该程序,对MessageBoxA下断,F9运行后中断,接着回到程序领空:
00405B59 |. FF75 14 push dword ptr [ebp+14] ; /Style
00405B5C |. FF75 10 push dword ptr [ebp+10] ; |Title
00405B5F |. FF75 0C push dword ptr [ebp+C] ; |Text
00405B62 |. FF75 08 push dword ptr [ebp+8] ; |hOwner
00405B65 |. FF15 34344300 call dword ptr [<&USER32.MessageBoxA>>; MessageBoxA
00405B6B |. 8945 E4 mov dword ptr [ebp-1C], eax
00405B6E |. C745 FC FEFFF>mov dword ptr [ebp-4], -2
00405B75 |. E8 0B000000 call 00405B85
00405B7A |. 8B45 E4 mov eax, dword ptr [ebp-1C]
00405B7D |> E8 5FB40100 call 00420FE1
00405B82 . C3 retn
一直跟踪下去,会发现代码又回到了405B59,据此分析,有一个循环在不停地显示该窗口:
004012FB > BE 00010000 mov esi, 100
00401300 > 6A 00 push 0
00401302 . 6A 00 push 0
00401304 . 68 70894300 push 00438970
00401309 . E8 F84A0000 call 00405E06
0040130E . 83EE 01 sub esi, 1
00401311 .^ 75 ED jnz short 00401300 ; 判断语句
00401313 . 5F pop edi
00401314 . 8D46 01 lea eax, dword ptr [esi+1]
00401317 . 5E pop esi
00401318 . C3 retn
将401311处的跳转指令改为jz并保存。
打开Hex Workshop载入保存之后的程序,搜索ansi string“小兄弟,搞基吗?”,将其替换为“小兄弟,我是傻哔”。
重新打开效果如下:
========================小剧场模式启动=======================》
“喂,110吗?”
“是的,你有什么事?”
“我要举报!有一个傻[哔]出现在我附近!”
“嗯?可傻[哔]不触犯法律啊。”
“对了,你是怎么知道他是傻[哔]的?”
“嗨,也没什么,就是破解人家软件的时候发现的。”
。。。。。。。
“小朋友,下午来局子里喝杯茶吧~”
========================小剧场模式关闭=======================》
用OD载入修改过的程序,对GetDlgItemTextA下断,运行后中断,接着返回程序领空:
0040B45F |. FF75 10 push dword ptr [ebp+10] ; /Count
0040B462 |. FF75 0C push dword ptr [ebp+C] ; |Buffer
0040B465 |. FF75 08 push dword ptr [ebp+8] ; |ControlID
0040B468 |. FF71 20 push dword ptr [ecx+20] ; |hWnd
0040B46B |. FF15 68334300 call dword ptr [<&USER32.GetDlgItemTe>; GetDlgItemTextA
0040B471 |. 5D pop ebp ; 0012F82C
0040B472 |. C2 0C00 retn 0C
继续跟踪:
0040141F . A1 3C524400 mov eax, dword ptr [44523C] ; 取出输入字串
00401424 . 51 push ecx ; USER32.77D321CC
00401425 . 52 push edx
00401426 . 53 push ebx
00401427 . E8 00000000 call 0040142C
0040142C /$ 5A pop edx
0040142D |. 83C2 07 add edx, 7
00401430 |. 52 push edx ; 等价于 jmp edx
00401431 . C3 retn ; 跳入1433,则1432没有被执行
00401432 C4 db C4
00401433 5B db 5B ; CHAR '['
00401434 5A db 5A ; CHAR 'Z'
00401435 59 db 59 ; CHAR 'Y'
00401436 51 db 51 ; CHAR 'Q'
00401437 52 db 52 ; CHAR 'R'
00401438 53 db 53 ; CHAR 'S'
00401439 33 db 33 ;
0040143A DB db DB
0040143B 74 db 74 ;
0040143C 01 db 01
0040143D E8 db E8
0040143E 5B db 5B ; CHAR '['
0040143F 5A db 5A ; CHAR 'Z'
00401440 59 db 59 ; CHAR 'Y'
00401441 80 db 80 ;
00401442 30 db 30 ; CHAR '0'
发现程序后面出现了一大段无用数据,继续运行,发现程序跟入了无用数据中,但寄存器的值却在不停的变换,说明该处指令被混淆过。选择40141F-401431处指令,右键->去除花指令->Obsidium即可去除花指令:
0040141F A1 3C524400 mov eax, dword ptr [44523C] ; 取出输入字串
00401424 51 push ecx ; USER32.77D321CC
00401425 52 push edx
00401426 53 push ebx
00401427 E8 00000000 call 0040142C
0040142C 5A pop edx
0040142D 83C2 07 add edx, 7
00401430 52 push edx ; 等价于 jmp edx
00401431 C3 retn ; 跳入1433,则1432没有被执行
00401432 C45B 5A les ebx, fword ptr [ebx+5A]
00401435 59 pop ecx
00401436 51 push ecx
00401437 52 push edx
00401438 53 push ebx
00401439 33DB xor ebx, ebx ; 清空ebx
0040143B 74 01 je short 0040143E ; 跳入143E,143D没有被执行
0040143D E8 5B5A5980 call 80996E9D
00401442 306E 51 xor byte ptr [esi+51], ch
00401445 52 push edx
00401446 53 push ebx
00401447 33DB xor ebx, ebx ; 清空ebx
00401449 74 01 je short 0040144C ; 跳入144C,144B没有被执行
可以看到接下来的指令依旧有些奇怪,地址143B跳入143E,而该条指令的地址却是143D,有一个字节没有被执行。确定其是垃圾数据,用nop填充之(“剩余的用nop填充”不要打钩):
0040143B /74 01 je short 0040143E ; 跳入143E,143D没有被执行
0040143D |90 nop
0040143E 5B pop ebx
0040143F 5A pop edx
00401440 59 pop ecx
00401441 8030 6E xor byte ptr [eax], 6E ; 第一个字符与6E异或
00401444 51 push ecx
00401445 52 push edx
00401446 53 push ebx
之后的花指令都用此法抹除:
0040141F A1 3C524400 mov eax, dword ptr [44523C] ; 取出输入字串
00401424 51 push ecx
00401425 52 push edx
00401426 53 push ebx
00401427 E8 00000000 call 0040142C
0040142C 5A pop edx
0040142D 83C2 07 add edx, 7
00401430 52 push edx ; 等价于 jmp edx
00401431 C3 retn ; 跳入1433,则1432没有被执行
00401432 90 nop
00401433 5B pop ebx
00401434 5A pop edx
00401435 59 pop ecx
00401436 51 push ecx
00401437 52 push edx
00401438 53 push ebx
00401439 33DB xor ebx, ebx ; 清空ebx
0040143B 74 01 je short 0040143E ; 跳入143E,143D没有被执行
0040143D 90 nop
0040143E 5B pop ebx
0040143F 5A pop edx
00401440 59 pop ecx
00401441 8030 6E xor byte ptr [eax], 6E ; 第一个字符与6E异或
00401444 51 push ecx
00401445 52 push edx
00401446 53 push ebx
00401447 33DB xor ebx, ebx ; 清空ebx
00401449 74 01 je short 0040144C ; 跳入144C,144B没有被执行
0040144B 90 nop
0040144C 5B pop ebx
0040144D 5A pop edx
0040144E 59 pop ecx
0040144F 8070 01 62 xor byte ptr [eax+1], 62 ; 第二个字符与62异或
00401453 51 push ecx
00401454 52 push edx
00401455 53 push ebx
00401456 33DB xor ebx, ebx ; 清空ebx
00401458 74 01 je short 0040145B ; 跳入145B,145A没有被执行
0040145A 90 nop
0040145B 5B pop ebx
0040145C 5A pop edx
0040145D 59 pop ecx
0040145E 8070 02 76 xor byte ptr [eax+2], 76 ; 第三个字符与76异或
00401462 51 push ecx
00401463 52 push edx
00401464 53 push ebx
00401465 33DB xor ebx, ebx ; 清空ebx
00401467 74 01 je short 0040146A ; 跳入146A,1469没有被执行
00401469 90 nop
0040146A 5B pop ebx
0040146B 5A pop edx
0040146C 59 pop ecx
0040146D 8070 03 65 xor byte ptr [eax+3], 65 ; 第四个字符与65异或
00401471 51 push ecx
00401472 52 push edx
00401473 53 push ebx
00401474 33DB xor ebx, ebx ; 清空ebx
00401476 74 01 je short 00401479 ; 跳入1479,1478没有被执行
00401478 90 nop
00401479 5B pop ebx
0040147A 5A pop edx
0040147B 59 pop ecx
0040147C 8070 04 7F xor byte ptr [eax+4], 7F ; 第五个字符与7F异或
00401480 51 push ecx
00401481 52 push edx
00401482 53 push ebx
00401483 33DB xor ebx, ebx ; 清空ebx
00401485 74 01 je short 00401488 ; 跳入1488,1487没有被执行
00401487 90 nop
00401488 5B pop ebx
00401489 5A pop edx
0040148A 59 pop ecx
0040148B 8070 05 49 xor byte ptr [eax+5], 49 ; 第六个字符与49异或
;省略一部分指令
异或操作都完成后,来到判断算法:
0040156C 8B0D 3C524400 mov ecx, dword ptr [44523C] ; 取出异或后的输入字串
00401572 0FB611 movzx edx, byte ptr [ecx] ; 判断首字符
00401575 85D2 test edx, edx ; 非0则跳转出错
00401577 75 4A jnz short 004015C3 ; 则退出
00401579 C645 FF 00 mov byte ptr [ebp-1], 0 ; 一开始计数器置零
0040157D EB 08 jmp short 00401587
0040157F 8A45 FF mov al, byte ptr [ebp-1]
00401582 04 01 add al, 1
00401584 8845 FF mov byte ptr [ebp-1], al
00401587 0FB64D FF movzx ecx, byte ptr [ebp-1]
0040158B 83F9 14 cmp ecx, 14 ; 计数器为0x14?
0040158E 7D 33 jge short 004015C3 ; 是则直接结束
00401590 0FB655 FF movzx edx, byte ptr [ebp-1]
00401594 0FB645 FF movzx eax, byte ptr [ebp-1]
00401598 8B0D 3C524400 mov ecx, dword ptr [44523C] ; 异或后字符地址存入ecx
0040159E 0FB60401 movzx eax, byte ptr [ecx+eax]
004015A2 3BD0 cmp edx, eax ; 迭代字串的第n个字符是否等于n?
004015A4 75 19 jnz short 004015BF ; 否则直接结束比较
004015A6 0FB64D FF movzx ecx, byte ptr [ebp-1]
004015AA 83F9 13 cmp ecx, 13 ; 计数器为0x13?
004015AD 75 0E jnz short 004015BD ; 否则跳转 继续循环
004015AF 6A 00 push 0
004015B1 6A 00 push 0
004015B3 68 84894300 push 00438984
004015B8 E8 49480000 call 00405E06
004015BD 90 nop
004015BE 90 nop
004015BF 90 nop
004015C0 90 nop
004015C1 ^ EB BC jmp short 0040157F
004015C3 5B pop ebx
004015C4 8BE5 mov esp, ebp
004015C6 5D pop ebp
004015C7 C3 retn
该处将我们输入的字串和一串固定的密钥进行异或,结果字串值应为0,1,2,3,4。。。。
写一段C代码实现逆算法:
#include <iostream>
using namespace std;
int main()
{
char ch = 0x7F;
string str = "nbve";
str += ch;
str +="I6KWyoex>QDy #n"; //粘合用来异或的字串
string::iterator iter = str.begin();
for ( int i = 0 ; iter != str.end() ; iter++,i++ ){
*iter ^= i;
}
cout << str << endl;
return 0;
}
结果为:
复制黏贴至程序: