How to RIP control in libc 2.36
TL;DR
위 문제를 libc 2.36의 exit
함수의 call rax
가젯을 이용하여 익스플로잇하는 과정이다.
Intro
때는 바야흐로 약 한 달 전.. POXX 2022 본선 문제 출제를 하던 중이었다. 해당 문제는 fsb와 aaw가 가능하지만 FULL RELRO가 걸려있어 GOT overwriting은 불가능 해서 일반적으로 malloc_hook
이나 free_hook
을 덮어서 익스해야 한다. 하지만 해당 문제에서의 libc 버전이 최신 버전(2.36) 이기 때문에 위와 같은 방법은 익스가 불가능하다. 왜냐하면 둘 다 사용을 안하는 것으로 패치되었기 때문이다. 해당 과정을 분석하게 된 계기는, printf에서의 malloc 호출과 exit에서의 free 호출을 살펴보면서 분석하게 되었다.
malloc_hook
과 free_hook
을 사용하지 않는 익스 방법이 있을까 분석을 진행하다가 exit
함수에서 call rax
라는 쓸만한 가젯을 보았고 해당 부분 트리거를 진행해보았다. 물론 여러가지의 시행착오가 있었지만 한 달 전의 일이라 기억이 가물하기도 해서 트리거에 성공한 과정만 간략하게 블로그에 작성해보겠다.
How to RIP control in libc 2.36
주목할 가젯은 loc_3AF9E
의 call rax
이고 rax
에 집중 및 동적 분석을 통해 최종적으로 분석할 블록은 loc_3AF70
이다.
1 | .text:000000000003AF70 |
$rax 값을 [rdx+18h]에서 가져와서, ROR 연산을 시도한다. 그 후 XOR 연산도 진행한다. 이러한 두 번의 연산을 거쳐서 결국 call rax
를 한다. 우리는 임의 주소 쓰기가 가능하기 때문에 rax를 조작할 수 있는 상황이다. 더 나아가서 생각해보면 [rdx+18h]을 직접 조작할 수 있고, 해당 값은 우리가 호출하고 싶은 주소가 signing 된 값임을 알 수 있다.
역연산을 통해 signing 된 값이 무엇인지 수식으로 작성해보면 아래와 같다.
1 | ROL = lambda val, r_bits, max_bits: \ |
signing 된 값을 구하기 위해 key를 알아야 한다. 0x3AF89에 break point를 걸고 rax 레지스터를 0으로 세팅하면 아래의 식에 의해 fs:30h
값을 구할 수 있다. 이 값이 key이다.
1 | 0 ^ A = A |
그렇게 구한 fs:30h
을 메모리 상에서 검색해보면 아래와 같다.
1 | gef➤ search-pattern 0x7801dafd8bba0173 |
stack에 이미 해당 값이 존재하고 있었고, 해당 문제에서 fsb 터지는 주소가 더 높은 곳에 위치했기 때문에 fs:30h
을 leak할 수 있다.
다만 key 값이 한 바이트 밀려 있어서(?) 조금의 전처리 과정이 필요하다.
1 | 0x7fff23c14cf0: 0x01dafd8bba0173f5 0x0034365f36387878 |
leak1 = 0x01dafd8bba0173f5
leak2 = 0x0034365f36387878
leak1에서 맨 마지막 바이트를 자르고 leak2에서 맨 마지막 바이트를 가져와 맨 앞에 붙여줘야 한다.
1 | leak1 >> 8 = 0x1dafd8bba0173 |
최종적으로 아래와 같은 수식을 통해 key 값을 구할 수 있다.
1 | key = (leak1 >> 8) + ((leak2 & 0xff) << 8*7) |
결론적으로 우리는 key를 구했으므로 gift 함수가 signing 된 값을 구하고 스택에 값을 쓸 수 있다. 이 값은 exit 함수의 로직 중 인증이 되어 최종적으로 call 할 수 있다.