Skip to content

Conversation

@enkomio
Copy link
Contributor

@enkomio enkomio commented Dec 12, 2025

Hi,
this PR was mainly created to address an issue in the GetProcAddress emulation.
The function always returns a value without checking whether the DLL actually exports the function. This causes problems under specific conditions. For example, the test binary in the PR tries to load FlsGetValue2, which is not exported by Kernel32 (at least on my system :P), resulting in an error.

I also added some additional improvements, in particular:

  • GetProcAddress now verifies whether the function is actually exported by the target DLL. If so, the correct function address is returned instead of a “random one”. This ensures that the address falls within the DLL's memory address range.
  • I modified the structure of DecoyModule; in particular, the sections are now aligned at FileAlignment and SectionAlignment.
  • During module jitting, I map the PE headers and all the sections into memory, instead of only reserving memory for the whole module.

Thanks

@enkomio
Copy link
Contributor Author

enkomio commented Dec 18, 2025

hi @williballenthin,
let me know if I can improve this PR in any way, thx!

Copy link
Collaborator

@williballenthin williballenthin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see inline comments providing suggestions. feel free to push back on any of them (or implement if reasonable). let me know when we should merge. thank you!


self.basepe = pefile.PE(data=husk, fast_load=True)

# set base properties
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# set base properties


return exp_size

def align_file(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def align_file(self):
def pad_file(self):

perhaps?

i think aligning would be changing the base address, not adding to the end

Comment on lines 527 to 528
self.add_section(name='.text', chars=0x60000020)
self.add_section(name='.edata', chars=0x40000040)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there constants we could use for chars to make this more readable?

return self.get_raw_pe()

def init_export_section(self, name, exports):
def init_export_section(self, name, exports, export_rvas):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make any senses to merge exports and export_rvas into a single object, rather than keeping them as parallel data structures?

def generate_export_table(self, modname):
def generate_decoy_module(self, modname, base):
'''
Generates a PE export table that can be parsed by malware
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you restore some of the documentation or perhaps make it more detailed? im not sure that "synthetic decoy module" has enough meaning to most people.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about:

Create a decoy module that emulates a DLL. The decoy module will contain basic 
structures such as a .text section and an .edata section containing a list of exported functions.

?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great!


if not path:
path = default_file_path
path = default_file_path
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
path = default_file_path
path = default_file_path

@enkomio
Copy link
Contributor Author

enkomio commented Dec 19, 2025

Thank you for the great suggestions! I have updated my PR accordingly :)

Comment on lines +697 to +701
def create_pattern(index):
if self.arch == _arch.ARCH_X86:
p = b'\x89\xff\x90\xB8' + index.to_bytes(4, 'little') + b'\xc3\x90\x90\x90\x90\x90\x90\x90'
else:
p = b'\x48\x89\xFF\x90\x48\xC7\xC0' + index.to_bytes(4, 'little') + b'\xc3\x90\x90\x90\x90'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you add the disassembly of this in a comment, and possibly add a little human readable explanation as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants