Dockerfile에서 libc를 추출해서 patch한 다음에 문제를 풀어야 한다. 참고로 최신 libc이다.
Analysis
Mitigation
1 2 3 4 5
Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
No canary found라고 나오지만 canary가 존재한다.
FULL RELRO라서 GOT overwriting이 불가능하다. 그래서 보통 malloc_hook, free_hook, _rtld_global._dl_rtld_lock_recursive 등을 덮어 실행 흐름을 조작할 수 있다. 하지만 문제에서 최신 libc를 사용하기 때문에 malloc_hook과 free_hook 등을 덮는 것은 불가능하고 덮을 다른 곳을 찾아봐야 한다.
if ( choice == 1 ) { puts("What do you want?"); printf(">> "); read(0, buf, 0xFuLL); if ( !strchr((constchar *)buf, 'n') ) printf((constchar *)buf); // fsb } elseif ( choice == 2 ) { puts("Would you like an exchange? What about?"); printf(">> "); read(0, nptr, 0xFuLL); v7 = (_QWORD *)strtoul(nptr, 0LL, 10); puts("I can exchange it for another for you. What do you want?"); printf(">> "); read(0, nptr, 0x14uLL); v8 = strtoul(nptr, 0LL, 10); *v7 = v8; // aaw }
먼저 1번 메뉴를 살펴보자. buf에 입력을 받고 printf로 출력을 해준다. 이 때 fsb가 발생하지만 ‘n’을 필터링하고 있기 때문에 스택 주소 leak만 가능하다.
2번 메뉴에서는 *v7 = v8; 이 구문을 통해 원하는 주소에 원하는 값을 쓸 수 있다. 다시 말해서 임의 주소 쓰기가 가능하다.
이때 strtoul을 이용해서 정수로 구성된 str을 unsigned long으로 바꿔준다.
v7에 0x7ffcea69ddf0를 넣고 싶다면? read에서 nptr에 해당 값을 입력해야한다. 그럼 strtoul 함수의 인자는 아래와 같이 들어가게 된다.
이 값은 strtoul 함수에 의해 문자열(예를 들면 140737488355327)이 unsigned long으로 바뀌게 되므로, 입력하는 글자 수를 len('140737488355327')=15로 제한하게 되었다.
또한 v8을 위한 nptr 입력도 0x14로 특별히 제한한 이유가 있다. 이 글의 언인텐이 아닌 다음 글의 언인텐을 위해서다. (출제할 때부터 인지하고 있었던 언인텐이고 일부러 입력 크기를 0x14로 두고 언인텐을 방지하지 않았다.)
이에 관해서는 다음 글에서 설명하도록 한다.
Vunlerability
아무리 봐도 bof는 터지지 않는다. 임의 주소 쓰기와 스택 주소 leak을 이용하여 Exploit을 진행해보자.
스택에 있는 주소를 모두 출력할 수 있다는 것은 강력한 취약점이다. pie base, libc base, stack address 등을 바로 구할 수 있기 때문이다.
Exploit
인텐과 언인텐 하나씩 소개한다. 인텐은 스택 ret를 gift 함수로 덮는 것이다. 이 문제는 예선 때 냈던 문제를 살짝 바꿔서 낸 문젠데 예선 때 풀어주신 분들이 모두 똑같은 방식(언인텐)으로 푸셔서 그 부분을 패치해서 냈다.
목표는 1. 패치를 통해 예선 때의 언인텐 풀이가 불가능 한 것, 2. 포너블 올클 방지 였다. 예선 때의 언인텐은 main 함수가 return 0으로 종료되어서 스택 ret를 gift함수 주소로 바꿔서 풀이하는 것이다. 이것을 방지하기 위해 main이 종료하는 부분을 exit(0)으로 변경하였다. 본선 문제를 풀이할 때 read 함수의 ret를 덮지 않고 풀이를 했으나 해당 방식은 익스하는데 오랜 시간이 걸려서 다른 익스 방식을 탐색하였다. 결국 예선 언인텐과 비슷한 방식의 풀이를 본선 인텐으로 채택하였다. 재밌는 것은 대회 끝나고 해당 문제를 풀이하려고 하신 분의 익스 코드를 살펴봤는데 이 방법이 아니라 다른 방법으로 시도하고 계셨다.. 이 방법도 해당 글에 소개하도록 하겠다.
Demon 팀으로써 CTF 문제를 출제한 것 중에 가장 공을 들였고 오래걸렸다. 최신 libc를 가진 문제는 많이 풀이를 안해봐서 두 번째 익스 방법은 몰랐다. 새로운 것을 배워서 기분이 좋다 :) 이 문제 덕분에 최신 libc도 분석을 해보았는데 관련 내용은 Docs 카테고리에 (언젠간) 업뎃할 예정이다.