Hackvent 2019: Day 18
Challenge
HV19.18 Dance with me
Introduction
Santa had some fun and created todays present with a special dance. this is what he made up for you:
096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15
Dance with him to recover the flag.
Resource mirror: HV19-Day18-dance.zip
Solution
In our zip file we get a dance binary that we discover is an arm binary. After some digging around we find out that it is in fact a DEB and written for iOS. We attempt to run the code in an emulator like QEMU but unfortunately don't have much luck with getting the emulator to work. Instead we rely on static analysis.
We use IDA's ARM decompiler to give us the Objective C code belonging to the binary. As I didn't have the required IDA plugin I got this from BlindHero (thanks!).
This was the code:
#include <cstring>
#include <cstdio>
typedef long long int __int64;
typedef long int _DWORD;
typedef unsigned long int uint32;
typedef unsigned int uint;
typedef unsigned char _BYTE;
typedef unsigned char __int8;
template<class T> T __ROL__(T value, int count)
{
const uint nbits = sizeof(T) * 8;
if ( count > 0 )
{
count %= nbits;
T high = value >> (nbits - count);
if ( T(-1) < 0 ) // signed value
high &= ~((T(-1) << count));
value <<= count;
value |= high;
}
else
{
count = -count % nbits;
T low = value << (nbits - count);
value >>= count;
value |= low;
}
return value;
}
inline uint32 __ROR4__(uint32 value, int count) { return __ROL__((uint32)value, -count); }
__int64 dance_words(__int64 result, __int64 a2)
{
__int64 v2; // x8
int v3; // w6
int v4; // w7
int v5; // w8
int v6; // w14
int v7; // w16
int v8; // w17
int v9; // w3
int v10; // w5
int v11; // w12
int v12; // w13
int v13; // w2
int v14; // w15
int v15; // w4
int v16; // w19
signed int v17; // w11
int v18; // w9
int v19; // w10
int v20; // w17
int v21; // w5
int v22; // w7
int v23; // w14
int v24; // w3
int v25; // w6
int v26; // w8
int v27; // w16
int v28; // w19
int v29; // w10
int v30; // w13
int v31; // w2
int v32; // w9
int v33; // w12
int v34; // w15
int v35; // w4
__int64 v36; // x11
int v37; // [xsp+8h] [xbp-58h]
int v38; // [xsp+Ch] [xbp-54h]
int v39; // [xsp+10h] [xbp-50h]
int v40; // [xsp+14h] [xbp-4Ch]
int v41; // [xsp+18h] [xbp-48h]
int v42; // [xsp+1Ch] [xbp-44h]
int v43; // [xsp+20h] [xbp-40h]
int v44; // [xsp+24h] [xbp-3Ch]
int v45; // [xsp+28h] [xbp-38h]
int v46; // [xsp+2Ch] [xbp-34h]
int v47; // [xsp+30h] [xbp-30h]
int v48; // [xsp+34h] [xbp-2Ch]
int v49; // [xsp+38h] [xbp-28h]
int v50; // [xsp+3Ch] [xbp-24h]
int v51; // [xsp+40h] [xbp-20h]
int v52; // [xsp+44h] [xbp-1Ch]
v2 = 0LL;
do
{
*(&v37 + 4 * ((unsigned int)v2 >> 2) + (v2 & 3)) = *(_DWORD *)(a2 + 4 * v2);
++v2;
}
while ( v2 != 16 );
v4 = v49;
v3 = v50;
v6 = v37;
v5 = v38;
v8 = v41;
v7 = v42;
v10 = v45;
v9 = v46;
v12 = v43;
v11 = v44;
v13 = v47;
v14 = v48;
v16 = v51;
v15 = v52;
v17 = 10;
v19 = v39;
v18 = v40;
do
{
v20 = v8 ^ __ROR4__(v6 + v4, 25);
v21 = v10 ^ __ROR4__(v20 + v6, 23);
v22 = v4 ^ __ROR4__(v21 + v20, 19);
v23 = v6 ^ __ROR4__(v22 + v21, 14);
v24 = v9 ^ __ROR4__(v7 + v5, 25);
v25 = v3 ^ __ROR4__(v24 + v7, 23);
v26 = v5 ^ __ROR4__(v25 + v24, 19);
v27 = v7 ^ __ROR4__(v26 + v25, 14);
v28 = v16 ^ __ROR4__(v13 + v12, 25);
v29 = v19 ^ __ROR4__(v28 + v13, 23);
v30 = v12 ^ __ROR4__(v29 + v28, 19);
v31 = v13 ^ __ROR4__(v30 + v29, 14);
v32 = v18 ^ __ROR4__(v15 + v14, 25);
v33 = v11 ^ __ROR4__(v32 + v15, 23);
v34 = v14 ^ __ROR4__(v33 + v32, 19);
v35 = v15 ^ __ROR4__(v34 + v33, 14);
v5 = v26 ^ __ROR4__(v32 + v23, 25);
v19 = v29 ^ __ROR4__(v5 + v23, 23);
v18 = v32 ^ __ROR4__(v19 + v5, 19);
v6 = v23 ^ __ROR4__(v18 + v19, 14);
v12 = v30 ^ __ROR4__(v27 + v20, 25);
v11 = v33 ^ __ROR4__(v12 + v27, 23);
v8 = v20 ^ __ROR4__(v11 + v12, 19);
v7 = v27 ^ __ROR4__(v8 + v11, 14);
v14 = v34 ^ __ROR4__(v31 + v24, 25);
v10 = v21 ^ __ROR4__(v14 + v31, 23);
v9 = v24 ^ __ROR4__(v10 + v14, 19);
v13 = v31 ^ __ROR4__(v9 + v10, 14);
v4 = v22 ^ __ROR4__(v35 + v28, 25);
v3 = v25 ^ __ROR4__(v4 + v35, 23);
v16 = v28 ^ __ROR4__(v3 + v4, 19);
v15 = v35 ^ __ROR4__(v16 + v3, 14);
--v17;
}
while ( v17 );
v36 = 0LL;
v49 = v4;
v50 = v3;
v37 = v6;
v38 = v5;
v41 = v8;
v42 = v7;
v45 = v10;
v46 = v9;
v43 = v12;
v44 = v11;
v47 = v13;
v48 = v14;
v51 = v16;
v52 = v15;
v39 = v19;
v40 = v18;
do
{
*(_DWORD *)(result + 4 * v36) = *(_DWORD *)(a2 + 4 * v36) + *(&v37 + 4 * ((unsigned int)v36 >> 2) + (v36 & 3));
++v36;
}
while ( v36 != 16 );
return result;
}
__int64 dance_block(__int64 a1, int *a2, __int64 a3, __int64 a4)
{
__int64 v4; // x19
__int64 result; // x0
int v6; // w8
__int64 v7; // x9
char v8[64]; // [xsp+8h] [xbp-98h]
int v9; // [xsp+48h] [xbp-58h]
int v10; // [xsp+4Ch] [xbp-54h]
int v11; // [xsp+50h] [xbp-50h]
int v12; // [xsp+54h] [xbp-4Ch]
int v13; // [xsp+58h] [xbp-48h]
int v14; // [xsp+5Ch] [xbp-44h]
__int64 v15; // [xsp+60h] [xbp-40h]
__int64 v16; // [xsp+68h] [xbp-38h]
int v17; // [xsp+70h] [xbp-30h]
int v18; // [xsp+74h] [xbp-2Ch]
int v19; // [xsp+78h] [xbp-28h]
int v20; // [xsp+7Ch] [xbp-24h]
int v21; // [xsp+80h] [xbp-20h]
int v22; // [xsp+84h] [xbp-1Ch]
v4 = a1;
v9 = 1634760805;
v10 = *a2;
v11 = a2[1];
v12 = a2[2];
v13 = a2[3];
v14 = 857760878;
v15 = a3;
v16 = a4;
v17 = 2036477234;
v18 = a2[4];
v19 = a2[5];
v20 = a2[6];
v21 = a2[7];
v22 = 1797285236;
result = dance_words((__int64)v8, (__int64)&v9);
v6 = 0;
v7 = 0LL;
do
{
*(_BYTE *)(v4 + v7) = *(_DWORD *)&v8[4 * ((unsigned int)v7 >> 2)] >> (v6 & 0x18);
++v7;
v6 += 8;
}
while ( v7 != 64 );
return result;
}
__int64 dance(__int64 result, __int64 length, __int64 a3, __int64 a4)
{
__int64 v4; // x19
int *v5; // x20
__int64 v6; // x21
__int64 v7; // x22
__int64 v8; // x23
char v9[64]; // [xsp+8h] [xbp-88h]
if ( length )
{
v4 = a4;
v5 = (int *)a3;
v6 = length;
v7 = result;
v8 = 0LL;
do
{
if ( !(v8 & 0x3F) )
result = dance_block((__int64)v9, v5, v4, (unsigned int)v8 >> 6);
*(_BYTE *)(v7 + v8) ^= v9[v8 & 0x3F];
++v8;
}
while ( v6 != v8 );
}
return result;
}
unsigned char unk_100007F50[] = { 0x03, 0x20, 0x63, 0x46, 0x61, 0xB6, 0x3C, 0xAF, 0xAA, 0x76, 0xC2, 0x7E, 0xEA, 0x00, 0xB5, 0x98 };
unsigned char unk_100007F80[] = { 0xAD, 0xF1, 0x6C, 0x63, 0x45, 0x6A, 0x6E, 0xF1, 0xED, 0x90, 0x79, 0x46, 0x9D, 0xA2, 0xA0, 0xB5 };
unsigned char unk_100007F60[] = { 0xFB, 0x2F, 0x70, 0x97, 0x21, 0x4F, 0xD0, 0x4C, 0xB2, 0x57, 0xAC, 0x29, 0x04, 0xEF, 0xEE, 0x46 };
unsigned char unk_100007F70[] = { 0x79, 0x73, 0x0F, 0xF4, 0xEC, 0x0C, 0x40, 0x6B, 0xFD, 0x91, 0xC9, 0x1F, 0xE7, 0x04, 0x00, 0xA8 };
int main(int argc, const char **argv, const char **envp)
{
size_t input_length; // x19
size_t v4; // x21
int result; // w0
char v6; // [xsp+10h] [xbp-D0h]
unsigned char v7[32]; // [xsp+30h] [xbp-B0h]
__int128 v8; // [xsp+40h] [xbp-A0h]
__int128 v9; // [xsp+50h] [xbp-90h]
__int128 v10; // [xsp+60h] [xbp-80h]
unsigned char* v11; // [xsp+70h] [xbp-70h]
unsigned char* v12; // [xsp+80h] [xbp-60h]
unsigned char* v13; // [xsp+90h] [xbp-50h]
unsigned char* v14; // [xsp+A0h] [xbp-40h]
__int64 v15; // [xsp+B8h] [xbp-28h]
v13 = unk_100007F70;
v14 = unk_100007F80;
v11 = unk_100007F50;
v12 = unk_100007F60;
v9 = 0u;
v10 = 0u;
// v7 = 0u;
v8 = 0u;
printf("Input your flag: ", argv, envp);
fgets(&v6, 32, stdin);
input_length = strlen(&v6);
if ( input_length )
memcpy(&v7, &v6, input_length);
dance((__int64)&v7, input_length, (__int64)&v11, -5678246756302764783LL);
if ( strlen(&v6) )
{
v4 = 0LL;
do
printf("%02X", *((unsigned __int8 *)&v7 + v4++));
while ( strlen(&v6) > v4 );
}
result = putchar(10);
return result;
}
It takes a while to analyse this code to learn what cipher is actually being used.
Through many clues we learn its a stream cipher and the appropriately named Salsa20 cipher.
At this point its reasonable to assume we need to decrypt our encrypted flag 096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15
which is just hex data using the Salsa20 algorithm and some key and nonce. We are looking for a 128 bit or 256 bit key and 64 bit nonce. We can see four arguments passed to the dance method. The first is the result and the second is simply the input length used to check against loop guards. The last two arguments are our key and nonce. We take the longer length argument &v11
as our nonce.
We use IDA to easily fetch the key as raw data:
\x03\x20\x63\x46\x61\xB6\x3C\xAF\xAA\x76\xC2\x7E\xEA\x00\xB5\x9B\xFB\x2F\x70\x97\x21\x4F\xD0\x4C\xB2\x57\xAC\x29\x04\xEF\xEE\x46\x79\x73\x0F\xF4\xEC\x0C\x40\x6B\xFD\x91\xC9\x1F\xE7\x04\x00\xA8\xAD\xF1\x6C\x63\x45\x6A\x5E\xF1\xED\x9D\x79\x46\x9D\xA2\xA0\xB5
As it turns out only the upper (first) 32 bytes of this key are used.
Our nonce is the number -5678246756302764783
which we convert to hex as \xB1\x32\xD0\xA8\xE7\x8F\x45\x11
. However, we are using little endian ordering so we reverse the order of bytes to \x11\x45\x8F\xE7\xA8\xD0\x32\xB1
.
Finally we can write a python script to decode our Salsa20 encoded string. We simply use a template from online:
# Hackvent 2019 - Day 18
# Mo Beigi (https://mobeigi.com)
from Crypto.Cipher import Salsa20
key = b'\x03\x20\x63\x46\x61\xB6\x3C\xAF\xAA\x76\xC2\x7E\xEA\x00\xB5\x9B\xFB\x2F\x70\x97\x21\x4F\xD0\x4C\xB2\x57\xAC\x29\x04\xEF\xEE\x46'
nonce = b'\x11\x45\x8F\xE7\xA8\xD0\x32\xB1'
ciphertext = b'\x09\x6C\xD4\x46\xEB\xC8\xE0\x4D\x2F\xDE\x29\x9B\xE4\x4F\x32\x28\x63\xF7\xA3\x7C\x18\x76\x35\x54\xEE\xE4\xC9\x9C\x3F\xAD\x15'
cipher = Salsa20.new(key=key, nonce=nonce)
plaintext = cipher.decrypt(ciphertext)
print(plaintext)
Running this prints out our daily flag!
Flag:
HV19{Danc1ng_Salsa_in_ass3mbly}