본문 바로가기

Pwnable/개념

[Pwnable] Dreamhack STAGE 11

https://dreamhack.io/lecture/roadmaps/2

 

System Hacking

시스템 해킹을 공부하기 위한 로드맵입니다.

dreamhack.io


[ Pwnable stage11: Use-After-Free ]

 메모리 참조에 사용한 포인터를 메모리 해제 후에 적절히 초기화하지 않아서 또는 해제한 메모리를 초기화하지 않고 다음 청크에 재할당해주면서 발생하는 취약점

 

* Dangling Pointer

 - 유효하지 않은 메모리 영역을 가리키는 포인터

 - 메모리 동적 할당에 사용되는 malloc 함수는 할당한 메모리의 주소를 반환함.

   일반적으로 메모리를 동적 할당할 때는 포인터를 선언하고, 그 포인터에 malloc 함수가 할당한 메모리의 주소를

    저장함. 그리고 해당 포인터를 참조하여 할당한 메모리에 접근함

 - 메모리를 해제할 때는 free 함수를 호출함.

   그런데 free 함수는 청크를 ptmalloc에 반환하기만 할 뿐, 청크의 주소를 담고 있던 포인터를 초기화하지 않음

   따라서 free 호출 이후 프로그래머가 포인터를 초기화하지 않으면, 해당 포인터는 해제된 청크를 가리키는

     dangling pointer가 됨

 dangling pointer가 생긴다고 해서 프로그램이 보안적으로 취약한 것은 아니지만, 프로그램이 예상치 못한 동작을 할 가능성을 키우며, 경우에 따라서는 공격자에게 공격 수단으로 활용될 수도 있음

 

 

* Use After Free (UAF)

 - 해제된 메모리에 접근할 수 있을 때 발생하는 취약점

 - dangling pointer로 인해 발생하기도 하지만, 새롭게 할당된 영역을 초기화하지 않고 사용하면서 발생하기도 함

 - malloc과 free 함수는 할당 또는 해제할 메모리의 데이터들을 초기화하지 않음. 따라서 새롭게 할당된 청크를

     프로그래머가 명시적으로 초기화하지 않으면, 메모리에 남아있던 데이터가 유출되거나 사용될 수 있음

 

 

[ UAF 취약점 예제 코드 ]

 

해당 코드는 UAF 취약점을 가지고 있는 예제 코드이다.

 

먼저 NameTag와 Secret 구조체가 정의되어 있는데, main 함수를 보면 외부에 유출되면 안 되는 Secret 구조체를 먼저 할당하고 있다.

 

그다음 secret_name, secret_info, code에 값을 입력하고, 해제한다.

 

그리고 아래에서는 사원의 정보를 담고 있는 nametag를 생성하고, team_name, name에 각각 값을 입력하고, 입력한 데이터를 출력한다.

 

이후 마지막에서 함수 포인터 func가 NULL이 아니라면 포인터가 가리키는 주소를 출력하고, 해당 주소의 함수를 호출한다.

 

해당 코드를 직접 실행해보면 결과는 아래와 같다.

 

결과를 보니, Team Name으로 “security team” 문자열이 출력되고, 값을 입력한 적 없는 함수 포인터가 0x1337을 가리킨다.

 

 

 

→ uaf 동적 분석(1)

 - ptmalloc2는 새로운 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인하고, 있다면 해당 청크를 꺼내어 재사용한다.

 - 예제에서 nametag와 secret은 같은 크기의 구조체이며, 따라서 먼저 할당한 secret을 해제하고 nametag를 할당하면, nametag는 secret과 같은 메모리 영역을 사용하게 된다. 이때 free는 해제한 메모리의 데이터를 초기화하지 않으므로, nametag에는 secret의 값이 일부 남아있게 된다.

 

 

gdb를 이용하여 secret을 해제한 직후 secret이 사용하던 메모리 영역을 출력해보았다.

Heap 명령은 할당 및 해제된 청크들의 정보를 조회하는 명령어이다.

 

확인해보니 secret_namedms 적절한 fd와 bk값으로 초기화됐다고 한다.

 

(찾아보니 fd는 forward pointer to next chunk in list이고, bk는 back pointer to previous chunk in list이고, heap이 생성될 때 data 영역에 fd와 bk가 생성된다고 한다.)

 

그러나 secret_info의 값은 그대로 남아있는 것을 볼 수 있다.

 

 

→ uaf 동적 분석(2)

 - 다음으로 nametag를 할당하고, printf 함수를 호출하는 시점에서 nametag 멤버 변수들의 값을 확인해보았다.

Gdb로 확인해보니

nametag->team_name에는 “security team”이 그대로 입력되었으나,

nametag->name에는 초기화되지 않은 secret_info의 값이 존재했다.

또한 nametag->func 위치에는 secret->code에 대입했던 0x1337이 남아있는 것을 알 수 있다.

해당 값이 0이 아니므로, 문제 코드의 if문에서 nametag -> func이 호출되고, segmentation fault가 발생한다.

 

 결론적으로 동적 할당한 청크를 해제한 뒤에는 해제된 메모리 영역에 이전 객체의 데이터가 남는다.

  해당 특징을 공격자가 이용한다면 초기화되지 않은 메모리의 값을 읽어내거나, 새로운 객체가 악의적인 값을 사용하게 유도하여 프로그램의 정상적인 실행을 방해할 수 있다.

 

 

[ Exploit Tech: Use After Free ]

 

왼쪽부터 코드 시작

코드가 어마무시하게 길다,,

 

checksec으로 확인해보니 full relro 보호기법으로 인해 GOT를 덮어쓰는 공격은 어렵다. 이럴 때에는 라이브러리에 존재하는 훅 또는 코드에서 사용하는 함수 포인터를 덮는 방법을 생각해볼 수 있다고 한다.

 

 

코드를 찬찬히 보면 크기가 같은 Human과 Robot 구조체가 정의되어 있다.

 

Human_fun 함수와 robot_func 함수를 보면, 메모리 영역을 할당하고 해제할 때, 할당한 메모리 영역을 초기화하지 않고 있다. 따라서 한 구조체를 할당하고 다른 구조체를 할당하면 해제된 구조체의 값을 사용할 수 있는 uaf가 발생할 수 있다.

 

또한 robot_func 함수에서는 robot 변수의 fptr이 null이 아니면 이를 호출하고 있으므로, uaf로 해당 변수에 원하는 값을 남겨놓을 수 있다면, 실행 흐름을 조작할 수 있다.

 

 

또한 custom_func 함수를 사용하면 0x100 이상의 크기를 갖는 청크를 할당하고 해제할 수 있으며, 해당 함수에서도 메모리 영역을 초기화하지 않기 때문에 uaf가 발생할 수 있다.

 

 

→ 익스플로잇 설계

 - 목표: Robot.fptr의 값을 one_gadget의 주소로 덮어서 셸 획득

  → ld를 위해 libc가 매핑된 주소를 먼저 구해야 함

 

1. 라이브러리 릭

  - 코드에 있는 취약점은 uaf밖에 없으므로, 해당 취약점을 이용하여 libc가 매핑된 주소를 구해야 함. 이를 위해 unsorted bin의 특징을 이용한다.

unsorted bin에 처음 연결되는 청크는 libc의 특정 주소와 이중 원형 연결 리스트를 형성함

    즉, 처음 unsorted bin에 연결되는 청크의 fd와 bk에는 libc 내부의 주소가 쓰임. 따라서 unsorted bin에 연결된 청크를 재할당하고, fd나 bk의 값을 읽으면 libc가 매핑된 주소를 계산할 수 있음

 - 예제의 custom_func 함수는 0x100 바이트 이상의 크기를 갖는 청크를 할당하고, 할당된 청크들 중 원하는 청크를 해제할 수 있는 함수임. 0x410 이하의 크기를 갖는 청크는 tcache에 먼저 삽입되므로, 이보다 큰 청크를 해제해서 unsorted bin에 연결하고, 이를 재할당하여 값을 읽으면 libc가 매핑된 주소를 계산할 수 있을 것임

주의할 점!

    - 해제할 청크가 탑 청크와 맞닿으면 안 됨.

      : unsorted bin에 포함되는 청크와 탑 청크는 병합 대상이므로, 이 둘이 맞닿으면 청크가 병합됨.

       이를 피하려면 청크 두 개를 연속으로 할당하고, 처음 할당한 청크를 해제해야 함.

 

2. 함수 포인터 덮어쓰기

 - human과 robot은 같은 크기의 구조체이므로, human 구조체가 해제되고 robot 구조체가 할당되면, robot은 human 이 사용했던 영역을 재사용하게 됨

 - robot이 할당될 때, 사용할 메모리 영역을 초기화하지 않으므로 human에 입력한 값이 재사용됨

 - human 구조체의 age는 robot 구조체의 fptr와 위치가 같음. 따라서 human_func을 호출했을 때, age에 one_gadget 주소를 입력하고, 이어서 robot_func을 호출하면 fptr의 위치에 남아있는 one_gadget을 호출할 수 있음.

'Pwnable > 개념' 카테고리의 다른 글

[Pwnable] Dreamhack STAGE 10  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 8  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 8  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 7  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 6  (0) 2022.08.25