2019 360杯 re wp--Here are some big nums
测试文件:https://www.lanzous.com/i7303oh
1.准备
获取信息:
- 32位文件
2.IDA打开
找到主函数之后,反编译为伪C代码
1 int sub_404D70() 2 { 3 int result; // eax 4 char v1; // [esp+0h] [ebp-58h] 5 char v2[16]; // [esp+18h] [ebp-40h] 6 char v3; // [esp+28h] [ebp-30h] 7 char v4[16]; // [esp+2Ch] [ebp-2Ch] 8 char v5; // [esp+3Ch] [ebp-1Ch] 9 int v6; // [esp+40h] [ebp-18h] 10 size_t v7; // [esp+44h] [ebp-14h] 11 int i; // [esp+48h] [ebp-10h] 12 int v9; // [esp+54h] [ebp-4h] 13 14 sub_404EF0("------360CTF------\n", v1); 15 sub_404EF0("Please input fl@g:", v1); 16 sub_404F30("%s", (unsigned int)Str); 17 v7 = strlen(Str); 18 if ( v7 == 32 ) 19 { 20 sub_401F20(&v1); 21 v9 = 0; 22 for ( i = 0; i < 16; ++i ) 23 { 24 v4[i] = Str[i]; 25 v2[i] = byte_4080A8[i]; 26 } 27 v5 = 0; 28 v3 = 0; 29 if ( (unsigned __int8)((int (__cdecl *)(char *, signed int))loc_404600)(v4, 16) && (unsigned __int8)sub_404860(v2) ) 30 { 31 sub_404EF0("Congratulations!!!", v1); 32 system("PAUSE"); 33 v9 = -1; 34 sub_401FD0(&v1); 35 result = 0; 36 } 37 else 38 { 39 sub_404EF0("Wrong!!!", v1); 40 v6 = 0; 41 v9 = -1; 42 sub_401FD0(&v1); 43 result = v6; 44 } 45 } 46 else 47 { 48 sub_404EF0("wrong format!!!", v1); 49 result = 0; 50 } 51 return result; 52 }
从第18行代码,我们能够得知flag(输入字符串)长度应该为32
代码的第26行loc_404600(v4, 16) && sub_404860(v2)很明显就是我们需要关注的两个函数,但是打开loc_404600(v4, 16)
.text:00404600 loc_404600: ; CODE XREF: sub_404D70+D3↓p .text:00404600 ; DATA XREF: TlsCallback_0:loc_404552↑o ... .text:00404600 cmp ch, ah .text:00404602 mov ds, word ptr [edx+edx*4] .text:00404605 push es .text:00404606 adc [ecx], ebp .text:00404608 sub eax, 6DD7066Eh .text:0040460D outsb .text:0040460E bound esi, [esi+3Dh] .text:00404611 or ch, bl .text:00404613 push ebx .text:00404614 insd .text:00404615 outsb .text:00404616 bound esi, [esi-14h] .text:00404619 adc dl, 76h .text:0040461C insd .text:0040461D outsb .text:0040461E movsd .text:0040461F xor eax, [ecx+6D76626Eh] .text:00404625 push es .text:00404626 cdq .text:00404627 pop ss .text:00404628 sub eax, 0E13BEF6Eh .text:0040462D xchg bh, [esi+ebp*4] .text:00404630 xchg eax, edx .text:00404631 xchg eax, ecx .text:00404632 movsd .text:00404633 xor edx, [ecx+6D76626Eh] .text:00404639 push es .text:0040463A inc ecx .text:0040463B adc al, 2Dh .text:0040463D outsb .text:0040463E out dx, eax .text:0040463F cmp ecx, ecx .text:00404641 xchg ch, [eax] .text:00404643 scasb .text:00404644 xchg eax, edx .text:00404645 xchg eax, ecx .text:00404646 movsb .text:00404647 xor edx, [ecx+0F160A6Fh]
于是尝试用OD进行动态调试,并和IDA中的伪C代码作比较
3. OD 动态调试
一句一句分析理解,直接上代码和注释了
3.1 loc_404600函数
输入“abcdefghijklmnopqrstuvwxyz123456”作为测试,第56行找到了loc_404600函数,进入
1 1 00404600 55 push ebp 2 2 00404601 8BEC mov ebp,esp 3 3 00404603 6A FF push -0x1 4 4 00404605 68 735F4000 push test.00405F73 5 5 0040460A 64:A1 00000000 mov eax,dword ptr fs:[0] 6 6 00404610 50 push eax ; eax=0x19FF1C 7 7 00404611 64:8925 0000000>mov dword ptr fs:[0],esp 8 8 00404618 81EC B0000000 sub esp,0xB0 ; 为局部变量申请栈空间 9 9 0040461E C745 EC 0000000>mov dword ptr ss:[ebp-0x14],0x0 ; 原本指向flag前16个字符 10 10 00404625 68 FB614000 push test.004061FB ; 指向开头的字符串--360CTF... 11 11 0040462A 8D4D 8C lea ecx,dword ptr ss:[ebp-0x74] 12 12 0040462D E8 5ED8FFFF call test.00401E90 13 13 00404632 C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0 14 14 00404639 68 23624000 push test.00406223 15 15 0040463E 8D4D A4 lea ecx,dword ptr ss:[ebp-0x5C] ; 0x19fe60其中的值为0x0F 16 16 00404641 E8 4AD8FFFF call test.00401E90 17 17 00404646 C645 FC 01 mov byte ptr ss:[ebp-0x4],0x1 ; 下面的字符串压入了0x19FE00=EBP-0xC0 18 18 0040464A 68 60624000 push test.00406260 ; ASCII "12345679" 19 19 0040464F 8D8D 74FFFFFF lea ecx,dword ptr ss:[ebp-0x8C] 20 20 00404655 E8 36D8FFFF call test.00401E90 ; 返回的eax="123456789" 21 21 0040465A C645 FC 02 mov byte ptr ss:[ebp-0x4],0x2 22 22 0040465E C745 E8 6C62400>mov dword ptr ss:[ebp-0x18],test.0040626>; ASCII "greatctf" 23 23 00404665 8D4D F3 lea ecx,dword ptr ss:[ebp-0xD] 24 24 00404668 E8 F3D6FFFF call test.00401D60 25 25 0040466D C645 FC 03 mov byte ptr ss:[ebp-0x4],0x3 26 26 00404671 8B45 E8 mov eax,dword ptr ss:[ebp-0x18] 27 27 00404674 50 push eax ; 将新赋值的eax="greatctf"压入栈中 28 28 00404675 E8 14160000 call <jmp.&api-ms-win-crt-string-l1-1-0.>; 计算长度strlen 29 29 0040467A 83C4 04 add esp,0x4 30 30 0040467D 8BC8 mov ecx,eax ; ecx=8 31 31 0040467F 8B45 EC mov eax,dword ptr ss:[ebp-0x14] ; eax=[ebp-0x14] 32 32 00404682 33D2 xor edx,edx ; edx清零 33 33 00404684 F7F1 div ecx ; [ebp-0x14]/8 34 34 00404686 8B45 E8 mov eax,dword ptr ss:[ebp-0x18] ; eax="greatctf" 35 35 00404689 0FBE0C10 movsx ecx,byte ptr ds:[eax+edx] ; 取eax=“greatctf”的第edx(这里相当于是[ebp-0x14]%8的值)个字节放入ecx 36 36 0040468D 8B55 08 mov edx,dword ptr ss:[ebp+0x8] ; edx="abcdefghijklmnop",flag前16个字符 37 37 00404690 0355 EC add edx,dword ptr ss:[ebp-0x14] ; edx=edx+0 38 38 00404693 0FBE02 movsx eax,byte ptr ds:[edx] ; 取edx="abc..."的第edx([ebp-0x14])个字节放入eax 39 39 00404696 33C1 xor eax,ecx ; “greatctf"的首字母与"abcd.."的首字母做异或 40 40 00404698 8B4D 08 mov ecx,dword ptr ss:[ebp+0x8] ; ecx="abcdefghijklmnop"的地址 41 41 0040469B 034D EC add ecx,dword ptr ss:[ebp-0x14] ; ecx=ecx+0---有点怀疑是个循环操作了 42 42 0040469E 8801 mov byte ptr ds:[ecx],al ; 将异或结果保存到"abcd..."的首字符处 43 43 004046A0 8B55 EC mov edx,dword ptr ss:[ebp-0x14] ; edx=0 44 44 004046A3 83C2 01 add edx,0x1 ; edx=edx+1 45 45 004046A6 8955 EC mov dword ptr ss:[ebp-0x14],edx ; 更新[ebp-0x14]中的值为edx 46 46 004046A9 8B45 EC mov eax,dword ptr ss:[ebp-0x14] ; 将[ebp-0x14]赋值到eax 47 47 004046AC 3B45 0C cmp eax,dword ptr ss:[ebp+0xC] ; eax与16比较,截取的部分flag的长度 48 48 004046AF ^ 7C C0 jl Xtest.00404671 ; 小于就跳回上面,继续执行异或操作 49 49 004046B1 83EC 18 sub esp,0x18 ; 获取大小为0x18的栈空间 50 50 004046B4 8BCC mov ecx,esp 51 51 004046B6 8965 D0 mov dword ptr ss:[ebp-0x30],esp ; 将esp=0x19FDEC存入0x19FE90 52 52 004046B9 8B55 08 mov edx,dword ptr ss:[ebp+0x8] ; 将0x19fefc存入edx 53 53 004046BC 52 push edx ; edx入栈,0x19fde8,[ebp-d8],edx的地址指向的值是异或后的结果 54 54 004046BD E8 CED7FFFF call test.00401E90 55 55 004046C2 8945 CC mov dword ptr ss:[ebp-0x34],eax ; eax=0x19FDEC,即异或后的地址存入0x19FE8C 56 56 004046C5 C645 FC 04 mov byte ptr ss:[ebp-0x4],0x4 ; 0x4存入0x19febc 57 57 004046C9 6A 2A push 0x2A ; 将0x2a压入栈中 58 58 004046CB 83EC 18 sub esp,0x18 ; esp=0x19fdd0 59 59 004046CE 8BCC mov ecx,esp 60 60 004046D0 8965 C8 mov dword ptr ss:[ebp-0x38],esp 61 61 004046D3 8B45 08 mov eax,dword ptr ss:[ebp+0x8] 62 62 004046D6 50 push eax ; eax=0x19fefc 63 63 004046D7 E8 B4D7FFFF call test.00401E90 64 64 004046DC 8D8D 5CFFFFFF lea ecx,dword ptr ss:[ebp-0xA4] ; ecx=0x19fe1c 65 65 004046E2 51 push ecx 66 66 004046E3 C645 FC 03 mov byte ptr ss:[ebp-0x4],0x3 ; 0x3存入0x19febc 67 67 004046E7 8D4D F3 lea ecx,dword ptr ss:[ebp-0xD] ; ecx=0x19feb3 68 68 004046EA E8 51F0FFFF call test.00403740 ; 自身平方 69 69 004046EF 8945 E4 mov dword ptr ss:[ebp-0x1C],eax ; eax=0x19fe1c 70 70 004046F2 8B55 E4 mov edx,dword ptr ss:[ebp-0x1C] ; edx=0x19fe1c 71 71 004046F5 8955 E0 mov dword ptr ss:[ebp-0x20],edx 72 72 004046F8 8B45 E0 mov eax,dword ptr ss:[ebp-0x20] ; eax=0x19fe1c 73 73 004046FB 50 push eax 74 74 004046FC 8D4D 8C lea ecx,dword ptr ss:[ebp-0x74] ; ecx=0x19fe4c 75 75 004046FF E8 0CD9FFFF call test.00402010 76 76 00404704 8D8D 5CFFFFFF lea ecx,dword ptr ss:[ebp-0xA4] ; ecx=0x19fe1c 77 77 0040470A E8 C1D8FFFF call test.00401FD0 78 78 0040470F 83EC 18 sub esp,0x18 ; esp=0x19fdec 79 79 00404712 8BCC mov ecx,esp 80 80 00404714 8965 C4 mov dword ptr ss:[ebp-0x3C],esp ; [0x19fe84]=0x19fdec 81 81 00404717 8D95 74FFFFFF lea edx,dword ptr ss:[ebp-0x8C] ; edx="12345679"的地址0x19fe34 82 82 0040471D 52 push edx ; edx入栈 83 83 0040471E E8 CDD6FFFF call test.00401DF0 84 84 00404723 8945 C0 mov dword ptr ss:[ebp-0x40],eax ; eax为"12345679"的地址,赋值到[0x19fe80] 85 85 00404726 C645 FC 05 mov byte ptr ss:[ebp-0x4],0x5 ; [0x19febc]=0x5 86 86 0040472A 6A 2A push 0x2A ; 2a入栈 87 87 0040472C 83EC 18 sub esp,0x18 88 88 0040472F 8BCC mov ecx,esp 89 89 00404731 8965 BC mov dword ptr ss:[ebp-0x44],esp ; [0x19fe7c]=0x19fdd0 90 90 00404734 8D45 8C lea eax,dword ptr ss:[ebp-0x74] ; 取")*0990510578848262884380342813696"的地址0x19fe4c存入eax 91 91 00404737 50 push eax 92 92 00404738 E8 B3D6FFFF call test.00401DF0 93 93 0040473D 8D8D 44FFFFFF lea ecx,dword ptr ss:[ebp-0xBC] ; ecx=0x19fe04 94 94 00404743 51 push ecx 95 95 00404744 C645 FC 03 mov byte ptr ss:[ebp-0x4],0x3 ; [0x19febc]=0x3 96 96 00404748 8D4D F3 lea ecx,dword ptr ss:[ebp-0xD] ; ecx=0x19feb3 97 97 0040474B E8 F0EFFFFF call test.00403740 ; 与123456789相乘 98 98 00404750 8945 DC mov dword ptr ss:[ebp-0x24],eax ; "'-+4823339700468499618579595434614979584"的地址eax=0x19fe04 99 99 00404753 8B55 DC mov edx,dword ptr ss:[ebp-0x24] ; edx=0x19fe04 100 100 00404756 8955 D8 mov dword ptr ss:[ebp-0x28],edx ; [0x19fe98]=0x19fe04 101 101 00404759 8B45 D8 mov eax,dword ptr ss:[ebp-0x28] ; eax=0x19fe04 102 102 0040475C 50 push eax ; eax入栈 103 103 0040475D 8D4D A4 lea ecx,dword ptr ss:[ebp-0x5C] ; ecx=0x19fe64 104 104 00404760 E8 ABD8FFFF call test.00402010 105 105 00404765 8D8D 44FFFFFF lea ecx,dword ptr ss:[ebp-0xBC] ; ecx=0x19fe04 106 106 0040476B E8 60D8FFFF call test.00401FD0 107 107 00404770 8D4D A4 lea ecx,dword ptr ss:[ebp-0x5C] ; ecx=0x19fe64 108 108 00404773 E8 48FEFFFF call test.004045C0 109 109 00404778 8945 D4 mov dword ptr ss:[ebp-0x2C],eax ; 将"'-+4823339700468499618579595434614979584"的地址0x5d68d0存入0x19fe94 110 110 0040477B 8B4D D4 mov ecx,dword ptr ss:[ebp-0x2C] ; ecx=0x19fe94 111 111 0040477E 51 push ecx ; s2="'-+4823339700468499618579595434614979584" 112 112 0040477F 68 78624000 push test.00406278 ; s1="667339003789000121539302795007135856775"地址入栈 113 113 00404784 E8 0B150000 call <jmp.&api-ms-win-crt-string-l1-1-0.>; strcmp 114 114 00404789 83C4 08 add esp,0x8 115 115 0040478C 85C0 test eax,eax 116 116 0040478E 75 41 jnz Xtest.004047D1
地址004046EA ,调用test.00403740,进入之后可以找到一段平方的代码
1 0040287B 8B55 F0 mov edx,dword ptr ss:[ebp-0x10] ; 循环跳转位置 2 0040287E 83EA 01 sub edx,0x1 3 00402881 8955 F0 mov dword ptr ss:[ebp-0x10],edx 4 00402884 837D F0 00 cmp dword ptr ss:[ebp-0x10],0x0 5 00402888 0F8C 83000000 jl test.00402911 ; 循环结束条件 6 0040288E 8B45 F0 mov eax,dword ptr ss:[ebp-0x10] 7 00402891 50 push eax 8 00402892 8D4D 0C lea ecx,dword ptr ss:[ebp+0xC] 9 00402895 E8 16F8FFFF call test.004020B0 10 0040289A 0FBE08 movsx ecx,byte ptr ds:[eax] ; 将低位的一字节移动到ecx 11 0040289D 83E9 30 sub ecx,0x30 12 004028A0 8BC1 mov eax,ecx 13 004028A2 0FAF45 E0 imul eax,dword ptr ss:[ebp-0x20] 14 004028A6 0345 E8 add eax,dword ptr ss:[ebp-0x18] 15 004028A9 99 cdq 16 004028AA B9 0A000000 mov ecx,0xA 17 004028AF F7F9 idiv ecx 18 004028B1 8955 D8 mov dword ptr ss:[ebp-0x28],edx 19 004028B4 8B55 F0 mov edx,dword ptr ss:[ebp-0x10] 20 004028B7 52 push edx 21 004028B8 8D4D 0C lea ecx,dword ptr ss:[ebp+0xC] 22 004028BB E8 F0F7FFFF call test.004020B0 23 004028C0 0FBE00 movsx eax,byte ptr ds:[eax] ; 将低位的一字节移动到eax 24 004028C3 83E8 30 sub eax,0x30 ; 将字符转换为整型数据 25 004028C6 0FAF45 E0 imul eax,dword ptr ss:[ebp-0x20] ; 从低位开始一位一位的自身相乘 26 004028CA 0345 E8 add eax,dword ptr ss:[ebp-0x18] 27 004028CD 99 cdq 28 004028CE B9 0A000000 mov ecx,0xA 29 004028D3 F7F9 idiv ecx 30 004028D5 8945 E8 mov dword ptr ss:[ebp-0x18],eax 31 004028D8 8D55 84 lea edx,dword ptr ss:[ebp-0x7C] 32 004028DB 52 push edx 33 004028DC 8B45 D8 mov eax,dword ptr ss:[ebp-0x28] 34 004028DF 83C0 30 add eax,0x30 ; 整型转换为字符 35 004028E2 50 push eax 36 004028E3 8D8D 48FFFFFF lea ecx,dword ptr ss:[ebp-0xB8] 37 004028E9 51 push ecx 38 004028EA E8 01E8FFFF call test.004010F0 39 004028EF 83C4 0C add esp,0xC 40 004028F2 8945 C8 mov dword ptr ss:[ebp-0x38],eax 41 004028F5 8B55 C8 mov edx,dword ptr ss:[ebp-0x38] 42 004028F8 52 push edx 43 004028F9 8D4D 84 lea ecx,dword ptr ss:[ebp-0x7C] 44 004028FC E8 0FF7FFFF call test.00402010 45 00402901 8D8D 48FFFFFF lea ecx,dword ptr ss:[ebp-0xB8] 46 00402907 E8 C4F6FFFF call test.00401FD0 47 0040290C ^ E9 6AFFFFFF jmp test.0040287B ; 进行下一个循环
同样的,第97行代码,对字符串乘12345679也是一样
3.2 处理总结
在第109行~第113行代码,这就是一个将输入字符串前16个字符处理之后(先异或"greatctf",再自身平方,最后乘以12345679),与"667339003789000121539302795007135856775"比较。
因此,我们只需要逆向处理,就能得到输入字符的前16个字符
4.脚本处理
str1 = "greatctf" num2 = 667339003789000121539302795007135856775 flag="" num2 = num2 // 12345679 num2 = pow(num2, 0.5) str2 = str(num2) for i in range(16): flag += chr(ord(str2[i]) ^ ord(str1[i%8])) print(flag)
得到前16个字符
PAPSETGQ_FRRBQLS
5. sub_404860函数
1 v33 = &v21; 2 v32 = (int *)&v25; 3 v31 = (int *)&v23; 4 v30 = &v22; 5 v21 = *a1; 6 v25 = a1[1]; 7 v26 = 0; 8 v23 = a1[2]; 9 v24 = 0; 10 LOWORD(v22) = *((_WORD *)a1 + 6); 11 BYTE2(v22) = *((_BYTE *)a1 + 14); 12 *(_DWORD *)((char *)&v22 + 3) = *((unsigned __int8 *)a1 + 15); 13 HIBYTE(v22) = 0; 14 srand(0xBC6146u); 15 v29 = rand() % 360; 16 v20 = rand() % 360; 17 v27 = rand() % 360; 18 v28 = rand() % 360; 19 v1 = sub_404FC0(v25, v26, 3, 0); 20 v3 = v2; 21 LODWORD(v4) = sub_404FC0(v29, HIDWORD(v29), 1000, 0); 22 if ( v21 + __PAIR__(v3, v1) - v4 != 0x1A06491E7i64 ) 23 goto LABEL_9; 24 v5 = sub_404FC0(v27, HIDWORD(v27), v23, v24); 25 v7 = v6; 26 LODWORD(v8) = sub_404FC0(v25, v26, v20, HIDWORD(v20)); 27 if ( __PAIR__(v7, v5) - v8 != 0x244BFD2B9Ci64 28 || (v9 = sub_404FC0(v23, v24, v28, HIDWORD(v28)), 29 v11 = v10, 30 LODWORD(v12) = sub_404FC0(v22 + 890, (unsigned __int64)(v22 + 890) >> 32, v29, HIDWORD(v29)), 31 v12 + __PAIR__(v11, v9) != 0x71CE119D5i64) 32 || (v13 = sub_404FC0(v28, HIDWORD(v28), 136, 0), 33 v15 = sub_404FC0(v13, v14, v22, HIDWORD(v22)), 34 v17 = v16, 35 LODWORD(v18) = sub_404FC0(v27, HIDWORD(v27), v21, HIDWORD(v21)), 36 __PAIR__(v17, v15) - v18 != 0x431E9A36840i64) ) 37 { 38 LABEL_9: 39 result = 0; 40 } 41 else 42 { 43 result = 1; 44 } 45 return result; 46 }
5.1 代码分析
第14行代码~第18行代码,在生成随机数,这些数在后面也会用到,因此可以写一个生成随机数的程序。
#include <bits/stdc++.h> using namespace std; int main(){ int v15,v8,v14,v13; srand(0xbc6146); v15 = rand() % 360; v8 = rand() % 360; v13 = rand() % 360; v14 = rand() % 360; cout << v15 << " " << v8 << " " << v13 << " " << v14 << endl; return 0; }
输出
2 11 192 31
又因为这个函数要返回1,使得上一层的判断成立,因此我们要让27~36行代码不成立。则,利用z3包,我们能够写出脚本
5.2 脚本获取
引用官方的WP给出的脚本
from z3 import * import struct s = [Int('serial%d' % i) for i in range(4)] z3 = Solver() v1 = 2 v2 = 11 v3 = 192 v4 = 31 z3.add(3*s[1]-1000*v1+s[0] == 6985912807) z3.add(v3*s[2]-s[1]*v2 == 155893705628) z3.add(s[2]*v4+(890+s[3])*v1 == 30549285333) z3.add(v4*136*s[3]-v3*s[0] == 4612419708992) print(z3.check()) answer=z3.model() res = "" for d in s: num = answer.eval(d).as_long() res += struct.pack('<L', num) print(repr(res))
得到
simpleRe__360CTF
6.get flag!
两段组合
PAPSETGQ_FRRBQLSsimpleRe__360CTF
参考