-
-
[原创]看雪CTF2017 第七题 CrakeMe逆向分析
-
2017-6-14 22:54 3075
-
1.用OD加载程序,直接崩溃
看到开头是这样,明显不正常,经过检查,发现TLS中有代码:
char __stdcall TlsCallback_0(int a1, int a2, int a3) { int v3; // et1@2 int v4; // eax@2 _BYTE *v5; // ST2C_4@2 char result; // al@2 unsigned int v7; // [sp+10h] [bp-Ch]@2 __int16 v8; // [sp+14h] [bp-8h]@2 if ( a2 == 1 ) { v3 = *MK_FP(__FS__, 24); v4 = *(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 8); v5 = (_BYTE *)(v4 + *(_DWORD *)(v4 + *(_DWORD *)(v4 + 60) + 40)); v7 = 0xCC5874EB; v8 = 0x75E8; //v5 =0x414429 result = decode_410DD6(v5, 200, (int)&v7, 6); } return result; }
TLS中通过PEB结构拿到当前程序的入口点0x414429,然后执行异或解密程序,用OD加载的可以勾选strongOD中下图所示的选项:
重启就会停在TLS入口点了,同时要删掉OD自动在入口添加的断点:
Breakpoints, 条目 3 地址=00414422 <crackme.start> 模块=crackme 激活=仅一次 反汇编=ADD ESP, DWORD PTR DS:[EDX+0x5D]
不然解密之后就是图1的结果。
输入注册码断在这:
00410285 > CC INT3 00410286 6A 01 PUSH 0x1 00410288 FF15 343E4500 CALL NEAR DWORD PTR DS:[<PostQuitMess>; user32.PostQuitMessage 0041028E 33C0 XOR EAX, EAX 00410290 C2 1000 RETN 0x10
这明显是程序内设置的断点,查找异常处理:
.text:0040F2CA push offset TopLevelExceptionFilter ; lpTopLevelExceptionFilter .text:0040F2CF call ds:SetUnhandledExceptionFilter
可以找到上边这个全局异常处理,同时可以看到异常处理函数:
signed int __stdcall TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo) { signed int result; // eax@2 if ( ExceptionInfo->ExceptionRecord->ExceptionCode == 0x80000003 ) { PostMessageW(0, 0x464u, 0, 0); ExceptionInfo->ContextRecord->Eip += 9; result = -1; } else { result = 0; } return result; }
结合前面的断点就很清楚了,产生Int3 断点之后,程序自己处理,把eip+9,程序跳到:
0041028E 33C0 XOR EAX, EAX
继续处理,同时会发送一个自定义消息,消息处理函数:
sub_40E37A,该函数重要内容如下:
1.限定编辑框最长长度为20
signed int __thiscall onInitDialog_4102F7(int this, int a2, int a3, int a4, int a5) { .... // 0x3e8 edit // 0x3e9 button v13_edit = GetDlgItem(*v6, 0x3E8); SendMessageW(v13_edit, EM_SETLIMITTEXT, 0x14u, 0); ......... }
2.验证函数:
int __userpurge yanzheng_4104CD@<eax>(int a1@<ebp>, int a2, int a3, int a4, int a5) { int v5; // ecx@1 int v6; // esi@1 int v7; // ecx@1 int v9; // [sp-1Ch] [bp-1Ch]@1 int v10; // [sp-18h] [bp-18h]@1 int v11; // [sp-14h] [bp-14h]@1 signed int v12; // [sp-10h] [bp-10h]@1 int v13; // [sp-Ch] [bp-Ch]@1 signed int v14; // [sp-8h] [bp-8h]@1 signed int v15; // [sp-4h] [bp-4h]@1 v15 = 144; sub_413FD7(); v6 = v5; sub_4156A0((_BYTE *)(a1 - 128), 0, 101); // 获取用户输入的字符串 GetDlgItemTextA(*(HWND *)(v6 + 4), 1000, (LPSTR)(a1 - 128), 100); string_string_40FD2D(a1 - 152, a1 - 128); *(_DWORD *)(a1 - 4) = 0; v14 = 5; v13 = a1 - 24; *(_DWORD *)(a1 - 24) = 0x49444550; v12 = 0x1AA; *(_BYTE *)(a1 - 20) = 89; // 解密代码 decode_410DD6(sub_411B30, v12, v13, v14); v10 = v7; v9 = v7; *(_DWORD *)(a1 - 156) = &v9; string_string_1_40FD07((int)&v9, a1 - 152); *(_BYTE *)(a1 - 4) = 1; *(_BYTE *)(a1 - 4) = 0; // 用输入字符串计算 sub_411B30(a1, v9, v10, v11, v12, v13, v14); decode_410DD6(sub_411B30, 0x1AA, a1 - 24, 5); // 验证结果,解密shellcode执行 check_411825(a1); *(_DWORD *)(a1 - 4) = -1; sub_410AE3(a1 - 152, 1, 0); return sub_413F81(v14, v15); }
3.第一步验证:
sub_411B30
主要分3步,
第一步把输入的字符串每一位异或0xcc
第二步,查表计算,这个不怎么好描述,用python描述大致如下:
data=[0x89, 0xbc, 0x95, 0xfc, 0xfb, 0xba, 0xed, 0x9a, 0xbb, 0xae, 0xfe, 0x99, 0xa2, 0x98, 0xb9, 0xf9, 0x9f, 0x84, 0x9c, 0xfd, 0x83, 0xad, 0xb6, 0xa9, 0xa5, 0xf5, 0x8c, 0xa7, 0x9e, 0x96, 0x8a, 0xf4, 0x85, 0xbe, 0xa8, 0x8f, 0x86, 0xaf, 0x88, 0x9d, 0x87, 0xbf, 0xff, 0xa1, 0x8b, 0x81, 0xa0, 0xab, 0x8e, 0xbd, 0xb5, 0xaa, 0x82, 0x94, 0xa4, 0x8d, 0xa3, 0xf8, 0xb4, 0xfa, 0x9b, 0xa6, 0xb8, 0x80] strinput="BwnsAtPediy2017KX9Ok" strList=[] #第一步 for x in strinput: strList.append(ord(x)^0xcc) #print hex(strList[0]) print "\nfinal calc" #第二步 nDataLen=len(data) calcList=[] for i,x in enumerate(strList): index=data.index(x) print hex(index), y=i+1 while True: nCount=index/5+5 nNum=0 while True: index+=1 if index==64: index=0 nNum+=1 if nCount==nNum: break print hex(index), y-=1 if y<=0: break index=index%nDataLen calcList.append(data[index])
第三部,处理第二步的结果:
char __cdecl sub_410F75(_BYTE *a1, unsigned int a2) { _BYTE *v2; // edx@1 char result; // al@1 unsigned int v4; // esi@1 int v5; // edi@1 v2 = a1; result = 0; v4 = a2 - (_DWORD)a1; v5 = 0; if ( (unsigned int)a1 > a2 ) v4 = 0; if ( v4 ) { do { result = 8 * (*v2 ^ 0xCC) | ((char)(*v2 ^ 0xCC) >> 5); *v2++ = result; ++v5; } while ( v5 != v4 ); } return result; }
验证函数:
sub__411825
主要函数:
sub_411975
先把之前处理的结果由16进制转成字符串--
00411987 E8 93FFFFFF CALL <crackme.hex2string_41191F>
假设字符串为:
A30B1B828ABB4A9BA9BB93AAA36B82AA024AB243
然后求得每一个字符在下面数组中的下标
004119F4 E8 53F5FFFF CALL <crackme.strstr_410F4C>
0012FB34 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 0123456789ABCDEF
比如A的下标为index=A
然后把low=index/4 ,hgt=index%4的结果保存起来,
最后与一个保存好的数组比较。
00411A55 50 PUSH EAX 00411A56 FF75 B8 PUSH DWORD PTR SS:[EBP-0x48] 00411A59 FF76 18 PUSH DWORD PTR DS:[ESI+0x18] 00411A5C E8 C9460000 CALL <crackme.cmp_is_equ_41612A> 数组: MyArray=[0x02, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x03, 0x02, 0x03, 0x01, 0x00, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x03, 0x02, 0x01, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x02, 0x02, 0x03, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03]
如果相等返回1,不相等返回0
相等则进行下边的shellcode解码:
shellCode=[0x17, 0xfc, 0x82, 0x19, 0xbe, 0x1c, 0xb8, 0x60, 0x21, 0x69, 0x1d, 0x93, 0x30, 0x31, 0x37, 0x4b,0x08, 0xba, 0xa3, 0x4f, 0xe3, 0xf3, 0x6a, 0x33, 0x41, 0x47, 0x95, 0xec, 0x21, 0x99, 0x29, 0xbf, 0x75, 0xc5, 0x53, 0xe8, 0x58, 0x39, 0x4f, 0x6b, 0xc1, 0x12, 0xb6, 0x73, 0xcc, 0x31, 0x88, 0x35, 0xe9, 0x24, 0xa5, 0xf5, 0x75, 0xed, 0x75, 0x1c, 0x16, 0x6a, 0x1e, 0xe6, 0x07, 0x9a, 0xa9, 0x36, 0xa1, 0x61, 0x62, 0x47, 0x5b, 0x39, 0xf4, 0x77, 0xd0, 0xf6, 0x72, 0xaf, 0x3a, 0x0a, 0x6e, 0x56, 0x12, 0xfa, 0x2b, 0xa3, 0x86, 0x31, 0xb8, 0x42, 0x12, 0x1d, 0x03, 0x62, 0xf6, 0x74, 0xdb, 0x09, 0xb0, 0x2b, 0xb9, 0x94, 0xbd, 0xf4, 0xaa, 0x67, 0xcc, 0x31, 0xb0, 0x0f, 0x24, 0x01, 0x64, 0x0f, 0x70, 0x31, 0x67, 0x21, 0x58, 0xc6, 0x5a, 0x9b, 0x7f, 0x32, 0x6e, 0xf8, 0x0c, 0x80, 0x34, 0xec, 0x69, 0x69, 0x79, 0x32, 0x30, 0x68, 0xbc, 0x06, 0xa8, 0x0a, 0x82, 0x83, 0x6d, 0x53, 0x6e, 0x73, 0xca, 0x91, 0x0d, 0xa6]
解码算法大概:
int runShellCode(char* psz,int nLen) { unsigned char szBuf[sizeof(g_shellCode)]={0}; memcpy_s(szBuf,sizeof(g_shellCode),g_shellCode,sizeof(g_shellCode)); //void (*func)(void); for (int i=0;i<sizeof(g_shellCode);i++) { int nIndex=i%nLen; szBuf[i]=szBuf[i]^psz[nIndex]; } int nRet=1; try { ((void(*)(void))&szBuf)(); } catch (...) { nRet=0; } return nRet; } char* psz="BwnsAtPediy2017KX9Ok"; runShellCode(psz,strlen(psz));
shellcode解码正确为:
00C40000 55 PUSH EBP 00C40001 8BEC MOV EBP, ESP 00C40003 6A FF PUSH -0x1 00C40005 68 E8054500 PUSH 0x4505E8 00C4000A 64:A1 00000000 MOV EAX, DWORD PTR FS:[0] 00C40010 50 PUSH EAX 00C40011 83EC 24 SUB ESP, 0x24 00C40014 A1 84044000 MOV EAX, DWORD PTR DS:[<crackme.___s> 00C40019 33C5 XOR EAX, EBP 00C4001B 8945 F0 MOV DWORD PTR SS:[EBP-0x10], EAX 00C4001E 50 PUSH EAX 00C4001F 8D45 F4 LEA EAX, DWORD PTR SS:[EBP-0xC] 00C40022 64:A3 00000000 MOV DWORD PTR FS:[0], EAX 00C40028 8365 D8 00 AND DWORD PTR SS:[EBP-0x28], 0x0 00C4002C 8D45 D8 LEA EAX, DWORD PTR SS:[EBP-0x28] 00C4002F 50 PUSH EAX 00C40030 8D4D DC LEA ECX, DWORD PTR SS:[EBP-0x24] 00C40033 C745 DC 42574E5>MOV DWORD PTR SS:[EBP-0x24], 0x534E5> 00C4003A 51 PUSH ECX 00C4003B 8D45 ED LEA EAX, DWORD PTR SS:[EBP-0x13] 00C4003E C745 E0 1532223>MOV DWORD PTR SS:[EBP-0x20], 0x3F223> 00C40045 50 PUSH EAX 00C40046 8D45 E0 LEA EAX, DWORD PTR SS:[EBP-0x20] 00C40049 C745 E4 6233213>MOV DWORD PTR SS:[EBP-0x1C], 0x3D213> 00C40050 50 PUSH EAX 00C40051 8D45 D0 LEA EAX, DWORD PTR SS:[EBP-0x30] 00C40054 C745 E8 2776747>MOV DWORD PTR SS:[EBP-0x18], 0x7A747> 00C4005B 50 PUSH EAX 00C4005C C645 EC 42 MOV BYTE PTR SS:[EBP-0x14], 0x42 00C40060 E8 AE107DFF CALL crackme.00411113 00C40065 83C4 14 ADD ESP, 0x14 00C40068 8D45 E0 LEA EAX, DWORD PTR SS:[EBP-0x20] 00C4006B 6A 40 PUSH 0x40 00C4006D 68 1D3D4000 PUSH 0x403D1D 00C40072 50 PUSH EAX 00C40073 6A 00 PUSH 0x0 00C40075 FF15 F03D4500 CALL NEAR DWORD PTR DS:[<crackme.Mess>; user32.MessageBoxA 00C4007B 8B4D F4 MOV ECX, DWORD PTR SS:[EBP-0xC] 00C4007E 64:890D 0000000>MOV DWORD PTR FS:[0], ECX 00C40085 59 POP ECX 00C40086 8B4D F0 MOV ECX, DWORD PTR SS:[EBP-0x10] 00C40089 33CD XOR ECX, EBP 00C4008B E8 CB3E7DFF CALL crackme.00413F5B 00C40090 8BE5 MOV ESP, EBP 00C40092 5D POP EBP 00C40093 C3 RETN
反推过程:
1.根据下属函数的比较结果以及数组可以推出来sub_411B30 计算后正确字符串
00411A5C E8 C9460000 CALL <crackme.cmp_is_equ_41612A>
MyArray=[0x02, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x03, 0x02, 0x03, 0x01, 0x00, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x03, 0x02, 0x01, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x02, 0x02, 0x03, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03] stringRet="" nLen=len(MyArray) for x in range(0,nLen/2): index=MyArray[x*2]*4+MyArray[x*2+1] stringRet+=strCode[index] print stringRet //out A30B1B828ABB4A9BA9BB93AAA36B82AA024AB243
根据这个串能计算出sub_410f75之前的数组:
def getNum(findNum): for x in range(0,256): num=(8*(x^0xcc) | ((x^0xcc)>>5))%256 if num==findNum: return x testList=[] print len(stringRet)/2 for i in range(0,len(stringRet)/2): findNum=stringRet[i*2:i*2+2] hexNum=int(findNum,16) testList.append(getNum(hexNum)) 正确序列 #0xb8 0xad 0xaf 0x9c 0x9d 0xbb 0x85 0xbf 0xf9 0xbb 0xbe 0x99 0xb8 0xa1 0x9c 0x99 0x8c 0x85 0x9a 0xa4 正确的
根据上述字符串爆破:
data=[0x89, 0xbc, 0x95, 0xfc, 0xfb, 0xba, 0xed, 0x9a, 0xbb, 0xae, 0xfe, 0x99, 0xa2, 0x98, 0xb9, 0xf9, 0x9f, 0x84, 0x9c, 0xfd, 0x83, 0xad, 0xb6, 0xa9, 0xa5, 0xf5, 0x8c, 0xa7, 0x9e, 0x96, 0x8a, 0xf4, 0x85, 0xbe, 0xa8, 0x8f, 0x86, 0xaf, 0x88, 0x9d, 0x87, 0xbf, 0xff, 0xa1, 0x8b, 0x81, 0xa0, 0xab, 0x8e, 0xbd, 0xb5, 0xaa, 0x82, 0x94, 0xa4, 0x8d, 0xa3, 0xf8, 0xb4, 0xfa, 0x9b, 0xa6, 0xb8, 0x80] # strBox这个序列可以根据data异或0xcc 推出 strBox="!0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" def baoPao(n,value): nDataLen=len(data) for c in strBox: index=data.index(ord(c)^0xcc) y=n+1 while True: nCount=index/5+5 nNum=0 while True: index+=1 if index==64: index=0 nNum+=1 if nCount==nNum: break #print index, y-=1 if y<=0: break index=index%nDataLen if data[index]==value: print n,value,c,hex(ord(c)) #return c rightList=[0xb8,0xad,0xaf,0x9c,0x9d,0xbb,0x85,0xbf,0xf9,0xbb,0xbe,0x99,0xb8,0xa1,0x9c,0x99,0x8c,0x85,0x9a,0xa4] flag="" for i,x in enumerate(rightList): baoPao(i,x) print "\n" print flag
爆破结果:
0 B 0x42 1 j 0x6a 1 w 0x77 2 n 0x6e 3 d 0x64 3 s 0x73 4 A 0x41 4 Y 0x59 4 l 0x6c 5 b 0x62 5 t 0x74 6 P 0x50 6 i 0x69 7 H 0x48 7 e 0x65 8 c 0x63 8 d 0x64 8 s 0x73 9 P 0x50 9 i 0x69 10 y 0x79 11 2 0x32 11 5 0x35 12 0 0x30 12 g 0x67 12 o 0x6f 13 1 0x31 13 a 0x61 14 4 0x34 14 7 0x37 14 B 0x42 15 @ 0x40 15 K 0x4b 15 r 0x72 16 G 0x47 16 J 0x4a 16 X 0x58 17 9 0x39 17 D 0x44 17 I 0x49 18 O 0x4f 19 F 0x46 19 k 0x6b
以上有两个以上数字的有多种可能。
结合一般的函数开头是:
//开头 55 8B EC push ebp mov ebp,esp //结尾 8B E5 5D C3 mov esp,ebp retn
可以推出前三位为:
Bwn
5-8为AtPe
加上猜测:
1.作者ID: 不问少年 Bwsn-->Bwns
2.Pe -->Pediy
再结合下述特征:
最终推得
xorCode=[0x17, 0xfc, 0x82, 0x19, 0xbe, 0x1c, 0xb8, 0x60, 0x21, 0x69, 0x1d, 0x93, 0x30, 0x31, 0x37, 0x4b,0x08, 0xba, 0xa3, 0x4f, 0xe3, 0xf3, 0x6a, 0x33, 0x41, 0x47, 0x95, 0xec, 0x21, 0x99, 0x29, 0xbf, 0x75, 0xc5, 0x53, 0xe8, 0x58, 0x39, 0x4f, 0x6b, 0xc1, 0x12, 0xb6, 0x73, 0xcc, 0x31, 0x88, 0x35, 0xe9, 0x24, 0xa5, 0xf5, 0x75, 0xed, 0x75, 0x1c, 0x16, 0x6a, 0x1e, 0xe6, 0x07, 0x9a, 0xa9, 0x36, 0xa1, 0x61, 0x62, 0x47, 0x5b, 0x39, 0xf4, 0x77, 0xd0, 0xf6, 0x72, 0xaf, 0x3a, 0x0a, 0x6e, 0x56, 0x12, 0xfa, 0x2b, 0xa3, 0x86, 0x31, 0xb8, 0x42, 0x12, 0x1d, 0x03, 0x62, 0xf6, 0x74, 0xdb, 0x09, 0xb0, 0x2b, 0xb9, 0x94, 0xbd, 0xf4, 0xaa, 0x67, 0xcc, 0x31, 0xb0, 0x0f, 0x24, 0x01, 0x64, 0x0f, 0x70, 0x31, 0x67, 0x21, 0x58, 0xc6, 0x5a, 0x9b, 0x7f, 0x32, 0x6e, 0xf8, 0x0c, 0x80, 0x34, 0xec, 0x69, 0x69, 0x79, 0x32, 0x30, 0x68, 0xbc, 0x06, 0xa8, 0x0a, 0x82, 0x83, 0x6d, 0x53, 0x6e, 0x73, 0xca, 0x91, 0x0d, 0xa6] xxDecode=[0x55, 0x8b, 0xec, 0x6a, 0xff, 0x68, 0xe8, 0x05, 0x45, 0x00, 0x64, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x50, 0x83, 0xec, 0x24] #print len(xxDecode) #print hex(ord('O')^xorCode[18]) strKey="" for i,x in enumerate(xxDecode): c=x^xorCode[i] strKey+=chr(c) #print i,hex(c),c,chr(c) print strKey
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课