Scripts
Points 0
Solves 0
Scripting != Coding (or is it?)
In this challenge, we are given a binary that validates flag input through a custom scripting system. The binary implements a Lua-like environment with defined operations to check whether our input matches the expected flag.
Examining the main checking function, we can see that the program reads our input, initializes a scripting context, and runs a validation script:
__int64 __fastcall sub_402056(__int64 a1, int a2, int a3, int a4, int a5, int a6)
{
int v6; // edx
int v7; // ecx
int v8; // r8d
int v9; // r9d
__int64 result; // rax
int i; // [rsp+Ch] [rbp-44h]
int j; // [rsp+10h] [rbp-40h]
__int64 v14; // [rsp+18h] [rbp-38h]
_BYTE v15[40]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 v16; // [rsp+48h] [rbp-8h]
v16 = __readfsqword(0x28u);
sub_44C870((unsigned int)"Flag: ", a2, a3, a4, a5, a6);
sub_44C7A0((unsigned int)"%s", (unsigned int)v15, v6, v7, v8, v9);
if ( j_ifunc_46ECC0() == 33 )
{
for ( i = 0; i <= 1162; ++i )
{
if ( byte_54CC00[i] == 42 )
{
for ( j = 0; j <= 32; ++j )
byte_54CC00[i + j] = v15[j];
break;
}
}
v14 = sub_41C650(v15);
if ( v14 )
{
sub_41C730(v14);
sub_403B70(v14, sub_401F24, 0);
sub_404380(v14, &byte_54CBA0);
sub_403B70(v14, sub_401F8A, 0);
sub_404380(v14, &byte_54CBC0);
sub_403B70(v14, sub_401FF0, 0);
sub_404380(v14, &byte_54CBE0);
if ( (unsigned int)sub_41B6F0(v14, byte_54CC00) || (unsigned int)sub_404A40(v14, 0, 0xFFFFFFFFLL, 0, 0, 0) )
{
sub_411090(v14);
result = 1;
}
else
{
sub_411090(v14);
result = 0;
}
}
else
{
result = 1;
}
}
else
{
sub_45C920("WRONG");
result = 1;
}
if ( v16 != __readfsqword(0x28u) )
sub_492140();
return result;
}
The main validation logic consists of:
- Prompting the user for a flag input
- Checking that the input is exactly 33 characters long
- Inserting the user input into a script at a position marked by an asterisk (
*
) - Initializing a scripting context and registering custom operations
- Running the validation script and checking if the result is correct
Looking at the initialization function reveals that the program performs XOR decoding of the script and operation names:
void *sub_402277()
{
void *result; // rax
int i; // [rsp+0h] [rbp-30h]
int j; // [rsp+4h] [rbp-2Ch]
int k; // [rsp+8h] [rbp-28h]
int m; // [rsp+Ch] [rbp-24h]
for ( i = 0; i <= 1162; ++i )
byte_54CC00[i] = byte_4E9040[i] ^ 0xA0;
byte_54D08B = 0;
for ( j = 0; j <= 4; ++j )
byte_54CBA0[j] = byte_4E94CC[j] ^ 0xA0;
byte_54CBA5 = 0;
for ( k = 0; k <= 4; ++k )
byte_54CBC0[k] = byte_4E94D2[k] ^ 0xA0;
byte_54CBC5 = 0;
result = byte_4E94D8;
for ( m = 0; m <= 4; ++m )
{
result = (void *)m;
byte_54CBE0[m] = byte_4E94D8[m] ^ 0xA0;
}
byte_54CBE5 = 0;
return result;
}
The program XORs each byte with 0xA0
to decrypt the script and the names of three operations that will be used during validation.
After decoding byte_4E9040
, we can see the Lua script responsible for flag validation:
ops = {"j3s5l", "j3s5l", "m9kp2", "qwx7z", "qwx7z", "m9kp2", "j3s5l", "j3s5l", "qwx7z", "j3s5l", "j3s5l", "qwx7z", "m9kp2", "j3s5l", "qwx7z", "j3s5l", "m9kp2", "j3s5l", "j3s5l", "m9kp2", "m9kp2", "qwx7z", "j3s5l", "m9kp2", "j3s5l", "m9kp2", "m9kp2", "j3s5l", "m9kp2", "qwx7z", "qwx7z", "qwx7z", "qwx7z"}
k = {143, 193, 38, 93, 97, 13, 149, 22, 102, 163, 38, 84, 55, 157, 130, 12, 65, 133, 194, 3, 9, 162, 198, 41, 77, 20, 55, 76, 17, 192, 207, 104, 163}
pt = "*********************************"
ct = {200, 132, 39, 158, 180, 71, 220, 93, 151, 155, 93, 185, 67, 194, 245, 111, 49, 236, 178, 113, 96, 272, 161, 54, 33, 77, 55, 43, 100, 289, 310, 205, 288}
for i = 1, #pt do
local op_name = ops[i]
local key_val = k[i]
local char_code = string.byte(pt, i)
local result = 0
if op_name == "qwx7z" then
result = qwx7z(char_code, key_val)
elseif op_name == "m9kp2" then
result = m9kp2(char_code, key_val)
elseif op_name == "j3s5l" then
result = j3s5l(char_code, key_val)
end
if result ~= ct[i] then
print("WRONG") os.exit(1)
end
end
print("CORRECT")
The script shows:
- An array of operation names (
ops
) to be applied to each character - A key array (
k
) with values to be used in each operation - The user input (
pt
) which is initially filled with asterisks but gets replaced with our input - A target ciphertext array (
ct
) containing the expected results
For each character in our input, the script:
- Gets the corresponding operation name from the
ops
array - Gets the corresponding key value from the
k
array - Applies the operation to our input character using the key value
- Compares the result with the expected value in the
ct
array - Exits with “WRONG” if any comparison fails
By examining the code that registers these operations with the Lua environment:
sub_41C730(v14);
sub_403B70(v14, sub_401F24, 0);
sub_404380(v14, &byte_54CBA0);
sub_403B70(v14, sub_401F8A, 0);
sub_404380(v14, &byte_54CBC0);
sub_403B70(v14, sub_401FF0, 0);
sub_404380(v14, &byte_54CBE0);
We can determine the three operations:
qwx7z(a, b)
: Performs subtractiona - b
m9kp2(a, b)
: Performs additiona + b
j3s5l(a, b)
: Performs bitwise XORa ^ b
To find the correct flag, we need to reverse the operations. For each position, we can determine the original character by applying the inverse of the operation:
ops = [ 'j3s5l', 'j3s5l', 'm9kp2', 'qwx7z', 'qwx7z', 'm9kp2', 'j3s5l', 'j3s5l', 'qwx7z', 'j3s5l', 'j3s5l', 'qwx7z', 'm9kp2', 'j3s5l', 'qwx7z', 'j3s5l', 'm9kp2', 'j3s5l', 'j3s5l', 'm9kp2', 'm9kp2', 'qwx7z', 'j3s5l', 'm9kp2', 'j3s5l', 'm9kp2', 'm9kp2', 'j3s5l', 'm9kp2', 'qwx7z', 'qwx7z', 'qwx7z', 'qwx7z' ]
k = [ 143, 193, 38, 93, 97, 13, 149, 22, 102, 163, 38, 84, 55, 157, 130, 12, 65, 133, 194, 3, 9, 162, 198, 41, 77, 20, 55, 76, 17, 192, 207, 104, 163 ]
ct = [ 200, 132, 39, 158, 180, 71, 220, 93, 151, 155, 93, 185, 67, 194, 245, 111, 49, 236, 178, 113, 96, 272, 161, 54, 33, 77, 55, 43, 100, 289, 310, 205, 288 ]
out = []
for o, ki, ci in zip(ops, k, ct):
out.append(chr(ci - ki if o == 'qwx7z' else ci + ki if o == 'm9kp2' else ci ^ ki))
print(''.join(out))
GEMASTIK18{ez_scripting_language}