Rating:

given a PE32 exe file, not stripped, we have to reverse it to find the flag.

I've never worked much on windows stuff, except just a few couple times... so it kinda was hard for me to find out the calling conventions, working with ida well enough, etc..

so I used cutter with its ghidra decompiler and my intuition. :)

internal symbols were kept, they weren't stripped off, so I could easily find out which function is internal (written by author) and what's its name.

the code was very straightforward.

here is the main function:
```
undefined4 main(void)
{
int32_t arg_8h;
undefined4 uVar1;
undefined auStack80 [4];
int32_t var_48h;
undefined4 uStack68;
int32_t var_3ch;
int32_t var_38h;
undefined4 uStack52;
int32_t var_2ch;
int32_t var_28h;
undefined4 uStack36;
int32_t var_1ch;
undefined4 var_14h;
int32_t var_10h;
undefined4 var_ch;
int32_t var_4h;

___main();
arg_8h = operator new[](unsigned int)(0x13);
*(undefined *)(arg_8h + 0x11) = 0x31;
*(undefined *)(arg_8h + 0x12) = 0x31;

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "Enter first part: ");
var_10h = 0;
while (var_10h < 0x11) {
fcn.0047c7e0(0x48ba00, arg_8h + var_10h);
var_10h = var_10h + 1;
}
var_1ch._3_1_ = shodai(char*)(arg_8h);
*(undefined *)(arg_8h + 0x11) = 0x31;
*(undefined *)(arg_8h + 0x12) = 0x30;

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "Now second part: ");
var_10h = 0;
while (var_10h < 0x11) {
fcn.0047c7e0(0x48ba00, arg_8h + var_10h);
var_10h = var_10h + 1;
}
var_1ch._2_1_ = shodai(char*)(arg_8h);
*(undefined *)(arg_8h + 0x11) = 0x30;
*(undefined *)(arg_8h + 0x12) = 0x31;

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "Third: ");
var_10h = 0;
while (var_10h < 0x11) {
fcn.0047c7e0(0x48ba00, arg_8h + var_10h);
var_10h = var_10h + 1;
}
var_1ch._1_1_ = shodai(char*)(arg_8h);
*(undefined *)(arg_8h + 0x11) = 0x30;
*(undefined *)(arg_8h + 0x12) = 0x30;

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "And now whats left of it: ");
var_10h = 0;
while (var_10h < 0x11) {
fcn.0047c7e0(0x48ba00, arg_8h + var_10h);
var_10h = var_10h + 1;
}
var_1ch._0_1_ = shodai(char*)(arg_8h);
if ((((var_1ch._3_1_ == '\0') || (var_1ch._2_1_ == '\0')) || (var_1ch._1_1_ == '\0')) || ((char)var_1ch == '\0')) {
uVar1 =
std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "What?! I don\'t believe it!");

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
(uVar1, 10);
uStack68 = 2000;
std::chrono::duration<long long, std::ratio<1ll, 1000ll> >::duration<int, void>(int const&)((int32_t)&uStack68);

void std::this_thread::sleep_for<long long, std::ratio<1ll, 1000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000ll> > const&)
((int32_t)auStack80);
uVar1 =
std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "Just kidding...");

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
(uVar1, 10);
uStack52 = 2000;
std::chrono::duration<long long, std::ratio<1ll, 1000ll> >::duration<int, void>(int const&)((int32_t)&uStack52);

void std::this_thread::sleep_for<long long, std::ratio<1ll, 1000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000ll> > const&)
((int32_t)&var_3ch);
uVar1 =
std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "It\'s as I expected.");

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
(uVar1, 10);
uStack36 = 2000;
std::chrono::duration<long long, std::ratio<1ll, 1000ll> >::duration<int, void>(int const&)((int32_t)&uStack36);

void std::this_thread::sleep_for<long long, std::ratio<1ll, 1000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000ll> > const&)
((int32_t)&var_2ch);
} else {
uVar1 =
std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "Correct!");

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
(uVar1, 10);
}
if (arg_8h != 0) {
operator delete[](void*)(arg_8h);
}
_system("pause");
return 0;
}
```
arg_8 is probably a class or struct. it has 3 fields, one is `input`, and let's name the two other `bin_first` (arg_8 + 0x11) and `bin_second` (arg_8 + 0x12) which are characters and initialized before each time getting input with either '0' or '1'.

the `main` function reads into input from user for 4 times. before each getting the input, sets `bin_first` and `bin_second` and after that calls `shodai` and saves the result of each time in a variable, and then checks those 4 variables so none of them equal to 0. if none of them are 0, then our inputs were correct, which means our inputs each time are parts of the flag.

let's see what's happening in `shodai` function:
```
undefined __cdecl shodai(char*)(int32_t arg_8h)
{
uint8_t uVar1;
uint8_t uVar2;
char cVar3;
char cVar4;
uint8_t *puVar5;
uint32_t var_70h;
int32_t var_6ch;
int32_t var_68h;
int32_t var_54h;
int32_t var_50h;
int32_t var_4ch;
int32_t var_30h;
int32_t var_2ch;
int32_t var_24h;
int32_t var_20h;
int32_t var_1ch;
int32_t var_18h;
int32_t var_ch;

__Unwind_SjLj_Register();
std::allocator<char>::allocator()();

std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
();
convert_ASCII(std::string)((int32_t)&var_30h, (int32_t)&var_2ch);
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
std::allocator<char>::~allocator()();
cVar3 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x11), 0x31);
cVar4 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x12), 0x31);
if ((cVar3 == '0') && (cVar4 == '0')) {
var_1ch = 0;
var_20h = 0x10;
while (var_1ch < 8) {
uVar1 = *(uint8_t *)(arg_8h + var_1ch);
uVar2 = *(uint8_t *)(arg_8h + var_20h);
puVar5 = (uint8_t *)std::string::operator[](unsigned int)(var_1ch);
if ((uVar1 ^ uVar2) != *puVar5) {
var_70h._0_1_ = 0;
goto code_r0x00401ea6;
}
var_1ch = var_1ch + 1;
var_20h = var_20h + -1;
}
if (*(char *)(arg_8h + 8) == 'd') {
var_70h._0_1_ = 1;
} else {
var_70h._0_1_ = 0;
}
} else {
var_70h._0_1_ = sandaime(char*)(arg_8h);
}
code_r0x00401ea6:
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
__Unwind_SjLj_Unregister(&var_6ch);
return (undefined)var_70h;
}
```

the `XOR` function just returns either '0' or '1' the same way as how we would do `a ^ b` which return 1 or 0 as if `a` and `b` only could've been 1 or 0.

so from now on let's consider `bin_first` and `bin_second` represent a number in binary and let's name it `part_num`.

so... `shodai` function checks if `part_num` is 0, which is the same as what `main` function set for `part_num` for the first input (or flag part).

if the `part_num` do not equal 0, the else part, it calls another function called `sandaime` which does the same checking of `part_num` with another number and it wasn't equivalent, then calls another function and so on for each 4 possible value of `part_num`.

in the decompiled code, the argument to `convert_ASCII` isn't shown. it's a string of hex codes and `convert_ASCII` converts each couple of those hex numbers to their ascii character representation.
the argument to `convert_ASCII` in this `shodai` function is "630c251d0c3a194b". let's call result of `conver_ASCII` as `enc` since it's used in each function.

the `shodai` function walks through the input and xor each character in half first of the input with its mirror index (I'll explain in next line) in half the end of input and the result must equal to same character at index of `enc`. the middle character is checked later in code and must be equivalent to 'd'.

a mirror index (it should've an actual universal name, but I'm not aware of it) is equivalent to `(length - index)`.

the length is 17. and we know the middle is 'd'. and length of 'RaziCTF{' which we know each flag starts with it is 8 == 16/2. thus we can xor this starting string with each character of `enc` and then reverse the result.

the code to do that in python:
```
def crack_1(enc):
first = 'RaziCTF{'
last = ''

for i in range(len(first)):
last += chr(ord(first[i]) ^ ord(enc[i]))

return first + 'd' + last[::-1]
```

so now let's see what `sandaime` does:
```
undefined __cdecl sandaime(char*)(int32_t arg_8h)
{
char cVar1;
char cVar2;
int32_t arg_8h_00;
int32_t iVar3;
char *pcVar4;
int32_t var_70h;
int32_t var_6ch;
int32_t var_68h;
int32_t var_54h;
int32_t var_50h;
int32_t var_4ch;
int32_t var_38h;
int32_t var_34h;
int32_t var_2eh;
int32_t var_28h;
int32_t var_20h;
int32_t var_1ch;
int32_t var_18h;
int32_t var_ch;

__Unwind_SjLj_Register();
std::allocator<char>::allocator()();

std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
();
convert_ASCII(std::string)((int32_t)&var_38h, (int32_t)&var_34h);
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
std::allocator<char>::~allocator()();
cVar1 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x11), 0x31);
cVar2 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x12), 0x31);
if ((cVar1 == '1') && (cVar2 == '0')) {
var_1ch = 0;
while (var_1ch < 0x11) {
cVar1 = *(char *)(arg_8h + var_1ch);
arg_8h_00 = (int32_t)cVar1;
iVar3 =
wthIsThis(int (*)(int (*)(int (*)()), int (*)(int (*)(int**))), int (*)(int (*)(int (*)()), int*, int (*)(int (*)())))
(arg_8h_00, arg_8h_00 - 3);
cVar2 = (char)var_1ch;
if (arg_8h_00 == iVar3) {
code_r0x00401b32:
iVar3 =
wthIsThis(int (*)(int (*)(int (*)()), int (*)(int (*)(int**))), int (*)(int (*)(int (*)()), int*, int (*)(int (*)())))
(arg_8h_00, arg_8h_00 - 0x11);
if (arg_8h_00 == iVar3) {
pcVar4 = (char *)std::string::operator[](unsigned int)(var_1ch);
if (*pcVar4 != (char)(cVar1 + cVar2 + -0x10)) {
var_70h._0_1_ = 0;
goto code_r0x00401c8b;
}
} else {
iVar3 =
wthIsThis(int (*)(int (*)(int (*)()), int (*)(int (*)(int**))), int (*)(int (*)(int (*)()), int*, int (*)(int (*)())))
(arg_8h_00, arg_8h_00 + 6);
if (arg_8h_00 != iVar3) goto code_r0x00401bd3;
pcVar4 = (char *)std::string::operator[](unsigned int)(var_1ch);
if (*pcVar4 != (char)(cVar1 + (-0x10 - cVar2))) {
var_70h._0_1_ = 0;
goto code_r0x00401c8b;
}
}
} else {
iVar3 =
wthIsThis(int (*)(int (*)(int (*)()), int (*)(int (*)(int**))), int (*)(int (*)(int (*)()), int*, int (*)(int (*)())))
(arg_8h_00, arg_8h_00 + 10);
if (arg_8h_00 == iVar3) {
code_r0x00401bd3:
pcVar4 = (char *)std::string::operator[](unsigned int)(var_1ch);
if (*pcVar4 != (char)(cVar1 + ('\x10' - cVar2))) {
var_70h._0_1_ = 0;
goto code_r0x00401c8b;
}
} else {
iVar3 =
wthIsThis(int (*)(int (*)(int (*)()), int (*)(int (*)(int**))), int (*)(int (*)(int (*)()), int*, int (*)(int (*)())))
(arg_8h_00, arg_8h_00 - 8);
if (arg_8h_00 != iVar3) goto code_r0x00401b32;
pcVar4 = (char *)std::string::operator[](unsigned int)(var_1ch);
if (*pcVar4 != (char)(cVar1 + cVar2 + '\x12')) {
var_70h._0_1_ = 0;
goto code_r0x00401c8b;
}
}
}
var_1ch = var_1ch + 1;
}
var_70h._0_1_ = 1;
} else {
var_70h._0_1_ = yondaime(char*)(arg_8h);
}
code_r0x00401c8b:
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
__Unwind_SjLj_Unregister(&var_6ch);
return (undefined)var_70h;
}
```

well, it checks the 3rd input (or part of flag).

that `wthIsThis` function always returns the minimum of two given characters.

and looking its use, it's apparent that the code always ends up checking each character of the input like this: `(input[i] + (16 - i)) == enc[i]`

which can be reverse like this in python:
```
def crack_3(enc):
ret = ''

for i, ch in enumerate(enc):
ret += chr(ord(ch) + (16 - i))

return ret
```

next function is `yondaime` which is decompiled to this:
```
undefined __cdecl yondaime(char*)(int32_t arg_8h)
{
char cVar1;
char cVar2;
undefined4 uVar3;
char *pcVar4;
int32_t var_80h;
int32_t var_7ch;
int32_t var_78h;
int32_t var_64h;
int32_t var_60h;
int32_t var_5ch;
int32_t var_44h;
int32_t var_40h;
int32_t var_39h;
int32_t var_30h;
int32_t var_28h;
int32_t var_20h;
int32_t var_1ch;
int32_t var_18h;
int32_t var_ch;

__Unwind_SjLj_Register();
std::allocator<char>::allocator()();

std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
();
convert_ASCII(std::string)((int32_t)&var_44h, (int32_t)&var_40h);
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
std::allocator<char>::~allocator()();
cVar1 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x11), 0x31);
cVar2 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x12), 0x31);
if ((cVar1 == '1') && (cVar2 == '1')) {
uVar3 =
std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
(0x48b940, "Don\'t mind me, I\'m just here to waste time.");

std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
(uVar3, 10);
var_1ch = 0;
while (var_1ch < 0x11) {
var_30h = 10000;
std::chrono::duration<long long, std::ratio<1ll, 1000ll> >::duration<int, void>(int const&)
((int32_t)&var_30h);

void std::this_thread::sleep_for<long long, std::ratio<1ll, 1000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000ll> > const&)
((int32_t)&var_39h + 1);
cVar1 = *(char *)(arg_8h + var_1ch);
if (((int32_t)cVar1 & 1U) == 0) {
pcVar4 = (char *)std::string::operator[](unsigned int)(var_1ch);
if (*pcVar4 != (char)(cVar1 / '\x02' + '\n')) {
var_80h._0_1_ = 0;
goto code_r0x004018e7;
}
} else {
pcVar4 = (char *)std::string::operator[](unsigned int)(var_1ch);
if (*pcVar4 != (char)((char)((int32_t)((int32_t)cVar1 - 1U) / 2) + -10)) {
var_80h._0_1_ = 0;
goto code_r0x004018e7;
}
}
var_1ch = var_1ch + 1;
}
var_80h._0_1_ = 1;
} else {
var_80h._0_1_ = nidaime(char*)(arg_8h);
}
code_r0x004018e7:
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
__Unwind_SjLj_Unregister(&var_7ch);
return (undefined)var_80h;
}
```

and check the 4th input (or part of flag).

it checks each character for being even or odd. if even then it checks `(input[i] / 2 + 10) == enc[i]`, otherwise (if it's odd) then it checks `((input[i]+1) / 2 - 10) == enc[i]`.

I couldn't think of any formula to check the outcome againt these two branches and find out which is applied to it in order to reverse it that way. or in other words, I couldn't say from the output if a character was odd or even before passing any of those branches.

thus I reversed the `enc` of this function based on both branches and printed them upon each other to manually find out what would make sense, as shown in the following code:
```
def crack_4(enc):
odd_rev = lambda n: ((n + 10) * 2 + 1) & 0xff
even_rev = lambda n: ((n - 10) * 2) & 0xff

is_even = lambda n: (n % 2) != 0
printable = lambda n: chr(n) if 0x1f < n < 0x7f else ' '

evens = ''
odds = ''

for ch in enc:
ch = ord(ch)

evens += printable(even_rev(ch))
odds += printable(odd_rev(ch))

print(f'part 4 even rev: "{evens}"')
print(f'part 4 odd rev: "{odds}"')
```

the output is:
```
part 4 even rev: "Ff60&,R6 r 4t 0NT"
part 4 odd rev: "o _YOU{_C 3] 1Yw}"
```

and looking a bit sharp, one can easily find it out: `of_YOUR_Cr34t10N}`

let's go for the last function, `nidaime`, which corresponds for 2nd input (or part of the flag).

its decompiled code is as follow:
```
uint32_t __cdecl nidaime(char*)(int32_t arg_8h)
{
char cVar1;
char cVar2;
uint8_t *puVar3;
uint32_t var_70h;
int32_t var_6ch;
int32_t var_68h;
int32_t var_54h;
int32_t var_50h;
int32_t var_4ch;
int32_t var_34h;
int32_t var_30h;
int32_t var_2ah;
int32_t var_20h;
int32_t var_1ch;
int32_t var_18h;
int32_t var_ch;

__Unwind_SjLj_Register();
std::allocator<char>::allocator()();

std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
();
convert_ASCII(std::string)((int32_t)&var_34h, (int32_t)&var_30h);
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
std::allocator<char>::~allocator()();
cVar1 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x11), 0x31);
cVar2 = XOR(char, char)((int32_t)*(char *)(arg_8h + 0x12), 0x31);
if ((cVar1 == '0') && (cVar2 == '1')) {
var_1ch = 0;
while (var_1ch < 0x11) {
cVar1 = *(char *)(arg_8h + var_1ch);
puVar3 = (uint8_t *)std::string::operator[](unsigned int)(var_1ch);
if ((uint8_t)~(cVar1 + 0xdU) != *puVar3) {
var_70h = 0;
goto code_r0x00401626;
}
var_1ch = var_1ch + 1;
}
var_70h = 1;
} else {
var_70h = 0;
}
code_r0x00401626:
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()();
__Unwind_SjLj_Unregister(&var_6ch);
return var_70h;
}
```

it simple checks each character of input like this: `~(inp[i] + 13) == enc[i]`

to reverse it, we can use this python code:
```
def crack_2(enc):
ret = ''

for ch in enc:
ret += chr(((~ord(ch)) & 0xff) - 13)

return ret
```

so... now we've solved each part of flag checks, let's put the script together:
```
def crack_1(enc):
first = 'RaziCTF{'
last = ''

for i in range(len(first)):
last += chr(ord(first[i]) ^ ord(enc[i]))

return first + 'd' + last[::-1]

def crack_2(enc):
ret = ''

for ch in enc:
ret += chr(((~ord(ch)) & 0xff) - 13)

return ret

def crack_3(enc):
ret = ''

for i, ch in enumerate(enc):
ret += chr(ord(ch) + (16 - i))

return ret

def crack_4(enc):
# comment this to see differnt branch paths separated
return 'of_YOUR_Cr34t10N}' # I had to do it manually

odd_rev = lambda n: ((n + 10) * 2 + 1) & 0xff
even_rev = lambda n: ((n - 10) * 2) & 0xff

is_even = lambda n: (n % 2) != 0
printable = lambda n: chr(n) if 0x1f < n < 0x7f else ' '

evens = ''
odds = ''

for ch in enc:
ch = ord(ch)

evens += printable(even_rev(ch))
odds += printable(odd_rev(ch))

print(f'part 4 evens rev: "{evens}"')
print(f'part 4 odds rev: "{odds}"')

if __name__ == '__main__':
part1 = crack_1('\x63\x0c\x25\x1d\x0c\x3a\x19\x4b')
part2 = crack_2('\x7f\x7d\x84\x8e\xbf\xa0\x7f\x9e\xbe\xa4\x8e\x93\xbb\x8a\x89\x9f\x93')
part3 = crack_3('\x39\x44\x51\x41\x24\x69\x55\x6b\x40\x5e\x59\x6b\x4b\x74\x31\x71\x5f')
part4 = crack_4('\x2d\x3d\x25\x22\x1d\x20\x33\x25\x17\x43\x0f\x24\x44\x0e\x22\x31\x34')

print('part 1:', part1)
print('part 2:', part2)
print('part 3:', part3)
print('part 4:', part4)

print('flag:', part1 + part2 + part3 + part4)
```

and let's run it and get the flag: `RaziCTF{d0_nOt_m1sund3RsT4Nd_7hiS_IS_N0t_tHe_pOw3r_of_YOUR_Cr34t10N}`