Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to correctly close an emulation #139

Open
cecio opened this issue Mar 21, 2021 · 4 comments
Open

How to correctly close an emulation #139

cecio opened this issue Mar 21, 2021 · 4 comments

Comments

@cecio
Copy link
Contributor

cecio commented Mar 21, 2021

Hey,

first of all let me say a huge thank you for this awesome tool!

My question:
I'm trying to run two emulations in the same script:

se = speakeasy.Speakeasy(logger=get_logger())
sc_addr = se.load_shellcode('/tmp/shell1', arch)
se.reg_write('RSI',sc_addr)
se.emu.mem_map(sc_addr - 0x100, 0x100)
se.run_shellcode(sc_addr, offset=0x0)

se = speakeasy.Speakeasy(logger=get_logger())
sc_addr = se.load_shellcode('/tmp/shell2', arch)
se.reg_write('RSI',sc_addr)
se.emu.mem_map(sc_addr - 0x100, 0x100)
se.run_shellcode(sc_addr, offset=0x0)

With this sequence of instructions, I can run shell1, but shell2 is failing with a invalid read at the very beginning. If I try to invert the order (so I run shell2 before shell1), now the first runs fine, the second fails with the same invalid read error.
So, I think I need to "clean up" things after the first emulation, but I don't have very clear how. I tried to use the

se.emu.mem_purge()

but no luck. I did an initial investigation and I saw that the invalid read comes after the access to the allocated PEB. Let me know if you want I go deeper with this. But may be I'm just closing the emulation in the wrong way (actually I'm not closing it at all :-)).

Thanks in the meantime.

@drewvis
Copy link
Contributor

drewvis commented Apr 12, 2021

Hello, thanks for the feedback and sorry for the late reply. That "should" work as you have it. I would expect that once the emulator object is garbage collected, everything should just work again. This makes me think it's related to the native unicorn instance. Since it seems it's related to PEB access here, I wonder if it's related to how we setup the segment registers. I will also investigate a bit and reply here if I can get to bottom of this.

@cecio
Copy link
Contributor Author

cecio commented Apr 13, 2021

Thanks for the reply!

I did some more work. It looks like the issue is somewhere in PEB_LDR_DATA and specifically in InLoadOrderModuleList and InMemoryOrderModuleList LIST_ENTRY

This is a well-known sequence in a 'good' run:

0x1257: 0x64a130000000 mov eax, dword ptr fs:[0x30]
   EAX=0x6000
0x125d: 0x8b400c mov eax, dword ptr [eax + 0xc]
   EAX=0x8000
0x1260: 0x8b400c mov eax, dword ptr [eax + 0xc]
   EAX=0x6160
0x1263: 0x8b7818 mov edi, dword ptr [eax + 0x18]
   EDI=0x400000

Then the execution proceed.
This is the same sequence in a 'failing' run (so executed after some other shellcode has been run. Consider that if I run two times the same shellcode, it works):

0x1257: 0x64a130000000 mov eax, dword ptr fs:[0x30]
   EAX=0x6000
0x125d: 0x8b400c mov eax, dword ptr [eax + 0xc]
   EAX=0x8000
0x1260: 0x8b400c mov eax, dword ptr [eax + 0xc]
   EAX=0x1340                     <------- First difference
0x1263: 0x8b7818 mov edi, dword ptr [eax + 0x18]
   EDI=0xe553a458              <-------- Second difference, looks an uninitialized address

Then, when an access to dword ptr [edi + 0x3c] is done, I get an invalid read.

I don't have very clear why this is happening, trying to figure it out. If you have any idea... ;-)

@cecio
Copy link
Contributor Author

cecio commented Apr 14, 2021

May be I found something interesting.

In speakeasy/windows/objman.py in class Process we have the method add_module_to_peb.

    def add_module_to_peb(self, module):
        pld = self.peb_ldr_data
        list_type = self.nt_types.LIST_ENTRY(self.emu.get_ptr_size())

        # Initialize the LDTE
        ldte = LdrDataTableEntry(self.emu, module.get_emu_path())
        if not self.ldr_entries:
            prev = ldte
        else:
            prev = self.ldr_entries[-1]

I noticed that at the first run, this method is called several time (one for each module I guess). The very first one, having the self.ldr_entries list empty, sets the prev=ldte. So far so good.

In the second run, the method is entered again, but now the prev = ldte is never executed, because at the first loop the self.ldr_entries is still populated from the first run. I tried to place a breakpoint on the if line and tried to empty manually the list: with this trick the second run works fine.

So, it looks that the self.ldr_entries is not re-initialized between the two runs and the Process object is not destroyed/garbage collected. I'm not sure how to fix this the proper way. What do you think?

@cecio
Copy link
Contributor Author

cecio commented Apr 24, 2021

I tried to move the ldr_entries initialization from the Process scope to the __init__ method of the Process object:

it works fine, the two shellcodes are running fine, no matter the order.

Also, all the unit tests are running fine. I'm not sure why the initialization of the ldr_entries was out if the __init__, so I'd like to ask your thoughts on this before doing a pull request.

Thanks

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

No branches or pull requests

2 participants