Flare On 2014 - Challenge 5 Writeup
Everybody, protect your keystrokes!
Detect it easy
revealed that the file for the fifth challenge is a C/C++ library (DLL) file:
I started analyzing the file using cutter
and the Ghidra
decompiler.
In the beginning, the "malware" copies itself to C:\Windows\System32\svchost.dll
.
This is happening in fcn.1000a680()
whichgets called by fcn.1000b090()
(which is called in entry0
).
And then creates a Registry entry:
This behavior is pretty common for malicious software; after digging deeper I found a call to GetAsyncKeyState
which can be used for keylogging.
The method called fcn.1000a4c0()
shown in the second screenshot calls a method which then calls the keylogging method:
The keylogging function appears to be big but follows a rather simple structure. First, there are a lot of if-statements checking which key was pressed by the user.
0iVar1 = (*_GetAsyncKeyState)((int32_t)(int16_t)vKey);
1 if (iVar1 == -0x7fff) {
2 if ((int16_t)vKey == 0x27) {
3 uVar2 = fcn.100093b0();
4 return uVar2;
5 }
6 if ((int16_t)vKey == 0x28) {
7 uVar2 = fcn.100093c0();
8 return uVar2;
9 }
10 if ((int16_t)vKey == 0x29) {
11 uVar2 = fcn.100093d0();
12 return uVar2;
13 }
14 if ((int16_t)vKey == 0x2a) {
15 uVar2 = fcn.100093e0();
16 return uVar2;
17 }
18[...]
I began renaming the functions and also checked why ones simply "did nothing" (or in this reset the progress):
The functions marked with "USELESS" lead to a "reset function" being called:
0void reset_progress(void)
1{
2 *(undefined4 *)0x10017000 = 1;
3 *(undefined4 *)0x10019460 = 0;
4 *(undefined4 *)0x10019464 = 0;
5 *(undefined4 *)0x10019468 = 0;
6 *(undefined4 *)0x1001946c = 0;
7 *(undefined4 *)0x10019470 = 0;
8 *(undefined4 *)0x10019474 = 0;
9 *(undefined4 *)0x10019478 = 0;
10 *(undefined4 *)0x1001947c = 0;
11 *(undefined4 *)0x10019480 = 0;
12 *(undefined4 *)0x10019484 = 0;
13 *(undefined4 *)0x10019488 = 0;
14 *(undefined4 *)0x1001948c = 0;
15[...]
Reversing The Logging
I picked a random function, that was not marked as useless. It doesn't matter with which one you start - I picked letter_I()
for now:
0undefined4 letter_I(void)
1{
2 if (*(int32_t *)0x1001946c < 1) {
3 reset_progress();
4 } else {
5 *(int32_t *)0x1001946c = 0;
6 *(undefined4 *)0x10019470 = 1;
7 }
8 return 0x100142c4;
9}
Next, I used the Cross-Reference function to check where 0x1001946c
is set to 1
:
letter_G()
does that, so I moved on to that function:
0undefined4 letter_G(void)
1{
2 if (*(int32_t *)0x10019464 < 1) {
3 if (*(int32_t *)0x10019468 < 1) {
4 if (*(int32_t *)0x10019474 < 1) {
5 reset_progress();
6 } else {
7 *(int32_t *)0x10019474 = 0;
8 *(undefined4 *)0x10019478 = 1;
9 }
10 } else {
11 *(int32_t *)0x10019468 = 0;
12 *(undefined4 *)0x1001946c = 1;
13 }
14 } else {
15 *(int32_t *)0x10019464 = 0;
16 *(int32_t *)0x10019468 = 1;
17 }
18 return 0x100142bc;
19}
Since I want 0x1001946c
set to 1
we have to move the code execution to the second else-block. To do so 0x10019468
has to be 1
.
From now on I repeated the progress => cross-referencing the global variable in the if-check and looking for the function that sets it to 1
.
Eventually, I arrived at letter_L()
where 0x10017000
is checked for being less than 1
. Said variable gets set to 1
by the reset function so I assumed that this is the starting point.
letter_L()
can set two global variables to 1
depending on the results of the nested if-statements.
Since the outer check will fail I continue to cross-reference 0x10019460
and looked for a cmp
instruction:
In the number_0
function 0x10019460
will be set to 0
and 0x10019464
to 1
, so I continued by cross-referencing the latter.
Repeating that technique I eventually arrived at letter_M()
which "terminates" the logging sequence:
0undefined4 letter_M(void)
1{
2 if (0 < *(int32_t *)0x100194fc) {
3 reset_progress();
4 fcn.10001240();
5 }
6 return 0x100142d4;
7}
Reversing the algorithm lead to the following string:
l0gging dot ur dot 5tr0ke5 at flare dash on dot com
Which can be "translated" to:
Challenge 4