안녕하세요. uaf_overwrite
워게임을 풀어보며 해당 풀이 강의를 보면서, 궁금증이 생겨서 질문을 남기게 되었습니다.
이번 문제에서 libc-2.27.so
라이브러리를 이용해야 하는데, 워게임을 다운받고 gdb를 켜서 vmmap
으로 파악해보면 libc-2.27.so
가 아닌 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
으로 라이브러리가 설정되어있습니다. libc-2.27.so
로 바꾸기 위해서는 LD_PRELOAD
를 설정해야 한다고 해서,
set environment LD_PRELOAD libc-2.27.so
를 입력해준 후 실행해봐도 아래와 같이 오류가 발생합니다.
LD_LIBRARY_PATH
도 추가로 설정해야 된다는 글을 보고, set environment LD_LIBRARY_PATH ./
로 해줬는데 방법이 잘못된건지 잘 되지 않습니다.. 혹시 이런 경우에 라이브러리를 설정해주는 방법을 알려주실 수 있으실까요 ?
이번 문제의 원가젯 조건은 아래와 같고, 소스 코드에서 if(robot->fptr)
을 검사하는 부분에서,
robot->fptr();
이 실행되기 때문에 여기에 중단점을 걸고 모든 조건을 확인해보았습니다.
gdb로 검사할 때는 robot->fptr
에 원가젯 주소를 넣은 상태로 확인해보지는 않아서 정확하지는 않을테지만,
이 상태에서는 0x4f432
인 원가젯만 가능하고, 0x4f3ce
와 4f3d5
는 나머지 조건은 만족하지만 rcx == NULL
이 만족안되고, 0x10a41c
는 [rsp + 0x70] == NULL
을 만족하지 않아서 안된다고 생각했는데, 실제로는 0x10a41c
만 가능했습니다.
혹시 writable
조건은 vmmap
에서 w
가 존재하는 영역에 속하는지 확인하면 되는게 맞고,
조건이 이렇게 3줄로 되있는건 각줄이 and
로 연결되어서 동시에 전부 만족해야 하는게 맞는건가요?
address rsp+0x50 is writable
rsp & 0xf == 0
rcx == NULL || {rcx, "-c", r12, NULL} is a valid argv
그리고, {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
이런 조건은 어떻게 판단해야 하는지 궁금합니다..
제가 위에서 원가젯의 조건을 판단하는 방법이 잘못된건가요 ? 만약 틀렸다면 원가젯의 조건을 정석적으로 판단하는 방법은 무엇인지 알려주실 수 있으실까요 ?
이번 문제에서 malloc
으로 할당된 메모리에 데이터를 입력할 때, fd
가 덮어씌워지게 되는데, 1bytes
의 값을 입력할 경우 fd
의 하위 1bytes
만 덮어씌워지게 됩니다.
그래서 만약 B
를 입력하면, fd
가 가리키는 libc
의 특정 영역과 베이스 사이의 미리 계산한 offset
의 하위 1bytes
를 0x42
로 바꿔주었는데, ASLR이 적용되어도 libc
의 하위 12bits
는 항상 0x000
이기 때문이 맞나요 ?
libc base
는 무조건 페이지내에서의 오프셋이 0
이어서 하위 12bits
가 항상 0x000
임이 보장되나요 ?
안녕하세요~!
일단 첫 번째 질문부터 답변 드리겠습니다.
해당 문제는 주어진 glibc 버전과 ld(loader)의 호환 문제로 보입니다.
해결책은 다음과 같이 두 가지가 있습니다(제가 아는한에서는...).
- Docker를 사용하여 주어진 glibc와 호환되는 로더를 사용
- patchelf를 와 주어진 glibc 버전과 호환되는 ld를 이용하여 바이너리가 해당하는 glibc와 로더를 이용할 수 있도록 패치
저는 전자를 더 추천합니다 참고로 해당 glibc는 ubuntu 18.04에서 preload 했을 때 동작합니다.
(아마 glibc 2.30으로 가면서 로더도 그에 따라 변경되면서 호환성 문제가 생긴것 같네요)
두 번째 질문에 대한 답변입니다.
- 맞습니다. memory map에서 w(write) 권한이 설정되어 있는 부분이 writable한 영역입니다.
- 저
&
는 비트 연산자로rsp
레지스터와0xf
를 AND 연산 했을 때 0이 나와야 성립합니다. rcx
레지스터가NULL
값이거나 뒤의 파라미터 ([rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88]
) 값이 유효한 값이어야 한다는 뜻입니다.
address rsp+0x50 is writable --- 1
rsp & 0xf == 0 --- 2
rcx == NULL || {rcx, "-c", r12, NULL} is a valid argv ---3
추가로 설명 드리자면 execve("/bin/sh", rsp+0x70, environ)
이 부분을 참고하시면 좋을 것 같습니다.
execve
함수는 2번째 인자로 문자열 포인터를 받는데 이 때 {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...}
로 표현된 이유는 파라미터로 줄 문자열 포인터가 2개라면 [rsp+0x70], [rsp+0x78]
까지 채워줘야 하기 때문에 이런식으로 표현해준 것 같습니다.
요점은 2번째 파라미터로 넘겨주는 부분은 rsp+0x70
부터 라고 생각하시면 될 것 같습니다.
0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
마지막 질문에 대한 답변입니다.
결론부터 말하자면 "넵 맞습니다" base 주소는 항상 하위 12비트는 항상 0입니다.
그 이유는 바이너리가 메모리에 적재될 때 페이지 단위로 적재 됩니다. 리눅스 운영체제에서 기본적인 페이지 단위는 4096 바이트로 이를 헥스값으로 표현하면 0x1000
이 됩니다 따라서 base 주소의 하위 12비트는 항상 0이 됩니다.
답변이 되었으면 좋겠네요!
PS) 첫 번째 적어주신 질문에서 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
는 라이브러리가 아닌 로더에 대한 경로입니다!