-
-
[原创]看雪 2022 KCTF 春季赛 第三题 石像病毒
-
2022-5-16 06:16 7255
-
IDA逆向
主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | int __cdecl main_0( int argc, const char * * argv, const char * * envp) { void * v3; / / esp char v7; / / [esp - 10h ] [ebp - 1048h ] struct struc_1 v; / / [esp + 0h ] [ebp - 1038h ] BYREF CPPEH_RECORD ms_exc; / / [esp + 1020h ] [ebp - 18h ] v3 = alloca( 4128 ); memset(&v, 0xCCu , sizeof(v)); ms_exc.registration.ScopeTable = (PSCOPETABLE_ENTRY)(( int )ms_exc.registration.ScopeTable ^ __security_cookie); memset(v.s, 0 , sizeof(v.s)); memset(v.ciphertext, 0 , sizeof(v.ciphertext)); gets_s(v.s, 4000u ); if ( strlen(v.s) = = 32 ) { for ( v.field_40 = 0 ; v.field_40 < 512 ; + + v.field_40 ) { v.field_3C = 0 ; MEMORY[ 0 ] = v.field_40; ms_exc.registration.TryLevel = - 2 ; } strcpy(v.field_24, "Enj0y_1t_4_fuuuN" ); _DX = '_\0' ; * (_WORD * )&v.field_24[ 17 ] = 0 ; v.field_24[ 19 ] = 0 ; memset(v.field_8, 0 , sizeof(v.field_8)); for ( v.field_0 = 0 ; v.field_0 < 512 ; + + v.field_0 ) { __asm { insb } ms_exc.registration.TryLevel = - 2 ; _DX = LOWORD(v.field_0) + 1 ; } j_md5(v.field_24, 0x10u , v.field_8); j_aes(v.field_8, 0x10u , v.s, v.ciphertext, 32 ); if ( !memcmp(v.ciphertext, finalcompare, 0x20u ) ) j_printf( "OK\n" , v7); else j_printf( "NO\n" , v7); return 0 ; } else { j_printf( "NO\n" , v7); return 0 ; } } |
背景:SEH
注意到 MEMORY[0] = v.field_40;
,这是第一次引发异常的位置。
引发异常的位置还有若干个;程序利用 SEH(Structured Exception Handling,结构化异常处理) 机制处理异常,在处理过程中对程序进行了一定的修改。
这部分的汇编如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | .text: 004028B8 loc_4028B8: ; CODE XREF: _main_0:loc_40290B↓j .text: 004028B8 mov ecx, [ebp + v.field_40] .text: 004028BE add ecx, 1 .text: 004028C1 mov [ebp + v.field_40], ecx .text: 004028C7 .text: 004028C7 loc_4028C7: ; CODE XREF: _main_0 + F6↑j .text: 004028C7 cmp [ebp + v.field_40], 200h .text: 004028D1 jge short loc_40290D .text: 004028D3 ; __try { / / __except at loc_402901 .text: 004028D3 mov [ebp + ms_exc.registration.TryLevel], 0 .text: 004028DA mov [ebp + v.field_3C], 0 .text: 004028E4 mov edx, [ebp + v.field_3C] .text: 004028EA mov al, byte ptr [ebp + v.field_40] .text: 004028F0 mov [edx], al .text: 004028F0 ; } / / starts at 4028D3 .text: 004028F2 mov [ebp + ms_exc.registration.TryLevel], 0FFFFFFFEh .text: 004028F9 jmp short loc_40290B .text: 004028FB ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .text: 004028FB .text: 004028FB loc_4028FB: ; DATA XREF: .rdata:stru_40A030↓o .text: 004028FB ; __except filter / / owned by 4028D3 .text: 004028FB mov eax, 1 .text: 00402900 retn .text: 00402901 ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .text: 00402901 .text: 00402901 loc_402901: ; DATA XREF: .rdata:stru_40A030↓o .text: 00402901 ; __except(loc_4028FB) / / owned by 4028D3 .text: 00402901 mov esp, [ebp + ms_exc.old_esp] .text: 00402904 mov [ebp + ms_exc.registration.TryLevel], 0FFFFFFFEh .text: 0040290B .text: 0040290B loc_40290B: ; CODE XREF: _main_0 + 139 ↑j .text: 0040290B jmp short loc_4028B8 .text: 0040290D ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .text: 0040290D |
IDA 已经标记出了 __try、__except filter 和 __except 块(在反编译的C代码中看不到)。
在 .rdata
节也有相应的数据结构,例如:
1 2 3 4 5 6 7 8 9 10 11 12 | .rdata: 0040A030 stru_40A030 dd 0FFFFFFE4h ; GSCookieOffset .rdata: 0040A030 ; DATA XREF: _main_0 + 5 ↑o .rdata: 0040A030 dd 0 ; GSCookieXOROffset .rdata: 0040A030 dd 0FFFFEFB8h ; EHCookieOffset .rdata: 0040A030 dd 0 ; EHCookieXOROffset .rdata: 0040A030 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel .rdata: 0040A030 dd offset loc_4028FB ; ScopeRecord.FilterFunc .rdata: 0040A030 dd offset loc_402901 ; ScopeRecord.HandlerFunc .rdata: 0040A030 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel .rdata: 0040A030 dd offset loc_4029AF ; ScopeRecord.FilterFunc .rdata: 0040A030 dd offset loc_4029B5 ; ScopeRecord.HandlerFunc .rdata: 0040A058 align 10h |
关于SEH机制,在搜索资料的过程中找到了两篇个人认为不错的文章:
- 看雪CTF签到题SEH异常处理--MysteriousLetter2:快速对SEH形成基本的印象。
- SEH的非常好的总结:系统化的介绍了SEH机制的设计思想,以及操作系统、编译器、运行时库三者的协同设计,还有汇编级别的实现细节等。强烈推荐完整阅读一遍,虽然文章很长,但是非常值得。
对于本题,只需要理解到本题程序发生异常后的控制流都是先运行 __except filter 块再运行 __except 块即可。
在此基础上也可以patch程序让程序的控制流恢复正常。(但对于做题来讲速度就太慢了)
分析
通过程序结构和常量,初步识别出 sub_402DB0 是 md5,sub_402070 是 AES (是否魔改过暂时未知)。
动态调试。异常会被调试器捕获,需要透传给程序,在 x64dbg 调试器中按 shift+F7/F8/F9
。
md5 过程中在 0x402D1C 触发了一次除零异常,然后进入 __except filter,在 0x402D2B 调用 0x402C60 修改了 md5 常量表的一个值。
对于 AES,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | int __cdecl aes(void * Src, size_t Size, char * plaintext, char * ciphertext, unsigned int a5) { int j; / / [esp + 4h ] [ebp - 1C8h ] unsigned int i; / / [esp + 8h ] [ebp - 1C4h ] int v8[ 6 ]; / / [esp + 10h ] [ebp - 1BCh ] BYREF int v9[ 11 ]; / / [esp + 28h ] [ebp - 1A4h ] BYREF char * v10; / / [esp + 54h ] [ebp - 178h ] char * v11; / / [esp + 58h ] [ebp - 174h ] char v12[ 360 ]; / / [esp + 60h ] [ebp - 16Ch ] BYREF v11 = ciphertext; v10 = v12; memset(&v9[ 6 ], 0 , 16 ); memset(v9, 0 , 16 ); memset(v8, 0 , 16 ); if ( !Src || !plaintext || !ciphertext ) return - 1 ; if ( Size > 0x10 ) return - 1 ; if ( a5 % 0x10 ) return - 1 ; memcpy(v9, Src, Size); sub_4010BE(( int )v9, 16 , ( int )v12); for ( i = 0 ; i < a5; i + = 16 ) { j_bytes_to_aes_state(( int )v8, ( int )plaintext); j_addroundkey(( int )v8, ( int )v10); for ( j = 1 ; j < 10 ; + + j ) { v10 + = 16 ; j_subbytes(( int )v8); j_shiftrows(( int )v8); j_mixcolumns(( int )v8); j_addroundkey(( int )v8, ( int )v10); } j_subbytes(( int )v8); j_shiftrows(( int )v8); j_addroundkey(( int )v8, ( int )(v10 + 16 )); j_aes_state_to_bytes(( int )v8, ( int )v11); v11 + = 16 ; plaintext + = 16 ; v10 = v12; } return 0 ; } |
在 sub_4010BE
(AES 的 key expansion) 里,0x401685 处有无效指令异常(可以手动patch成nop,IDA就能正确反编译此函数并识别出 __except 块灯信息了),在 __except filter 0x40168F 处调用到 sub_402330 函数交换了 sbox 0x71 和 0xA3 两项的值。(动态调试发现是每轮都会交换)
实际上,上面的分析全部都不用做,直接等到 sub_4010BE
结束从内存中提取展开后的 11 个 AES 轮密钥。
接下来是 AES 的四种轮操作,自己找一份标准 AES 实现,打印出每步的计算结果与程序做比对,看程序是否有对标准算法做修改:
- addroundkey 没有修改。不过要注意 AES 内部状态是列优先排布的 4*4 矩阵,而 v8 变量是这个矩阵的行优先表示,一些第三方库可能有不同的表示方式。
- subbytes:动态调试发现sbox是修改后的状态,解密时需要同步修改 invsbox
- shiftrows:动态调试发现移位方向相反,即实际上做了 invshiftrows
- mixcolumns:没有修改,但是在 0x401DB0 的 __except filter 中调用 sub_4023D0 修改了最终待比较的常量的第 16、17 个字符
脚本
根据分析结果修改 AES 解密的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | from phoenixAES import AddKey, SBox, ShiftRow, MC, InvSBox, InvShiftRow, InvMC import phoenixAES roundkeys = [ bytes.fromhex( "ffb1652fd086ed310f5c289a9d054840" ), bytes.fromhex( "f6ef0e7c2669e34d2935cbd7b4308397" ), bytes.fromhex( "7e620a92580be9df713e2208c50ea19f" ), bytes.fromhex( "a5c4a1a4fdcf487b8cf16a7349ffcbec" ), bytes.fromhex( "6bffb7b39630ffc81ac195bb533e5e57" ), bytes.fromhex( "301205fba622fa33bce36f88efdd31df" ), bytes.fromhex( "aecdc41c08ef3e2fb40c51a75bd16078" ), bytes.fromhex( "12f4fa8c1a1bc4a3ae179504f5c6f57c" ), bytes.fromhex( "02124eea18098a49b61e1f4d43d8ea31" ), bytes.fromhex( "c5082f76dd01a53f6b1fba7228c75043" ), bytes.fromhex( "df3ce913023d4c2c6922f65e41e5a61d" ) ] # dumped from memory sbox = phoenixAES._AesSBox sbox[ 0x71 ], sbox[ 0xA3 ] = sbox[ 0xA3 ], sbox[ 0x71 ] phoenixAES._AesInvSBox = [sbox.index(i) for i in range ( 256 )] def AddKey(state, key): r = bytearray( 16 ) for i in range ( 4 ): for j in range ( 4 ): r[ 4 * i + j] = state[ 4 * i + j] ^ key[ 4 * i + 3 - j] return r InvShiftRow = ShiftRow def aesdec(ciphertext, roundkeys): assert len (ciphertext) % 16 = = 0 r = b"" for i in range ( 0 , len (ciphertext), 16 ): state = ciphertext[i:i + 16 ] state = AddKey(state, roundkeys[ 10 ]) state = InvShiftRow(state) state = InvSBox(state) for i in range ( 9 , 0 , - 1 ): state = AddKey(state, roundkeys[i]) state = InvMC(state) state = InvShiftRow(state) state = InvSBox(state) state = AddKey(state, roundkeys[ 0 ]) r + = state return r state = bytearray.fromhex( "57 7C F5 6D 56 96 77 45 B0 BD A1 C7 89 A5 AB DC F2 F4 4B FE BE F5 F5 5C 4D 30 42 0F 2B 3B E6 CB" .replace( ' ' , '')) state[ 16 ] = 256 - 12 state[ 17 ] = 256 - 14 print (aesdec(state, roundkeys)) |
得到flag:
1 | flag{db5c6a8dfec4d0ec5569899640} |
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。