본문 바로가기
Pwnable

Return to dl resolve

by morae23 2019. 12. 28.

return to dl resolve는 lazy binding을 하는 상황에서 이용할 수 있는 기법이다.

이번 글에서는 codegate의 yocto라는 문제를 가지고 ret2dl 기법에 대해 소개하고 문제 풀이를 해보려고 한다.

 

# Lazy Binding이란
- Dynamic Linking 방식에서 외부 함수 호출시, 호출 시점에 해당 함수의 주소를 알아오는 것
- dynamic linker 내의 _dl_runtime_resolve()를 통해 이루어짐
- _dl_runtime_resolve() -> _dl_fixup() -> _dl_lookup_symbol_x() -> do_lookup_x() -> check_match()

 

Dynamic linking 방식에서 사용하는 방법이기 때문에, 우선 바이너리에 동적 링킹이 적용되었는지 확인해야 한다.

yocto 문제를 file 명령어로 확인해 보면 아래와 같이 dynamically linked인 것을 알 수 있다.

 

 

위의 Lazy Binding에 관한 설명을 보면, 외부 함수의 주소를 호출 시점에 알아온다고 적어 두었다.

readelf -s 를 이용하여 symbol table을 확인할 수 있다.

 

 

위의 사진을 보면, 이 바이너리에서 호출하는 함수들의 목록이 보이고, offset에 해당하는 value 필드는 0인 것을 볼 수 있다.

이제부터는 조금 더 자세히 분석을 할 것이기 때문에 setvbuf()를 기준으로 보려고 한다.

(물론 Dynamic Linking 방식을 설명하려는 것은 아니기 때문에 이 것에 대한 자세한 설명이나 분석보다는 ret2dl 기법에 필요한 부분들을 위주로 쓸 예정이다. 나중에 필요하다면 이 부분에 대해서는 별도의 글을 작성할 생각이다.)

 

해당 함수들이 실제로 존재하는 libc에 대해서도 동일하게 확인해보면, 이번에는 아래와 같이 offset 값이 들어있는 것을 볼 수 있다.

 

 

참고로 0x60360이라는 값이 실제 offset이 맞는지 확인하는 방법은 '더보기'를 누르면 확인할 수 있다.

더보기
해당 값을 확인하는 방법은 여러가지가 있지만, 이번에는 타겟 바이너리를 가지고 설명하려고 한다.

1. 우선 gdb yocto 를 입력한 뒤, gdb 안에서 start를 입력하여 바이너리를 실행한다.
2. 그 후, libc의 base 주소와 위에서 본 offset(0x60360)을 더한 위치에 setvbuf 함수가 있는지 확인하면 된다.

base address를 구하기 위해 vmmap을 이용한다.
(vmmap 대신 cat /proc/[pid]/maps를 이용해도 된다. pid는 ps -a를 통해 얻을 수 있다.)
여기서는 permission이 RX인 코드 영역에 해당하는 0xf7e17000이 libc의 base 주소이다.

그 후 base 주소에 offset 값을 더하면 0xf7e77360이 나온다.
gdb에서 p setvbuf를 통해 setvbuf의 주소를 확인해보면 동일한 것을 볼 수 있다.

 

 

 

 

여기서 주목할 점은 호출하고자 하는 외부함수의 주소(yocto 바이너리에는 없고, libc에만 있는 offset 값)를 어떻게 가져오는가이다.

 

ret2dl을 공부한다면, 아마 GOT overwrite 기법은 이미 알고 있을 것이라고 생각한다.

GOT와 PLT의 정확한 동작 방식에 대한 이해는 부족하더라도 GOT에 대해 들어보았을 것이고, 함수를 처음 호출했을 때와 1회 이상 호출했을 때 가지고 있는 값이 다르다는 것까지는 알고 있을 것이다.

이 때, 1회 이상 호출한 이후에 가지고 있는 값이 바로 해당 함수의 실제 주소라고 보면 된다.

 

 

GOT overwrite 기법은 말그대로 GOT를 덮어쓰는 것과 같다. ret2dl은 GOT table을 덮어쓰는 것이 아니라 GOT에 실제 함수의 주소가 담겨지는 방법을 이용하여 공격하는 기법이라고 할 수 있다.

 

쉽게 표현하기 위해 아래와 같은 그림을 그려보았다.

한번도 호출하지 않았을 때의 got(0x08049544)를 보면 0x080482d6으로 바로 아래의 plt코드 주소를 가지고 있는 것을 볼 수 있다.

 

 

그렇다면 이제 plt코드(0x080482d0) 부분을 계속 보면 된다.

가장 첫번째 줄은 GOT 테이블을 확인하여 실제 주소를 가지고 있다면 바로 실행하고, 아직 실제 주소를 알아오지 않았다면 plt 부분을 이어서 계속 실행한다는 것을 알 수 있었다.

두번째 줄인 push 0x10은 나중에 이 값이 필요하기 때문에 셋팅해준 것인데, 실행 흐름을 먼저 파악하기 위해 이 부분은 글의 후반부에서 설명할 것이다.

마지막 줄은 jmp 0x080482a0이다.

 

0x080482a0을 보면 아래와 같다.

jmp하는 곳을 확인하기 위해 0x08049538에 담긴 주소를 보면 _dl_runtime_resolve() 인것을 알 수 있다.

 

 

사실 여기서부터는 살짝 긴 과정을 거쳐야한다.

아래 코드는 _dl_runtime_resolve() 함수다. 여기서 이제 한 단계씩 따라가면서 분석을 하면 된다.

 

 

이 글에서 그 과정을 모두 기록하기는 어려울 것 같으니, 그 부분은 각자 해보면 좋을 것 같다.

(아래 그림의 가장 밑에 있는 trace 부분을 보면 call stack을 볼 수 있다. 이 것을 참고하여 분석하면 된다.)

 

결론을 말하면, 여기서 중요한 것은 strcmp() 함수이다.

외부함수의 실제 주소(offset)를 가져올 때 strcmp() 함수를 이용하여 구한다는 것이다.

다시 말하면, 외부 라이브러리에서 동일한 symbol을 가진 함수의 offset 값을 가져오는 것이다.

 

 

'문자열 비교'를 통해 함수의 주소를 알아온다는 사실은 상당히 흥미롭고 꽤나 취약하지 않을까 싶은 생각이 드는 부분이다.

이 공격 또한 그 부분을 활용한 것이다.

문자열을 비교하기 때문에, system이라는 문자열과 비교했다면 system 함수가 호출된다.

 

물론 system이라는 문자열만 넣으면 되는 것은 아니고, 몇몇 부분을 적절히 맞추어줄 필요가 있다.

그래서 전체적인 실행 흐름 파악을 위해 잠시 넘어갔던 push 0x10의 의미를 볼 것이다.

 

readelf에서 이번엔 -r 옵션을 이용해서 보면, relocation section을 볼 수 있다.

여기서 offset과 info라는 값을 볼 수 있다.

 

이 값들은 JMPREL의 base address에 reloc_offset을 더한 주소에서도 동일하게 확인할 수 있다.

위에서 push했던 0x10이 바로 reloc_offset이다.

 

 

저 값들이 무엇을 의미하는지 더 자세히 보려고 한다.

아래 코드와 같이 위 값들은 Elf32_Rel 구조체의 r_offset과 r_info에 해당하는 값이다.

r_offset은 .got.plt의 주소이고, r_info는 symbol index(3)과 relocation type(1)로 구성되어 있다.

 

.got.plt 주소의 경우 글의 초반부에 GOT, PLT에 대해 언급했던 부분을 다시 보면 0x08049544였다.

symbol index는 3인데, 이 것은 index 값으로 바로 위 그림에서 setvbuf 이외의 다른 함수들의 info 값을 보면, index 값이 1부터 5까지 차례로 증가하는 것을 확인할 수 있다. relocation type은 07로 되어 있는데, 여기서는 크게 신경쓰지 않아도 된다.

 

 

이제 push 0x10에 대한 의문은 풀렸지만, 한 가지 더 알아야할 부분이 있다.

우리의 목적은 setvbuf라는 문자열을 system으로 바꾸는 것이다.

그런데 아직 setvbuf라는 문자열이 어떻게 전달되는지를 보지 않았다.

이 부분은 위에서 본 symbol index와 관련이 있다.

 

Elf32_Rel 이외에도 Elf32_Sym이라는 구조체가 존재한다.

이 구조체 안에 함수이름에 대한 offset 값이 존재한다. (이 offset은 STRTAB으로부터의 offset이다.)

 

 

이 구조체가 존재하는 곳이 바로

SYMTAB + sizeof(Elf32_Sym) * symbol index 위치이다.

setvbuf의 경우에는 symbol index가 3이었기 때문에 0x0804818c + 16*3의 값을 확인해보면 아래와 같이 0x2c이다.

 

STRTAB에 0x2c를 더한 위치의 값을 보면 setvbuf라는 문자열이 들어 있는 것을 볼 수 있다.

 

 

여기까지 보면 이제 공격 시나리오를 떠올릴 수 있을 것이다.

우리가 호출하고자 하는 함수의 이름을 저장한 뒤, 위에서 본 두 개의 구조체를 만들어주고 각종 offset을 맞추어주면 쉘을 딸 수 있을 것이다.

 

원래 이 글에서 yocto 풀이까지 작성하려고 했으나, 생각보다 설명이 너무 길어져서

exploit을 작성하는 부분은 다음 글에서 쓰도록 하겠습니다!

'Pwnable' 카테고리의 다른 글

SROP (Sigreturn Oriented Programming  (0) 2020.02.09
Fake EBP  (0) 2019.12.24

댓글