Open riesha opened 1 year ago
Interesting, if you can share the binary I'm happy to take a closer look. Is the string decoded or contained in the file bytes?
Interesting, if you can share the binary I'm happy to take a closer look. Is the string decoded or contained in the file bytes?
Should be a decoded string. here's the bin!
FLARE FLOSS RESULTS (version 2.2.0)
+---------------------------------+------------------------------------------------------------------------------------+
| file path | /.../bin/Injector_[unknowncheats.me]_.exe_ |
| start date | 2023-01-31 12:47:59 |
| runtime | 00:20 |
| version | 2.2.0 |
| imagebase | 0x140000000 |
| min string length | 4 |
| extracted strings | |
| static strings | Disabled |
| stack strings | 3 |
| tight strings | 5 |
| decoded strings | 4 |
| analyzed functions | |
| discovered | 867 |
| library | 475 |
| stack strings | 331 |
| tight strings | 53 |
| decoded strings | 33 |
| identified decoding functions | 0x140004f90 (0.982), 0x14002da28 (0.981), 0x14000fa70 (0.967), 0x14001a140 |
| (offset and score) | (0.961), 0x140015eb4 (0.953), 0x140021e14 (0.953), 0x1400087e0 (0.950), |
| | 0x140016038 (0.941), 0x14001dfcc (0.938), 0x14002f0e8 (0.925), 0x14001fea0 |
| | (0.923), 0x14001522c (0.922), 0x1400153dc (0.922), 0x14002376c (0.920), |
| | 0x140010120 (0.914), 0x140014948 (0.914), 0x140016a34 (0.914), 0x140024014 |
| | (0.914), 0x140020eec (0.913), 0x1400152d4 (0.912), 0x140001160 (0.674), |
| | 0x140001a60 (0.613), 0x140002050 (0.561), 0x140006cb0 (0.752), 0x140008700 |
| | (0.557), 0x140008740 (0.557), 0x140008780 (0.557), 0x1400098c0 (0.656), |
| | 0x14000f35c (0.841), 0x1400151b0 (0.718), 0x140015360 (0.718), 0x140025f80 |
| | (0.588), 0x140029960 (0.587) |
+---------------------------------+------------------------------------------------------------------------------------+
---------------------------
| FLOSS STACK STRINGS (3) |
---------------------------
Function Function Offset Frame Offset String
----------- ----------------- -------------- --------
0x140007830 0x1400013c0 0x50 %+#.*Lf
0x140007dd0 0x1400013c0 0x70 %+#I64o
0x140007ef0 0x1400013c0 0x70 %+#I64o
---------------------------
| FLOSS TIGHT STRINGS (5) |
---------------------------
Function Function Offset Frame Offset String
----------- ----------------- -------------- -------------
0x140004f90 0x1400052c0 0x14b !"#$%&'(
0x140004f90 0x140005c80 0x73b !"#$%&'()*
0x140004f90 0x140006270 0xb1b !"#$%&'()
0x1400066d0 0x1400068f0 0x5b !"#$%&
0x1400066d0 0x140006c50 0x173 !"#$%&'()*+,
-----------------------------
| FLOSS DECODED STRINGS (4) |
-----------------------------
--------------------------------
| FUNCTION at 0x140004f90 (1) |
--------------------------------
Offset Called At String
------------------- ----------- ---------------------
(AddressType.STACK) 0x140006fe5 pe: invalid pe header
--------------------------------
| FUNCTION at 0x14002f0e8 (1) |
--------------------------------
Offset Called At String
------------------- ----------- ----------
(AddressType.STACK) 0x14002f4dc SystemRoot
--------------------------------
| FUNCTION at 0x140001160 (2) |
--------------------------------
Offset Called At String
------------------- ----------- --------
(AddressType.STACK) 0x14000696e Unknow
(AddressType.STACK) 0x14000696e \.\EIQDV
there are additional missed strings (unrelated to any encoding issues), such as here:
0x14000506E
encoding of the "Call of Duty(R) HD" string has 0xAE for (R)
related:
these strings should be handled by our tight strings implementation, but it doesn't work here. let me explain why:
this sample builds an encrypted (XOR) string on the stack and then decrypts it inline (tight string-style). it builds the encrypted string character by character, using a simple function (0x140002190) to encrypt the character:
char __fastcall enc_char(char character, char index)
{
return character ^ (index + 0x13);
}
building the string looks like:
Format[0] = enc_char('i', 0);
Format[1] = enc_char('n', 1);
Format[2] = enc_char('v', 2);
Format[3] = enc_char('a', 3);
Format[4] = enc_char('l', 4);
Format[5] = enc_char('i', 5);
Format[6] = enc_char('d', 6);
Format[7] = enc_char(' ', 7);
LOBYTE(v142) = enc_char('d', 8);
BYTE1(v142) = enc_char('l', 9);
and decryption:
for ( index = 0; index < 0xC; ++index )
{
character = &Format[index];
key = index + 0x13;
*character ^= key;
}
our tightstring handler correctly identifies the decryption loop and emulates to its start/end. but when it tries to diff memory and find strings it fails, unfortunately.
remember that the encrypted string is built by repeatedly invoking a function to encryption each character. our problem is that as we emulate the function to the start of the tightloop, we jump over calls for performance. so we skip the character encryption function. therefore, the encrypted character is not computed and therefore the encrypted string is not constructed correctly.
we'll need to brainstorm a way to handle this case. we can't just emulate into all calls due to performance and the likelihood that the emulator state will get corrupted. but maybe we can identify simple cases like this and do something special?
@MalwareMechanic @mr-tz
we could do some pre-analysis to determine reasonably small leaf functions that are called many times and allow-list them to be emulated during the path exploration. it would work for this sample but is ultimately a game of cat and mouse.
edit: this won't directly work, since the FullCoverageEmulatorDriver only emulates each basic block at most once, so subsequent calls to the character encryption routines would be skipped. not sure if there's anything we can do here without making the logic much more complex.
idea 2: we could re-implement SinglePathEmulatorDriver such that rather than using a lightweight search across all basic blocks (FullCovereEmulatorDriver) it precomputes the path through the target function and uses a DebuggerEmulatorDriver with breakpoints and flag manipulation to force the emulator to the target address. This way the driver could emulate into subroutines such as the character encryption function.
i've got to wonder why the author constructed the code like this. is it a FLOSS-bypass? the decrypted character is sitting right there in the disassembly listing:
and the decrypted string is available by hooking any API function. it just happens to not work in FLOSS.
(this is probably just my ego talking, im not sure many obfuscation authors would study FLOSS that carefully)
i've got to wonder why the author constructed the code like this. is it a FLOSS-bypass? the decrypted character is sitting right there in the disassembly listing:
and the decrypted string is available by hooking any API function. it just happens to not work in FLOSS.
(this is probably just my ego talking, im not sure many obfuscation authors would study FLOSS that carefully)
Nah highly doubt FLOSS was ever considered, in this game hacking context basic xor encryption is (mostly) just used to avoid having string references at all (that are somewhat frequently used by anti cheats for basic detections) and less for actual encryption anyways. I ran it through floss because i'm a file analyzer for a game hacking forum and we manually check everything that gets uploaded for malware before it's allowed to be downloaded. This is the first time i encounter a bug like this though!
thanks for reporting the issue! i'm not sure we'll be able to do anything about this right now, though if you notice this technique becomes more prevalent, we can revisit.
Soo i was analyzing a file with floss, and it completely skipped over this string
Call of Duty® HQ
, it decoded everything else flawlessly so i'm assuming it's an issue with the symbol? its not using a different encryption than the other successfully decoded strings and the xor function used is pretty basic.Here's the (debug + verbose) output file if it's of any help, i can upload the binary too if needed.
debug.txt
My apologies if this is a known issue or not an issue at all and i just got unlucky!