mandiant / flare-floss

FLARE Obfuscated String Solver - Automatically extract obfuscated strings from malware.
Apache License 2.0
3.17k stars 445 forks source link

FLOSS not decoding string with Registered trademark sign (®) #619

Open riesha opened 1 year ago

riesha commented 1 year ago

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!

mr-tz commented 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?

riesha commented 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?

Should be a decoded string. here's the bin!

bin.zip

williballenthin commented 1 year ago
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
williballenthin commented 1 year ago

there are additional missed strings (unrelated to any encoding issues), such as here:

0x14000506E

image
williballenthin commented 1 year ago

encoding of the "Call of Duty(R) HD" string has 0xAE for (R)

image

related:

image
williballenthin commented 1 year ago

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

williballenthin commented 1 year ago

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.

williballenthin commented 1 year ago

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:

image

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)

riesha commented 1 year ago

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:

image

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!

williballenthin commented 1 year ago

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.