Washi1337 / OldRod

An automated KoiVM disassembler and devirtualisation utility
GNU General Public License v3.0
345 stars 80 forks source link

An item with the same key has already been added #67

Open milenkowski opened 1 year ago

milenkowski commented 1 year ago

Describe the bug

Adding to a previous bug report on the same topic:

OldRod adds different values to dictionaries, related to constants and opcodes specifically, under previously used keys. This causes an exception to be thrown.

In the context of the sample provided below, this behavior can first be seen in the OldRodPipeline.ConstantsResolutions.ConstantResolutionStage.AutoDetectConstants function, where OldRod adds values to the constants.* dictionaries.

If the above function is modified to skip attempts to add values under a previously used key, the issue reappears in OldRodPipeline.OpCodeResolution.OpCodeResolutionStage.MatchOpCodeTypes function where OldRod works with the mapping1, mapping2, and opcodes dictionaries.

To Reproduce OldRod.exe 0onfirm.exe

The sample is part of a malware, so please exercise caution.

Attached sample (assembly):

**Expected behavior** No use of previously used keys when working with dictionaries for constant and opcode mapping. Thank you for considering this issue. **Screenshots** ![screenshot](https://user-images.githubusercontent.com/61630968/213677019-e56ed06e-a38a-4ee7-82b4-54b483ed1d0e.JPG) **Additional context** /
Washi1337 commented 1 year ago

This is a direct result of a modification and/or extra obfuscation that is applied on top of vanilla KoiVM, which is something that OldRod does not support out-of-the-box.

OldRod's current system for detecting opcode mappings is rather rudimentary. It searches for the method responsible for the initialization of the virtual machine. In vanilla KoiVM, this comprises a bunch of assignments to constant fields of type byte (see ConstantsResolutionStage.cs#L60 and ConstantsResolutionStage.cs#L190) . In your case this initialization is located in method SQLSTRING939AF857::.cctor() (metadata token 0x06000252). When you open this method in a decompiler, you can clearly see an extra layer of obfuscation is applied. Since oldrod is not a deobfuscator, you will need to clean those first, or else provide a config.json yourself that maps the byte values to their representative opcodes (see example-config.json for an example).

milenkowski commented 1 year ago

Thank you for your fast response, much appreciated.

Just wondering: If SQLSTRING939AF857::.cctor() is modified such that it contains only concrete byte assignments, will OldRod detect those operations as opcode mappings (as part of the tool's search for the method responsible) and process accordingly?

Washi1337 commented 1 year ago

Yes, OldRod's auto-detector assumes this method comprises only assignments to these fields. Thus, if you turn them into patterns similar to the following CIL sequence:

ldnull
ldc.i4 <value>
stfld field

then it should be able to automatically map all opcodes.

milenkowski commented 1 year ago

Thank you for your informative response.

I have (via dnSpy) transformed the obfuscated assignments to sequences of static assignments:

ldc.i4 <value>
stsfld field

However, the issue still persists and it triggers at the very same place (i.e., when resolving register mappings).

Wondering whether there are other obfuscated assignments that I may be missing, or whether the issue is with the code of Old Rod (since you pointed out to SQLSTRING939AF857::.cctor() in particular for the issue at hand).

Attaching the modified executable for reference.

EDIT: After closer look, I believe there is an added obfuscation by messing up the order of the constant groups (REG, FL, OP_, etc.), which may result in duplicate values per enum.

Thank you.

Washi1337 commented 1 year ago

Yes this indeed seems to be the case.

Unfortunately, there is not really a way for OldRod to map opcodes automatically to their proper opcode handlers without this order being preserved. You will have to either write some detection mechanisms yourself and modify OldRod, or manually infer which fields correspond to what semantics and construct a manual mapping in a config.json file.

Not a great answer unfortunately, but the only one I have right now for you.