banner
lca

lca

真正的不自由,是在自己的心中设下牢笼。

"Learning Notes on IDA Reverse Engineering from Scratch - 16 (Aspack Unpacking)"

puahad and popad#

Packager: unpackme.aspack.2.2, can be searched online.

image.png

Open with IDA, manually load the program, and uncheck create input segment.

image.png

Entry of the packager

In the image above, the first instruction is pushad, which pushes the values of all general-purpose registers onto the stack.

pushad saves the values of all registers onto the stack in the following order.

image.png

Order of values pushed by pushad

popad is the opposite operation of pushad, it pops the values from the stack and saves them to the registers in the following order.

image.png

In most simple packers, the pushad instruction is used to save the initial state of the registers, and then before jumping to the OEP to execute the original code in memory, POPAD is used to restore the initial state of the registers. According to this rule, the pushad-popad method can easily find the OEP.

Debugging with idapython#

What is the pushad-popad method?

There are two ways to run the program using a debugger. The first is through menu bar - debugger - select debugger, choose local windows debugger, and the second is to use Python to run the debugger. This time, we will use the second method to run the debugger.

  1. Import the idc module with import idc, type idc.load, then press the tab key to autocomplete, find idc.load_debugger, with the parameter idc.load_debugger("win32",0) //1 for remote debugging, returning True indicates success.

image.png

Load load_debugger and set parameters

At this point, the local windows debugger has been loaded.

image.png

Set a breakpoint after pusha

image.png

Set a breakpoint after pusha

The pushad-popad method involves finding the location on the stack where the register values are saved just before the instruction following pushad, and setting a breakpoint at that location. After the program decrypts the original code and jumps to the OEP, it will restore the initial values of the registers through popad before triggering the breakpoint to pause execution, thus determining the location of the OEP.

Press F2 to set a breakpoint on the instruction following pusha, so that the debugging will pause after executing pusha. (pusha is similar to pushad)

If you want to set a breakpoint using Python, you can use the following statement:

idaapi.add_bpt(0x46b002,0,idc.BPT_SOFT)
idaapi.add_bpt(0x46b002,0,0)
  • The first parameter is the breakpoint address
  • The second parameter is the length of the breakpoint
  • The third parameter is the type of breakpoint (software breakpoint BPT_SOFT or 0)

image.png

After selecting the debugger and setting the first breakpoint, you now need to start the debugger to run the program and pause at that breakpoint. Press F9 or use the Python statement to run the program.

idc.StartDebugger("","","");

In higher versions (ida 7.7), the above command will report an error, and you need to use the following command.

idc.start_process("","","")

After executing the above statement, the debugger will run to the previously set breakpoint, which is at 0x46b002.

image.png

Running to breakpoint 0x46b002

In the image below, the values in the stack view are the register values saved by pushad, which will be read by the popad instruction later, so a breakpoint can be set on the first line.

image.png

In this debugging session, the breakpoint is at 0x19ff54, which is the position in the ESP execution stack.

Click the small arrow on the right side of ESP to jump to that address in the corresponding window (in this case, the assembly window).

image.png

Press F2 to set a breakpoint here, changing it to trigger on read and write rather than on execution.

image.png

If the window does not appear, you can open the menu debugger - breakpoints - breakpoint list, then right-click to select edit settings.

If using Python, you can set the breakpoint as follows:

idaapi.add_bpt(0x19ff54,1,3)

image.png

The breakpoint list is as follows

image.png

Breakpoint list

In the above code, the first parameter indicates the breakpoint address, the second parameter indicates the size of the breakpoint, and the third parameter indicates the type, where 3 represents read-write access (reading and writing). Executing this statement is the same as manually setting the breakpoint.

BPT_EXEC = 0,
BPT_WRITE = 1,
BPT_RDWR = 3,
BPT_SOFT = 4,

Parameters for setting breakpoint types

In the breakpoint list, right-click the first breakpoint you set and select DISABLE to disable it, or use the following Python statement.

image.png

idaapi.enable_bpt(0x46b002,0)

image.png

  • The first parameter is the breakpoint address
  • The second parameter, if 1, enables the breakpoint; if 0, disables the breakpoint

0x46b002 marked in green indicates disabled, while the breakpoint on the stack is red indicating enabled.

image.png

Press F9 to continue debugging or enter the following Python statement

idaapi.continue_process()

In the image below, debugging is interrupted after the popad instruction retrieves the initial values of the registers, and the program will jump to the OEP, which is 0x4271b0, because push ret is similar to jmp.

image.png

popa instruction

Continue stepping to execute to OEP.

image.png

Now that the OEP entry has been found, you can reanalyze the executable file and identify functions.

image.png

Reanalyze the program

image.png

Using idapython for dumping#

After finding the OEP, the next step is to perform a dump, which requires the base address of the file and the highest address of the last section of the executable file.

image.png

From the image above, the base address is 0x40000, and the highest address is 0x46e000.

The dump script is as follows:

import idaapi
import idc
import struct

start_ea = 0x400000
end_ea = 0x46e000
step = 4  # Each address occupies 4 bytes of data

file_path = "dump.bin"
with open(file_path, "wb") as f:
    for ea in range(start_ea, end_ea, step):
        # Read 4 bytes of data from the specified address and convert to little-endian byte order
        bin_data = struct.pack("<L", idaapi.get_32bit(ea))
        f.write(bin_data)

Load the above Python script through File - Script File, and after execution, a dump.bin file will be generated in the current directory. Change its extension to exe.

Open with peeditor, go to the section view, right-click on all sections, and select dumpfixer.

image.png

At this point, the icon has been fixed.

image.png

After fixing the icon, use Scylla 0.98 to attach the process to the packed file, currently executing at the OEP entry.

image.png

Load this process.

image.png

Enter the OEP as 004271B0, click IAT Autosearch and Get Imports.

image.png

Click show invalid, and you will find an API that could not be recognized. The attempt to auto-fix failed and needs to be manually fixed.

image.png

API at 0x460818

In the image above, 0x460818 is the API function of the first valid section, and there are more unrecognized API addresses above it.

Check the first unrecognized address at 0x46080c to see what it contains, press D to change the data type and reorganize the bytes, as shown below:

image.png

The content here does not point to any valid address, and pressing CTRL+X shows no references.

image.png

For a real API function, there should be reference information indicating where it is called.

Therefore, since these are not API functions, they can be deleted.

image.png

Click the clear button, then click IAT Autosearch, and select No in the pop-up window.

image.png

Now the IAT starting point is at 0x460810, and then click Get Import to find all APIs.

image.png

Click Fix Dump and dump the file, selecting the previously exported file.

image.png

Finally, the unpacked program runs normally.

image.png

At this point, the entire unpacking process is complete.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.