Stretch
Reverse-engineering writeup that decrypts embedded strings and recovers the expected Stretch license value.
Introduction
The given tasks gives us only one file:
- Stretching.exe
Chall Description:
Warm your reverse skills we still have a long journy. Flag format : wwf{The_Right_val}
This is a rev challenge so the flag has to be found reverse engineering the program. As the program is not stripped its not difficult to understand which are the important functions.
Solution
I have extracted and refactored the important parts of the main to understand the how this challenge can be takled. Firstly i discovered three encrypted strings (in the code below flag_file_path_enc, ok_msg_enc and wrong_msg_enc).
undefined8 wmain(void) {
[...]
flag_file_path_enc = {
-99, -0x69, -0x1e, -0x4d,
-0x68, -0x7f, -0x41, -0x36,
-0x70, -0x65, -0x7a, -0x67, '\0'
};
ok_msg_enc = {
-99, -0x3e, -0x34, -99,
-0x65, -0x72, -0x56, -0x73,
-0x28, -0x7d, -0x61, -0x76,
-1, -0x59, '\0'
};
wrong_msg_enc = {
-0x66, -0x28, -0x26, -0x76,
-0x22, -0x67, -0x54, -0x2c,
-0x62, -0x79, -0x61, -0x61,
-0x46, -0x38, -0x34, -0x1b, '\0'
};
FUN_14000166d(flag_file_path_enc);
pcStack_28 = flag_file_path_enc;
pFStack_30 = fopen(pcStack_28,"r");
if (pFStack_30 == (FILE *)0x0) {
puts("Error opening file! Error code: ");
exit(1);
}
memset(flag,0,0x31);
fgets((char *)flag,0x31,pFStack_30);
pFVar6 = pFStack_30;
fclose(pFStack_30);
flag_enc = { 0xb33a2d07, 0x8565d2ad, 0x6233d52e, 0xd4a4ac94, 0x3b592793, 0x2412ad1c };
key = { 0xb2594e64, 0x36580af5, 0xb41d6e56, 0x9048ace6, 0xb979379e };
bVar1 = FUN_1400017f3();
if (bVar1 == 0) {
for (i = 0; i < 4; i = i + 1) {
FUN_1400016ed(key,(uint)i);
}
for (j = 0; j < 0x30; j = j + 8) {
/* We need to reverse this function :) */
enc(key,flag + (int)(uint)j);
}
uVar7 = 0x29;
iVar3 = memcmp(flag,flag_enc,0x29);
if (iVar3 == 0) {
FUN_14000166d(ok_msg_enc);
print(ok_msg_enc,&DAT_140005029,uVar7,in_R9);
}
else {
FUN_14000166d(wrong_msg_enc);
print(wrong_msg_enc,&DAT_140005029,uVar7,in_R9);
}
return 0;
}
[...]
}
After looking at the decryption function FUN_14000166d i reimplemented it in python to get the strings meaning:
void FUN_14000166d(char *enc_st) {
byte key [7] = { 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 6};
int local_c;
for (local_c = 0; enc_st[local_c] != '\0'; local_c = local_c + 1) {
enc_st[local_c] = enc_st[local_c] ^ key[local_c % 6];
}
return;
}
and got this messages:
[FLAG FILE PATH] C:\\flag.txt
[CORRECT FLAG MSG] Correct flag!
[INCORRECT FLAG MSG] Dude try harder
After decoding this messages it was pretty easy to understand how the program works. Firstly i understand what each function is doing and then i got to the reverse of the enc function:
NOTE:
Looking at this function we can see that ONLY key[4] is used, this imply that we could skip the reversing of FUN_1400016ed. Sadly i did it and it only decrypts the first 4 uint32_t of the key array that gives us this secret
[KEY STRING] Free_Palestine!!
NOTE:
The binary as it is gives problem to the decompilation, the instruction at the address 140001591 has to be nopped to get a good decompilation of the function out of Ghidra/IDA.
void enc(uint32_t *key,byte *buf) {
right = (uint)buf[3] | (uint)*buf << 0x18 | (uint)buf[1] << 0x10 | (uint)buf[2] << 8;
left = (uint)buf[7] | (uint)buf[4] << 0x18 | (uint)buf[5] << 0x10 | (uint)buf[6] << 8;
uVar4 = key[4];
bVar1 = *(byte *)((longlong)key + 0x11);
bVar2 = *(byte *)((longlong)key + 0x12);
bVar3 = *(byte *)((longlong)key + 0x13);
j = 0;
idx = 0;
while (idx < 32) {
right = right + (j + key[j & 3] ^ (left << 4 ^ left >> 5) + left);
j = j + ((uint)bVar3 | (uint)(byte)uVar4 << 0x18 | (uint)bVar1 << 0x10 | (uint)bVar2 << 8);
if ((j == 0) || (j != 0)) {
uVar5 = right * 0x10;
}
else {
uVar5 = func_0x000101fc5b2c();
}
left = left + (j + key[j >> 0xb & 3] ^ (uVar5 ^ right >> 5) + right);
if ((left == 0) || (left != 0)) {
idx = idx + 1;
}
else {
func_0x000141f05b5d();
}
}
*buf = (byte)(right >> 0x18);
buf[1] = (byte)(right >> 0x10);
buf[2] = (byte)(right >> 8);
buf[3] = (byte)right;
buf[4] = (byte)(left >> 0x18);
buf[5] = (byte)(left >> 0x10);
buf[6] = (byte)(left >> 8);
buf[7] = (byte)left;
return;
}
Then i proceded to reverse this function coming up with this code:
NOTE:
The first loop is from the main function, the inner loop is the one inside the enc function.
def dec_flag(key: list[int], flag_enc: list[int]) -> str:
mask32 = 0xFFFFFFFF
C = key[4] & mask32
buff = flag_enc[:]
for off in range(0, len(buff), 8):
right = ( (buff[off+0] << 24) | (buff[off+1] << 16) | (buff[off+2] << 8) | buff[off+3] ) & mask32
left = ( (buff[off+4] << 24)| (buff[off+5] << 16)| (buff[off+6] << 8)| buff[off+7] ) & mask32
for i in range(31, -1, -1):
j = ((i + 1) * C) & mask32
k2 = key[(j >> 11) & 3]
F2 = ((j + k2) ^ (((right << 4) & mask32) ^ (right >> 5)) + right) & mask32
left = (left - F2) & mask32
j0 = (i * C) & mask32
k1 = key[j0 & 3]
F1 = ((j0 + k1) ^ (((left << 4) & mask32) ^ (left >> 5)) + left) & mask32
right = (right - F1) & mask32
buff[off+0] = (right >> 24) & 0xFF
buff[off+1] = (right >> 16) & 0xFF
buff[off+2] = (right >> 8) & 0xFF
buff[off+3] = (right ) & 0xFF
buff[off+4] = (left >> 24) & 0xFF
buff[off+5] = (left >> 16) & 0xFF
buff[off+6] = (left >> 8) & 0xFF
buff[off+7] = (left ) & 0xFF
return bytes(buff).decode('utf-8', errors='replace')