2

I wanted to run an app on a VM but there are PUSH PUSH RETs stopping me from looking at their anti VM code. There is a messagebox when I run it on a VM. I set a breakpoint at MessageBoxA and it gets triggered as well as EAX is showing the message for the VM. It's all good until now.

enter image description here

I'm using x64dbg and I opened Call Stack tab and double-clicked on the address after user32.MessageBoxA (basically 2nd record), so I could trace back the calls before the actual MessageBoxA. It landed me here:

enter image description here

I heard that PUSH RET is like a jmp, PUSH PUSH RET is like a call. How do they work? Can they be deobfuscated with a tool/plugin or something?

PUSH 0xC9DFBF leads me here:

enter image description here

PUSH 0xA4DB49 leads me here:

enter image description here

When I follow that jmp, it leads to similar code to PUSH 0xC9DFBF's image which ends with a jmp to MessageBoxA.

What could you recommend me to step into the code before the actual MessageBoxA call, so I can find their anti VM code and patch it? Do I need to step the instructions from the beginning one by one?

Dumps: http://ufile.io/56c5b2z6. Rebase to 0x1270000 and find 0181E65C address. This address is in the executable file. I attached two dumped DLLs as well, because they were referenced.

4
  • 1
    Hi and welcome to RE.SE! Could you please post the code rather than screenshots. Screenshots make it impossible to search for any keywords, so it's worse for future visitors and also makes it - in general - harder to use your code as a template to explain things.
    – 0xC0000022L
    Jun 2, 2020 at 20:06
  • @0xC0000022L, thanks. ufile.io/56c5b2z6 - here are the dumps. Rebase to 0x1270000 and find 0181E65C address. That's the function which is executed before the MessageBoxA call. If there is a better way to provide the code, let me know.
    – nop
    Jun 2, 2020 at 23:52
  • 1
    Something went wrong, I still see only screenshots after your edit.
    – Jongware
    Jun 3, 2020 at 6:56
  • @usr2564301 even now?
    – nop
    Jun 3, 2020 at 7:13

2 Answers 2

1

Technically,

PUSH A
RET

is equivalent to

JMP A

and

PUSH A
PUSH B
RET

is equivalent to

CALL B
JMP A

You can write a x64dbg script which searches for the above pattern and replaces it with the simplified assembly.


Example

PUSH + RET has the byte pattern

0:  68 44 33 22 11          push   0x11223344
5:  c3                      ret 

The following script replaces it with JMP and the remainder (if any) with NOP.

$base = 0x91e000
$search_size = 0x100

findall $base, "68 ?? ?? ?? ?? c3", $search_size
$count = $result

next:
    dec $count
    $addr = ref.addr($count)
    $jmp_addr = dis.imm($addr)
    asm $addr, "jmp 0x{$jmp_addr}"
    $asm_size = $result
    $remainder = 6 - $asm_size

    fill_nop:
        dec $remainder
        asm $addr+$asm_size+$remainder, "nop"
        test $remainder, $remainder
        jnz fill_nop

    test $count, $count
    jnz next

msg "Done"

Similarly for PUSH + PUSH + RET,

0:  68 44 33 22 11          push   0x11223344
5:  68 dd cc bb aa          push   0xaabbccdd
a:  c3                      ret 

The following script replaces it with CALL + JMP.

$base = 0x91e000
$search_size = 0x100

findall $base, "68 ?? ?? ?? ?? 68 ?? ?? ?? ?? c3", $search_size
$count = $result

next:
    dec $count
    $addr = ref.addr($count)
    $ret_addr = dis.imm($addr)
    $call_addr = dis.imm($addr+5)
    asm $addr, "call 0x{$call_addr}"
    $asm_size = $result
    asm $addr+$asm_size, "jmp 0x{$ret_addr}"
    add $asm_size, $result
    $remainder = 0xb - $asm_size

    fill_nop:
        dec $remainder
        asm $addr+$asm_size+$remainder, "nop"
        test $remainder, $remainder
        jnz fill_nop

    test $count, $count
    jnz next

msg "Done"

However there are some caveats with the above approach:

  1. PUSH + PUSH + RET can be converted to CALL + JMP only if the callee uses RET instruction to return back to the caller (which is normal in cdecl). It won't work if it obfuscates the return using a JMP instruction. That is instead of RET there is

    ADD ESP, 4
    JMP DWORD PTR [ESP-4]
    
  2. Pattern search will work in most cases. However if parts of the code are encrypted it can return false positives and you may be inadvertently overwriting wrong data.

7
  • How does it work? I haven't used x64dbg scripts yet, only IDA scripts. I opened the executable in x64dbg, put a breakpoint at CreateProcessW because it creates some child processes and it decrypts the static encryption by that time. I then open Scripts tab and load the script from file (I changed $base to my Base Address as well). i.imgur.com/TvLi2f2.png. Then I put a breakpoint at MessageBoxA and Run until Anti VM message appears and then Call Stack tab to go back to the previous call and it's same as before: i.imgur.com/b4ue5Q5.png
    – nop
    Jun 4, 2020 at 8:13
  • Base address should be the address of the page which contains the said instructions, not the executable load address.
    – 0xec
    Jun 4, 2020 at 8:22
  • @nop Also to replace all instructions you have to run it in a loop
    – 0xec
    Jun 4, 2020 at 8:49
  • thanks! It worked but as you said, it needs a loop to replace all instructions. Mind sharing the solution with a loop because I have never made x64dbg scripts and there isn't much info about it?
    – nop
    Jun 4, 2020 at 14:12
  • @nop Scripts updated. (Warning: untested). You can find info about x64db g scripts in the docs
    – 0xec
    Jun 4, 2020 at 18:31
4

The RET instruction transfers control to the return address located on the stack. Normally this is used to go back from a function to where the function was called, as the address of the next instruction was pushed into the stack by the CALL instruction.

However, RET can be misused: a PUSH before a RET instruction is a typical obfuscation technique. In this case, when executing the RET, instead of coming back to the caller function, it behaves like a jump to the previously pushed address. This is not something your decompiler expects, which may result in a jump to a memory zone that hasn't been decompiled or has been incorrectly decompiled.

PUSH PUSH RET can be used to implement a CALL as you mention. In that case, the first PUSH pushes the return address into the stack (the one after the RET). The second PUSH pushes the function address. The RET (as explained before) jumps to the function address (and removes this address from the stack). When the function address executes its RET, it jumps back to the address currently located on the stack (the one of the first PUSH), coming back to the caller of the function. Note though that this is not your case. In your case the first address pushed is not in the current function. This means that this is not implementing a CALL. Most likely this it is used as an obfuscation technique, making more complicated to follow the calls and maybe tricking your disassembly to show you the code incorrectly.

Also, note that executing a PUSH doesn't lead you anywhere. It just pushes the address into the stack. It is the RET instruction which actually jumps to the last pushed address. This also means that the second pushed address is visited before the first one. This means that the code you are looking for is reached from A4DB49 (the first address to which RET jumps) that is another JUMP.

I recommend you to check the Anti-Disassembly chapter of the Practical Malware Analysis Book, where this kind of techniques are covered in detail and with examples.

4
  • First of all, thank you for your answer! That's correct. PUSH A PUSH B RET would be as same as CALL B JMP A. The thing is that I need to think about a solution which makes it more readable, because it's painful to be looking at it like that. I provided dumps in the previous comment.
    – nop
    Jun 2, 2020 at 23:51
  • 1
    Not exactly. It would be a CALL B which returns to A. If you have understood how it works, then it is easy to follow the addresses it jumps to. Another (maybe easier) alternative is to use a debugger. Add a breakpoint at A4DB49 and then execute every instruction to see where it is jumping. This is most likely self-written assembly code, so I don't think there is a way to write it in a much more readable way. Jun 3, 2020 at 7:25
  • I could manually follow the code but there are way too many PUSH RETs and JMPs which makes it almost untraceable backwards. Something like an IDA script would probably help. Something which converts those PUSH PUSH RETs instructions to CALLs
    – nop
    Jun 3, 2020 at 7:38
  • You can't convert it to CALL. CALL pushes the next address into the stack and this is not what happens in your code. Jun 3, 2020 at 7:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.