Appearance
Exploration of Hook Hijacking Technology
Hook hijacking technology allows developers to intercept calls to system or application functions and execute custom code before or after function calls. Hook hijacking technology is commonly used for viruses and malicious software, and can also allow developers to extend or modify the functionality of system functions, thereby improving software performance and functionality expansion.
Write functions using assembly instructions
Firstly, it is necessary to automatically convert a segment of assembly code passed in by the user into equivalent machine code, and write this segment of machine code into a memory space. Finally, the first address of that memory space needs to be reversed.
To achieve this requirement, three steps are required: first, allocate a segment of memory space in memory using the create_alloc
function, then obtain the number of bytes occupied by the assembly instruction by calling the assemble_code_size
function, and write this bytecode into the corresponding heap by calling the assemble_write_memory
function. Finally, the memory address of the heap is reversed.
python
from x32dbg import Debugger
# Encode a piece of assembly code and write it out into temporary heap space
def write_opcode_from_assemble(dbg_ptr,asm_list=[]):
addr_count = 0
addr = dbg_ptr.create_alloc(0,1024)
if addr != 0:
for index in asm_list:
asm_size = dbg_ptr.assemble_code_size(index)
if asm_size != 0:
write = dbg_ptr.assemble_write_memory(addr + addr_count, index)
if write == True:
addr_count = addr_count + asm_size
else:
dbg_ptr.delete_alloc(addr)
return 0
else:
dbg_ptr.delete_alloc(addr)
return 0
else:
return 0
return addr
if __name__ == "__main__":
dbg = Debugger(address="127.0.0.1",port=6589)
if False == dbg.connect():
exit()
# Obtain the memory address of Message Box A in the user32.DLL library
msg_ptr = dbg.get_module_proc_addr("user32.dll","MessageBoxA")
call = "call {}".format(str(hex(msg_ptr)))
print("Generate call assembly statements = {}".format(call))
# List of instruction sets to be written
asm_list = [
'push 0',
'push 0',
'push 0',
'push 0',
call
]
# Batch assemble and write to the corresponding memory
write_addr = write_opcode_from_assemble(dbg,asm_list)
print("Write out memory address = {}".format(hex(write_addr)))
# Set the memory execution attribute to read write execution
dbg.set_memory_protect(write_addr,32,1024)
# Set EIP to instruction set position
dbg.set_eip(write_addr)
dbg.close()
As shown in the above code, in order to write a pop-up window, the first step is to obtain the loading address of the MessageBoxA
function located in the user32.dll
module through the get_module_proc_addr
function. This function takes five parameters, with the first four being saved through push 0
, and the last parameter being a call to the call
statement. The constructed string is passed directly to the write_opcode_from_assemble
function to write the data to the process heap, and the executable properties are set through set_memory_protect
.
Implement Hook to replace MessageBox
In the previous case, we demonstrated how to write a piece of assembly code into peer memory. Next, we will take replacing the MessageBox
pop-up as an example to demonstrate how to replace the functionality of specific functions in a process, and use this to achieve the effect of custom pop ups.
Firstly, encapsulate the implementation of the assembly
function, which receives an assembly list and converts it into machine code, and sequentially writes it out to the memory of the specified process. By calling get_module_proc_addr
, the memory address of the original pop-up window is obtained, and the first address of MessageBoxA
is written out and replaced with the assembly jump contained in asm
, as shown in the following example;
python
from x32dbg import Debugger
# Encode a piece of assembly code and write it out to the specified memory
def assemble(dbg, address=0, asm_list=[]):
asm_len_count = 0
for index in range(0,len(asm_list)):
# Write to memory
dbg.assemble_at(address, asm_list[index])
# Obtain assembly instruction length
asm_len_count = dbg.assemble_code_size(asm_list[index])
# Address increment each time
address = address + asm_len_count
if __name__ == "__main__":
dbg = Debugger(address="127.0.0.1",port=6589)
if False == dbg.connect():
exit()
# Find MessageBoxA
messagebox_address = dbg.get_module_proc_addr("user32.dll","MessageBoxA")
print("MessageBoxA memory address = {}".format(hex(messagebox_address)))
# Allocate space
HookMem = dbg.create_alloc(0,1024)
print("Custom memory space = {}".format(hex(HookMem)))
# Write the MessageBoxA redirect address
asm = [
f"push {hex(HookMem)}",
"ret"
]
# Write assembly instructions from the list into memory
assemble(dbg,messagebox_address,asm)
dbg.close()
Due to the need for two temporary variables to represent the title and content in the MessageBox
pop-up window, we allocate two heap spaces in the peer memory using the create_alloc
function, and sequentially write out the pop-up window string into memory using set_memory_byte
. At this point, the pop-up window content is also filled in, where txt
represents the title and box
represents the content;
python
# Define two variables to store strings
MsgBoxAddr = dbg.create_alloc(0,512)
MsgTextAddr = dbg.create_alloc(0,512)
# Fill the pop-up window with the title lyshark
txt = [0x6c, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6b]
# Fill in the content as lyshark.com
box = [0x6C, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6B, 0x2E, 0x63, 0x6F, 0x6D]
for txt_count in range(0,len(txt)):
dbg.set_memory_byte(MsgBoxAddr + txt_count, txt[txt_count])
for box_count in range(0,len(box)):
dbg.set_memory_byte(MsgTextAddr + box_count, box[box_count])
print("Title Address = {} content = {}".format(hex(MsgBoxAddr),hex(MsgTextAddr)))
Next, it is necessary to implement the main body of the pop-up window. For a complete pop-up window, only the core code of the pop-up window needs to be extracted, without the need to extract all instruction sets.
However, it should be noted that the call 0x75B20E20
address needs to be replaced, and the address here may change depending on the system. Special attention should be paid when extracting it;
python
# This is the segment after replacing the Message Box
PatchCode =\
[
"mov edi, edi",
"push ebp",
"mov ebp,esp",
"push -1",
"push 0",
"push dword ptr ss:[ebp+0x14]",
f"push {hex(MsgBoxAddr)}",
f"push {hex(MsgTextAddr)}",
"push dword ptr ss:[ebp+0x8]",
"call 0x75B20E20",
"pop ebp",
"ret 0x10"
]
# Write to custom memory
assemble(dbg, HookMem, PatchCode)
# Set the memory execution attribute to read write execution
dbg.set_memory_protect(HookMem,32,1024)
The above is the code explanation for replacing pop ups. Integrating the code together can achieve the replacement of pop ups;
python
from x32dbg import Debugger
# Encode a piece of assembly code and write it out to the specified memory
def assemble(dbg, address=0, asm_list=[]):
asm_len_count = 0
for index in range(0,len(asm_list)):
dbg.assemble_at(address, asm_list[index])
asm_len_count = dbg.assemble_code_size(asm_list[index])
address = address + asm_len_count
if __name__ == "__main__":
dbg = Debugger(address="127.0.0.1",port=6589)
if False == dbg.connect():
exit()
# Find MessageBoxA
messagebox_address = dbg.get_module_proc_addr("user32.dll","MessageBoxA")
print("MessageBoxA memory address = {}".format(hex(messagebox_address)))
# Allocate space
HookMem = dbg.create_alloc(0,1024)
print("自定义内存空间 = {}".format(hex(HookMem)))
# Write the MessageBoxA redirect address
asm = [
f"push {hex(HookMem)}",
"ret"
]
# Write assembly instructions from the list into memory
assemble(dbg,messagebox_address,asm)
# Define two variables to store strings
MsgBoxAddr = dbg.create_alloc(0,512)
MsgTextAddr = dbg.create_alloc(0,512)
# Fill in string content
txt = [0x6c, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6b]
box = [0x6C, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6B, 0x2E, 0x63, 0x6F, 0x6D]
for txt_count in range(0,len(txt)):
dbg.set_memory_byte(MsgBoxAddr + txt_count, txt[txt_count])
for box_count in range(0,len(box)):
dbg.set_memory_byte(MsgTextAddr + box_count, box[box_count])
print("Title Address = {} content = {}".format(hex(MsgBoxAddr),hex(MsgTextAddr)))
# This is the segment after replacing the MessageBox
PatchCode =\
[
"mov edi, edi",
"push ebp",
"mov ebp,esp",
"push -1",
"push 0",
"push dword ptr ss:[ebp+0x14]",
f"push {hex(MsgBoxAddr)}",
f"push {hex(MsgTextAddr)}",
"push dword ptr ss:[ebp+0x8]",
"call 0x75B20E20",
"pop ebp",
"ret 0x10"
]
# Write to custom memory
assemble(dbg, HookMem, PatchCode)
# Set the memory execution attribute to read write execution
dbg.set_memory_protect(HookMem,32,1024)
print("Pop up has been replaced")
dbg.debug_run()
dbg.close()
When the above code is run, it will replace the MessageBoxA
function in the process with a custom function. Therefore, when the user clicks the pop-up button again, the original content will not be prompted, but a custom pop-up will be prompted;