Kategori
Rev
Beskrivning
Kenneth har börjat känna sig lite ensam och har försökt skapa en robot för att ha som sällskap och spela lite brädspel med. Men han fick inte riktigt till det, för roboten är ledsen hela tiden, oavsett vad han försöker ge den för input.
Harriet har kommit över mjukvaran till roboten, och försökt få den på gott humör. För om Kenneth har någon att lira Terraforming Mars med så kanske han tappar intresset för sin plan att ta över universum.
Men hon kan inte heller få roboten att dra på smilbanden. Kan du göra Kenneths robot glad?
Lösning
Öppnar vi den bifogade filen wake_me_up_inside_024fa20249d9c3fffa2582a582f995d9 i IDA, så får vi en stor binär med statiskt länkade funktioner från exemepelvis OpenSSL.
I funktionen start kan vi hitta originalfunktionen för main.
// positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn start(__int64 a1, __int64 a2, int a3)
{
__int64 v3; // rax
int v4; // esi
__int64 v5; // [rsp-8h] [rbp-8h] BYREF
_UNKNOWN *retaddr; // [rsp+0h] [rbp+0h] BYREF
v4 = v5;
v5 = v3;
sub_621890((unsigned int)sub_402EC0, v4, (unsigned int)&retaddr, 0, 0, a3, (__int64)&v5);
__halt();
}
Här ser vi att sub_402EC0 är originalfuktionen. Öppnar vi den så kan vi fortsätta vår analys.
__int64 __fastcall sub_402EC0(__int64 a1, int a2)
{
int v2; // edx
int v3; // ecx
int v4; // r8d
int v5; // r9d
__int64 result; // rax
int i; // [rsp+8h] [rbp-38h]
unsigned int v8; // [rsp+Ch] [rbp-34h]
_BYTE v9[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v10; // [rsp+38h] [rbp-8h]
v10 = __readfsqword(0x28u);
sub_402E0A();
sub_62B480((unsigned int)"> ", a2, v2, v3, v4, v5);
sub_63A020(v9, 27, off_844BD8);
sub_63ABD0(&unk_70A01E);
for ( i = 0; i <= 25; ++i )
{
v8 = sub_402CC5(&v9[i], 1) + dword_842140[i];
sub_689960(1);
sub_689990(v8);
}
sub_63ABD0(":)");
result = 0;
if ( v10 != __readfsqword(0x28u) )
sub_6925D0();
return result;
}
Nu kan vi köra programmet för att se vad som händer.
$ ./wake_me_up_inside_024fa20249d9c3fffa2582a582f995d9
> TEST
:(
Nu vet vi att sub_62B480 skriver ut “> “. sub_63A020 verkar vara en funktion som läser in input från användaren. sub_63ABD0 verkar vara en funktion som skriver ut text till användaren.
Vi kan inte enkelt se vad sub_402E0A och funktionerna i for-loopen gör. Vi kan börja med att analysera for-loopen där vår input används.
Debuggar vi programmet och kollar vad sub_402CC5 gör så ser vi att den tar en char, MD5-hashar den och sedan plockar ut delar av hashen.
__int64 __fastcall sub_402CC5(__int64 a1, unsigned int a2)
{
__int64 v2; // rax
__int64 v3; // rax
__int64 result; // rax
unsigned int v5; // [rsp+10h] [rbp-20h] BYREF
unsigned int v6; // [rsp+14h] [rbp-1Ch]
__int64 ctx; // [rsp+18h] [rbp-18h]
unsigned __int8 *v8; // [rsp+20h] [rbp-10h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]
v9 = __readfsqword(0x28u);
v2 = get_md();
v5 = EVP_MD_get_size(v2);
v6 = 0;
ctx = EVT_create_ctx();
v3 = get_md();
EVP_md_init(ctx, v3, 0);
EVP_DigestUpdate(ctx, a1, a2);
v8 = (unsigned __int8 *)malloc(v5, "wake_me_up_inside.c", 26);
EVP_DigestFinal_ex(ctx, v8, &v5);
sub_403CE0(ctx);
v6 |= v8[12] << 24;
v6 |= v8[13] << 16;
v6 |= v8[14] << 8;
v6 |= v8[15];
result = v6;
if ( v9 != __readfsqword(0x28u) )
stack_smashing_detected();
return result;
}
Raden som resultatet från funktionen sub_402CC5 används på läser även in en int från en array dword_842140 och tilldelar det till v8. Värdena i dword_842140 är:
.data:0000000000842140 dword_842140 dd 0B0DE2CB4h, 4CEC9C5Fh, 0E91F6E53h, 0B0DE2CB4h, 949A9B62h
.data:0000000000842140 ; DATA XREF: sub_402EC0+91↑o
.data:0000000000842154 dd 0CA9BBAFAh, 3 dup(6FB0CBA3h), 9688D99Fh, 0B319FFCEh
.data:000000000084216C dd 0D3CF0FCCh, 4CEC9C5Fh, 2 dup(6789B26h), 0F7BAE229h
.data:0000000000842180 dd 17BE13CEh, 0B319FFCEh, 6FB0CBA3h, 9688D99Fh, 0B319FFCEh
.data:0000000000842194 dd 4DFEC4CDh, 6789B26h, 0F7BAE229h, 0D58450Dh, 55FB6A31h
.data:00000000008421A8 dd 6 dup(0)
Nästa funktion, sub_689960, verkar vara en wrapper för sys_alarm.
unsigned __int64 __fastcall sub_689960(unsigned int a1)
{
unsigned __int64 result; // rax
result = sys_alarm(a1);
if ( result >= 0xFFFFFFFFFFFFF001LL )
{
__writefsdword(0xFFFFFFA8, -(int)result);
return -1;
}
return result;
}
sub_689990 som är nästa funktion gör en hel del andra intressanta saker.
__int64 __fastcall sub_689990(unsigned int a1)
{
unsigned int v1; // ebp
__int64 result; // rax
const struct timespec *v3; // [rsp+0h] [rbp-38h] BYREF
__int64 v4; // [rsp+8h] [rbp-30h]
unsigned __int64 v5; // [rsp+18h] [rbp-20h]
v5 = __readfsqword(0x28u);
v1 = __readfsdword(0xFFFFFFA8);
v3 = (const struct timespec *)a1;
v4 = 0;
if ( (int)sub_689A00((const struct timespec *)&v3, (struct timespec *)&v3) < 0 )
{
result = (unsigned int)v3;
}
else
{
__writefsdword(0xFFFFFFA8, v1);
result = 0;
}
if ( v5 != __readfsqword(0x28u) )
stack_smashing_detected();
return result;
}
Vilket i sin tur anropar sub_689A00.
__int64 __fastcall sub_689A00(const struct timespec *a1, struct timespec *a2)
{
__int64 result; // rax
result = sub_6E6FD0(0, 0, a1, a2);
if ( (_DWORD)result )
{
__writefsdword(0xFFFFFFA8, result);
return 0xFFFFFFFFLL;
}
return result;
}
Som i sin tur anropar sub_6E6FD0.
__int64 __fastcall sub_6E6FD0(
clockid_t clock_id,
unsigned int a2,
const struct timespec *timespec1,
struct timespec *timespec2)
{
unsigned int v4; // r13d
const struct timespec *v5; // r12
bool v7; // bl
int v8; // ebp
int v9; // r15d
unsigned int v10; // r15d
int v12; // r8d
__int64 v13; // rax
int v14; // r8d
_QWORD v15[2]; // [rsp+10h] [rbp-68h] BYREF
__m128i v16; // [rsp+20h] [rbp-58h] BYREF
unsigned __int64 v17; // [rsp+38h] [rbp-40h]
v17 = __readfsqword(40u);
if ( clock_id == 3 )
{
v10 = 22;
}
else
{
v4 = a2;
v5 = timespec1;
if ( clock_id == 2 )
{
v7 = 0;
clock_id = -6;
}
else
{
v7 = clock_id == 0;
}
if ( __readfsdword(24u) )
{
sub_6DAA40();
v8 = sys_clock_nanosleep(clock_id, a2, v5, timespec2);
sub_6DAAB0(v12);
}
else
{
v8 = sys_clock_nanosleep(clock_id, a2, timespec1, timespec2);
}
v9 = v8;
if ( v8 != -22 || !v7 )
goto LABEL_8;
v10 = 22;
if ( v5->tv_nsec <= 999999999uLL && v5->tv_sec >= 0 )
{
if ( (a2 & 1) == 0 )
{
if ( !__readfsdword(0x18u) )
{
LABEL_17:
v9 = sys_clock_nanosleep(1, v4, v5, timespec2);
LABEL_8:
v10 = -v9;
goto LABEL_9;
}
LABEL_23:
sub_6DAA40();
v9 = sys_clock_nanosleep(1, v4, v5, timespec2);
sub_6DAAB0(v14);
goto LABEL_8;
}
v16 = _mm_loadu_si128((const __m128i *)v5);
if ( !(unsigned int)sub_689400(0, v15) )
{
v13 = v16.m128i_i64[1] - v15[1];
v16.m128i_i64[1] = v13;
if ( v13 < 0 )
{
v16.m128i_i64[0] = v16.m128i_i64[0] - v15[0] - 1;
v16.m128i_i64[1] = v13 + 1000000000;
}
else
{
v16.m128i_i64[0] -= v15[0];
}
v5 = (const struct timespec *)&v16;
v4 = a2 & 0xFFFFFFFE;
if ( !__readfsdword(0x18u) )
goto LABEL_17;
goto LABEL_23;
}
}
}
LABEL_9:
if ( v17 != __readfsqword(0x28u) )
stack_smashing_detected();
return v10;
}
Utifrån detta kan vi se att programmet sover i lika många sekunder som resultatet av hashnings-funktionen returnerar, adderat med värdet från tabellen.
Detta betyder att vi behöver beräkna den modifierade MD5-hashen för varje bokstav. Är värdet av hashen adderat med värdet från tabellen noll, är det en korrekt bokstav.
Vi kan nu skriva ett skript som gör detta.
#!/usr/bin/env python3
import hashlib
import string
alphabet = string.printable
table = [0x0B0DE2CB4, 0x4CEC9C5F, 0x0E91F6E53, 0x0B0DE2CB4, 0x949A9B62, 0x0CA9BBAFA, 0x6FB0CBA3, 0x6FB0CBA3, 0x6FB0CBA3, 0x9688D99F, 0x0B319FFCE, 0x0D3CF0FCC,
0x4CEC9C5F, 0x6789B26, 0x6789B26, 0x0F7BAE229, 0x17BE13CE, 0x0B319FFCE, 0x6FB0CBA3, 0x9688D99F, 0x0B319FFCE, 0x4DFEC4CD, 0x6789B26, 0x0F7BAE229, 0x0D58450D, 0x55FB6A31]
def hash(s):
md5 = hashlib.md5(s)
digest = md5.digest()
v6 = 0
v6 |= digest[12] << 24
v6 |= digest[13] << 16
v6 |= digest[14] << 8
v6 |= digest[15]
return v6
flag = ''
for v in table:
for c in alphabet:
h = hash(c.encode())
if (h + v) & 0xFFFFFFFF == 0:
print(f"Found: {c}")
flag += c
break
print(f"Flag: {flag}")
Kör vi skriptet får vi flaggan undut{yyya_sn00ze_ya_l0z3}.