banner
lca

lca

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

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

puahad and popad#

Packaged program: unpackme.aspack.2.2, can be searched on the internet.

image.png

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

image.png

Entry of the packed program

In the above image, the first instruction is pushad, which will push 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 retrieves 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?

Use a debugger to run the program. There are two ways to run the debugger: the first is through menu bar - debugger - select debugger, choose local windows debugger, the second way 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 and then press the tab key to autocomplete, find idc.load_debugger, the parameter is 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, then 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, then trigger 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, and a breakpoint can be set on the first line.

image.png

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

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

image.png

Press F2 to set a breakpoint here, changing it to trigger on read and write instead of 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 you want to set a breakpoint using Python, you can install it 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 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 through to execute to OEP.

image.png

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

image.png

Reanalyze the program

image.png

Dumping with idapython#

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 it 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 being debugged, 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

Clicking show invalid will reveal an unrecognized API, and attempts to auto-fix will fail, requiring manual repair.

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 what the first unrecognized API at 0x46080c contains, press D to change the data type and reorganize the bytes, as shown in the image below:

image.png

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

image.png

For real API functions, there should be reference information indicating where they are 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, 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 of the program is complete.

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