본문 바로가기

High Level Technique/System Hacking

Use After Free (UAF)

Use After Free (UAF)




개념


프로그래머가 코드를 작성시 실수를 하여 발생하는 것을 말한다. Heap 영역에 할당하여 사용 후에 free를 하면 해제가 되는 것이 맞지만 같은 크기로 다시 할당하는 경우 한번 할당된 영역을 다시 재사용 됨으로써 발생하는 것을 뜻한다.


브라우저 익스플로잇을 할 때 많이 사용된다.










설명






먼저 간단한 C 코드를 보면서 UAF가 어떻게 발생 되는지 알아보도록 하겠습니다.


test라는 구조체에는 int형 변수가 선언되어 있습니다. 그리고 main()에서는 p1, p2로 구조체 포인터로 선언이 되있습니다.


p1과 p2는 각각 동적할당으로 256만큼 할당하도록 되어있죠.


프로그램이 동작하는 순서를 보면, p1이 동적할당되고 초기화가 안된 num의 값을 가져옵니다. 그리고 p1을 이용해서 num에 4855라는 값을 넣고, 다시 한번 출력합니다. 그리고 p1을 해체하고 p2를 할당시킵니다.


그리고 p2에 대한 num 값을 가져옵니다.



이렇게 코드를 작성하고 실행을 하면 어떤 값이 출력이 될까요? 그저 간단히 생각해보면 초기화가 안되어있으니 쓰레기 값이 나올거 같고, 4855로 초기화 해줬으니 4855가 나올것이고 해제 했으니 사라지고, p2에서는 쓰레기 값이 나와야 하지 않을까? 이렇게 생각할 수 있습니다.



출력 결과를 살펴보도록 하겠습니다.





값을 확인해보니 신기한 부분이 있습니다. 바로 p2의 num 값인데요. p1에서 초기화 했던 값인 4855가 그대로 들어가져 있는 것을 알 수 있습니다.



왜 이러한 현상이 나타나는 것 일까요?





간략히 말하자면 한번 할당되었던 Heap의 영역을 재 할당 할 경우 같은 크기라면 그대로 다시 사용한다는 것이죠. 이러한 이유때문에 p2의 num값이 4855가 되는 것입니다.





이러한 과정을 통해서 UAF가 일어나는 취약한 프로그램에서 shell을 획득하는데, 실제로 어떻게 되는지 알아보도록 하겠습니다.


해당 소스코드는 pwnable.kr uaf 입니다.





main()을 확인하면 위와 같은 소스코드로 되있습니다.

c++에서는 객체를 생성하면 할당이 되므로 m, w는 Jack과 Jill로 할당이 되어있을 겁니다.



그리고 1, 2, 3을 입력받아서 처리하는 부분이 있습니다.


1은 할당 2는 값 넣기 3은 해제 입니다.



uaf 바이너리를 gdb로 열어서 먼저 main에 브레이크 포인트를 걸고 switch case 문이 위치한 곳을 확인합니다.








1을 입력해서 introduce로 이동할 것이기 때문에 main+265에 브레이크 포인트를 걸고 실행시키도록 하겠습니다.



해당위치로 이동했는데, rbx를 확인해 보면 give_shell이 나타난 것을 알 수 있습니다. 해당 주소를 이용해야 할 것 같습니다.




그리고 Jack, Jill이 할당이 되있을 테니 해당 값을 한번 찾아보겠습니다.



603028, 603078 Heap 영역에 할당되어 있는 것을 알 수 있습니다.




위 Heap 영역을 살펴 보니 Jack과 Jill이 들어있는 것을 알 수 있습니다.


give_shell의 주소인 0x401550도 확인 할 수 있습니다.




call rdx를 하는 부분이 있는데 어디로 넘어가는 지 확인하기 위해서 main+286에 브레이크 포인트를 걸고 들어갑니다.







해당 부분은 introduce()라는 것을 알 수 있었습니다. 애초에 소스코드상으로도 알 수 있었습니다.




2를 선택했을 경우에는 소스코드를 확인해보면 argv[1]의 값을 atoi를 이용해 int형으로 바꿔서 len에 저장합니다.

그리고 해당 길이만큼 동적할당해서 data 배열을 하나 만들고, read를 이용해서 argv[2]를 열어서 data, len만큼 읽습니다.



그러면 다음과 같은 과정을 처리한 후 input에 담겨있는 AAAA라는 것이 어디에 저장되는지 확인해 보죠.


3 -> 2 -> 1



할당을 한번만 했을 경우 0x603090에 0x61616161가 들어있는 것을 알 수 있습니다. 원래는 0x401550이 들어있었죠.



그러면 한번 더 할당을 해서 확인해 보겠습니다.


3 -> 2 -> 2 -> 1




0x401570의 값이 0x61616161로 변경된 것을 알 수 있습니다.



그러면 input에 give_shell 주소를 넣어서 실행하면 될 것 같습니다.


이때 give_shell의 주소를 넣을 때 주의해야 할 점이 있습니다.



rax의 값을 +8을 하고 있습니다.



rax의 값이 +8된 상태입니다. rax가 가리키고 있는 곳이 introduce라는 것을 알 수 있습니다.


0x401570이 give_shell인데 이 값이 +8을 해서 introduce로 넘어가게 됩니다.


그러면 0x401570 - 8의 값을 input에 넣고 실행시키면 쉘을 얻을 수 있을 것 같습니다.



이렇게 쉘을 얻어냈습니다.!!