February 12, 2016

Dynamic shellcode analysis

In this article, we will study a shellcode using dynamic analysis. This analysisincludes a description of Miasm internals, which explains its length. Theshellcode is in the archivedyn_sc_shellcodes.zip, protected withthe passwordinfected. The final script is here:dyn_sc_run.py

Overview:

This analysis is based on Miasm revision2cf6970.

First blood

Here is a raw dump of the shellcode:

0000000050594949494949494949494949494949|PYIIIIIIIIIIIIII|00000010494937515a6a415850304130416b4141|II7QZjAXP0A0AkAA|0000002051324142324242304242414258503841|Q2AB2BB0BBABXP8A|0000003042754a4962786a4b6458505a6b396e36|BuJIbxjKdXPZk9n6|000000406c494b674b30656e7a494254466b6c79|lIKgK0enzIBTFkly|000000507a4b7773777077704c6c6654576c4f5a|zKwswpwpLlfTWlOZ|0000006039726b4a6b4f59425a6348685863596f|9rkJkOYBZcHhXcYo|00000070596f4b4f7a557677454f676c776c4372|YoKOzUvwEOglwlCr|...

We can note that this shellcode is in pure ascii. Let’s disassemble its firstbasic block:

pythonmiasm/example/disasm/full.py-mx86_32shellcode.bin--blockwatchdog1

This gives the following graph (filegraph_execflow.dot):

../../../_images/sc01_bbl1.svg

First basic block of the shellcode

Note thePUSHEAXPOPECX to mimic aMOVECX,EAX, keeping a pureascii encoding. As we can see, the shellcode starts with some computations,and willxor a memory cell:

00000019XORBYTEPTR[ECX+0x30],AL

We could analyze it manually or dynamically. For the exercise, we will try todetermine which pointer is manipulated here. Now, the question is: where doesthe valueECX+0x30 point to? In Miasm, there are at least two ways toanswer this:

  • using a symbolic execution from the beginning to retrieve the equation ofECX at address0x19
  • using theDependencyGraph, whose goal is to track all the lines whichparticipate to the value of a selected variable. We won’t introduce thismodule here, because a future post will be dedicated to it.

Symbolic Execution

Here are the steps to perform a symbolic execution of a basic block:

  1. disassemble the block
  2. translate it in the Miasm intermediate representation (IR)
  3. create an initial state
  4. launch the symbolic execution

The following code disassembles the shellcode from address0x0 to0x1C(after theXOR). Then we will translate it in IR and finally run thesymbolic execution, stopping at address0x1C. Here is the script:

importsysfrommiasm2.analysis.machineimportMachinefrommiasm2.core.bin_streamimportbin_stream_strfrommiasm2.ir.symbexecimportsymbexec# Create a bin_stream from a Python stringbs=bin_stream_str(open(sys.argv[1]).read())# Get a Miasm x86 32bit machinemachine=Machine("x86_32")# Retrieve the disassemble and IR analysisdis_engine,ira=machine.dis_engine,machine.ira# link the disasm engine to the bin_streammdis=dis_engine(bs)# Stop disassembler after the XORmdis.dont_dis=[0x1C]# Disassemble one basic blockblock=mdis.dis_bloc(0)# instanciate an IR analysisir_arch=ira(mdis.symbol_pool)# Translate asm basic block to an IR basic blockir_arch.add_bloc(block)# Store IR graphopen('ir_graph.dot','w').write(ir_arch.graph.dot())# Initiate the symbolic execution engine# regs_init associates EAX to EAX_init and to onsb=symbexec(ir_arch,machine.mn.regs.regs_init)# Start execution at address 0# IRDst represents the label of the next IR basic block to executeirdst=sb.emul_ir_blocs(ir_arch,0)print'ECX =',sb.symbols[machine.mn.regs.ECX]

The output is:

ECX=(EAX_init+0xFFFFFFF0)

So at this point, as thexored memory is located at[ECX+0x30], thepointer is in fact(EAX_init+0xFFFFFFF0)+0x30=EAX_init+0x20. Bythe way,EAX_init is the value ofEAX in the initial symbolic executionstate.

Actually, the shellcode has information about the value ofEAX when it’s runby the application. What I didn’t say is that this shellcode was executed afteran exploit which leads to the corruption of a vtable leading to aCALLEAX. Hence the shellcode knows that when its first instruction is executed,EAX points to it.

If you don’t want to bother writing Python code only to run a symbolicexecution, the scriptmiasm/example/ida/symbol_exec.py will do thetrick. UnderIDA, hitAlt-F7 and run the script. Now, select the code youwant to execute and hitF3.

../../../_images/sc01_select_code.png

Select the code onIDA

You should have the following result:

../../../_images/sc01_se_result.png

Result of the symbolic execution inIDA

Note: the script only displays modified registers and memory. Here again, thevalue ofECX isEAX_init+0xFFFFFFF0. Please, note that Miasm2 must be inIDA’s python path for the script to run properly.

So the shellcode will modify itself. Even if we could continue the analysismanually, here we are going to use the Miasm sandbox to run a dynamic execution.

Emulation

To continue the analysis, we will emulate the shellcode in a sandbox. For this,Miasm offers multiple solutions.

There is a simple sandbox demonstration in the examplemiasm/example/jitter/x86_32.py. Here is the core of the script:

# Create a x86 32bit sandboxmyjit=Machine("x86_32").jitter()# Add memory for the stack, and point ESP to this areamyjit.init_stack()# Read the shellcodedata=open(args.filename).read()# Add memory for the shellcoderun_addr=0x40000000myjit.vm.add_memory_page(run_addr,PAGE_READ|PAGE_WRITE,data)# Trace registers values and mnemonicsmyjit.jit.log_regs=Truemyjit.jit.log_mn=True# Push special address 0x1337BEEF on the stackmyjit.push_uint32_t(0x1337beef)# Add a breakpoint to special address 0x1337BEEF to stop emulationmyjit.add_breakpoint(0x1337beef,code_sentinelle)# Initialize and starts the emulatormyjit.init_run(run_addr)myjit.continue_run()

In this script, we start with an empty sandbox. If you don’t create space forthe stack, the firstPUSH will trigger an error saying that the code istrying to access an unmapped page. This explains themyjit.init_stack().0x1337BEEF is pushed on the stack to force a potentialRET to jump to a special address. We then add a breakpoint at this addressin order to spot such a behavior. So here is trace:

RAX0000000000000000RBX0000000000000000RCX0000000000000000RDX0000000000000000RSI0000000000000000RDI0000000000000000RSP000000000123FFFCRBP0000000000000000zf0000000000000000nf0000000000000000of0000000000000000cf0000000000000000RIP000000004000000040000000PUSHEAX...40000017POPEAXRAX0000000000000041RBX0000000000000000RCX00000000FFFFFFF0RDX00000000FFFFFFF0RSI0000000000000000RDI0000000000000000RSP000000000123FFFCRBP0000000000000000zf0000000000000000nf0000000000000001of0000000000000000cf0000000000000000RIP000000004000001740000018PUSHEAXRAX0000000000000041RBX0000000000000000RCX00000000FFFFFFF0RDX00000000FFFFFFF0RSI0000000000000000RDI0000000000000000RSP000000000123FFF8RBP0000000000000000zf0000000000000000nf0000000000000001of0000000000000000cf0000000000000000RIP000000004000001840000019XORBYTEPTR[ECX+0x30],ALWARNING:address0x20isnotmappedinvirtualmemory:WARNING:address0x20isnotmappedinvirtualmemory:...assert(self.get_exception()==0)AssertionError

In this log, the script fails at address0x40000019: theXOR analyzedpreviously. We can see the error is that the shellcode tries to access unmappedmemory area at address0x20. In fact the initial state of the sandbox setEAX to0x0. As the shellcode has been mapped at address0x40000000, thelookup fails. To fix it, we setEAX to0x40000000:

myjit.cpu.EAX=0x40000000

Now, the execution is able to continue after the self modifying code. Note thatthe logs are very verbose. From now on, we will only activate theblock trace(see previous article for more details).

myjit.jit.log_regs=Truemyjit.jit.log_mn=True

is replaced by:

myjit.jit.log_newbloc=True

The first basic block displayed:

loc_0000000040000000:0x40000000PUSHEAXPOPECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXDECECXAAAPUSHECXPOPEDXPUSH0x41POPEAXPUSHEAXXORBYTEPTR[ECX+0x30],ALINCECXIMULEAX,DWORDPTR[ECX+0x41],0x51XORAL,BYTEPTR[ECX+0x42]XORAL,BYTEPTR[EDX+0x42]XORBYTEPTR[EDX+0x42],ALINCECXINCEDXPOPEAXPUSHEAXCMPBYTEPTR[ECX+0x42],ALJNZloc_000000004000007D:0x4000007d->c_next:loc_0000000040000033:0x40000033c_to:loc_000000004000007D:0x4000007d

The interesting point is the next basic block displayed:

loc_000000004000001C:0x4000001cINCECXIMULEAX,DWORDPTR[ECX+0x41],0x10XORAL,BYTEPTR[ECX+0x42]XORAL,BYTEPTR[EDX+0x42]XORBYTEPTR[EDX+0x42],ALINCECXINCEDXPOPEAXPUSHEAXCMPBYTEPTR[ECX+0x42],ALJNZloc_000000004000007D:0x4000007d->c_to:loc_000000004000007D:0x4000007dc_next:loc_0000000040000033:0x40000033

Note that this new basic block is in fact aslice of the first basicblock. Here is what happened:

  1. Miasm translates the first basic block and starts its execution.
  2. The execution reaches the automodifying code, which messes up the current basicblock.
  3. The execution stops and this block is removed from the cache.
  4. The engine resumes the execution, so the new basic block is handled as a newone, disassembled and displayed

Note this new basic block is a bit different from the end of the first basicblock.

before:

IMULEAX,DWORDPTR[ECX+0x41],0x51

after

IMULEAX,DWORDPTR[ECX+0x41],0x10

Deeper in the Shellcode

This basic block (loc_000000004000001C) decrypts the next stage. We couldstop the execution at0x40000033 and dump the memory to the disk to watch thenext stage for further analysis. But wait! There is more:

loc_0000000040000040:0x40000040MOVECX,0x3EBLODSBXORAL,0x1CSTOSBLOOPloc_0000000040000045:0x40000045->c_next:loc_000000004000004B:0x4000004bc_to:loc_0000000040000045:0x40000045

The code above is another deciphering loop. At this point, we will add abreakpoint at address0x4000004b to dump the shellcode. This breakpoint willtrigger a callback which dumps the deciphered code from memory to the disk.

# A breakpoint callback takes the jitter as first parameterdefdump(jitter):# Dump data ad address run_addr with a length of len(data)new_data=jitter.vm.get_mem(run_addr,len(data))# Save to diskopen('/tmp/dump.bin','wb').write(new_data)# Stop executionreturnFalse# Register a callback to the breakpointmyjit.add_breakpoint(0x4000004b,dump)...myjit.cpu.EAX=0x40000000myjit.init_run(run_addr)myjit.continue_run()

At this stage, a static analysis of the decrypted code is possible. But we willperform a dynamic analysis to use the Miasm sandbox. Here is the next basicblock:

loc_0000000040000058:0x40000058POPESIPUSHEBPMOVEBP,ESPPUSH0x6E6FPUSH0x6D6C7275PUSHESPPUSH0xEC0E4E8EPUSH0x6E2BCA17CALLloc_00000000400002CA:0x400002ca->c_next:loc_0000000040000076:0x40000076

Spoiler: for the trained eyes, we have a code pattern which stacks a specialstring in memory:

>>>"6D6C7275".decode('hex')[::-1]+"6E6F".decode('hex')[::-1]'urlmon'

The logs raise another Miasm error (again) during the execution:

loc_00000000400002D9:0x400002d9PUSHADXOREAX,EAXMOVEDX,DWORDPTRFS:[EAX+0x30]MOVEDX,DWORDPTR[EDX+0xC]MOVEDX,DWORDPTR[EDX+0x14]MOVESI,DWORDPTR[EDX+0x28]XOREDI,EDIXOREAX,EAXLODSBINCESITESTEAX,EAXJZloc_0000000040000300:0x40000300->c_to:loc_0000000040000300:0x40000300c_next:loc_00000000400002F3:0x400002f3WARNING:address0x30isnotmappedinvirtualmemory:...assert(self.get_exception()==0)AssertionError

There is an other access outside of the sandbox virtual memory at address0x30during the execution of this basic block. Note that we don’t known the exactaddress of the faulty instruction in this case. We can retrieve it by launchingthe script in interactive mode:

python-irun_sc.pyshellcode.bin...assert(self.get_exception()==0)AssertionError>>>hex(myjit.cpu.EIP)'0x400002dcL'

The faulty instruction is:

MOVEDX,DWORDPTRFS:[EAX+0x30]

Here,EAX is0x0, so the memory lookup is at address0x30 which is notmapped in memory. But there is a trick: thereal memory lookup uses thesegment selectorFS. By default, Miasm doesn’t emulate segmentation, whichexplains the previous outcome.

As we are onWindows, we know that this code is a lookup of thePEB(Process Environment Block) so we have two choices:

  1. We can map a memory page at address0x30 in which we insert a fakePEBdata.
  2. The other solution is to assign a value to the segment selectorFS and acorresponding segment descriptor with a custom base address. This baseaddress will be a fresh memory area filled with a fakePEB structure. Youalso have to activate the segmentation support in Miasm.

Painful isn’t it? Fortunately, Miasm implements a minimalWindows structuresemulation (miasm2.os_dep.win_api_x86_32_seh.py).

ThePEB contains interesting information like the linked list of the modulesmapped in memory by the loader. By default, if you activate theWindowsstructures emulation, Miasm will create aPEB with dummy information relatedto it’s loader. However, you can force Miasm to load specific modules and usethem to create a consistent loaded modules linked list (see below).

To load all this information automatically, you can use the classmiasm2.analysis.sandbox::Sandbox_Win_x86_32 which takes a binary’s path asinput, and sets up a minimal environment like the one previously described. Anexample is inmiasm/example/jitter/sandbox_pe_x86_32.py.

ThePE binary given to the sandbox isiexplorer.exe (the exploittarget). This binary will serve as ahost and will be used by Miasm to buildthe loader structure. Module dependencies will be loaded as well (they have tobe present in the./win_dll directory).

As the shellcode doesn’t interact with this binary, we can also load a dummybinary (likecalc.exe). Last but not least, if you don’t havecalc.exe,you can build a valid executable from the shellcode usingelfesteem:

importsysfromelfesteemimportpe_init# Get the shellcodedata=open(sys.argv[1]).read()# Generate a PEpe=pe_init.PE(wsize=32)# Add a ".text" section containing the shellcode to the PEs_text=pe.SHList.add_section(name=".text",addr=0x1000,data=data)# Set the entrypoint to the shellcode's addresspe.Opthdr.AddressOfEntryPoint=s_text.addr# Write the PE to "sc_pe.py"open('sc_pe.exe','w').write(str(pe))

In the next part, we will base our script onmiasm/example/jitter/sandbox_pe_x86_32.py. This script is used to load abinary and create a working environment. Here are the default options:

$ python run_sc.py  -husage: run_sc.py [-h] [-a ADDRESS] [-x] [-b] [-z] [-d] [-g GDBSERVER] [-j JITTER]              [-q] [-i] [-s] [-o] [-y] [-l] [-r]              filenamePE sandboxerpositional arguments:  filename              PE Filenameoptional arguments:  -h, --help            show this help message and exit  -a ADDRESS, --address ADDRESS                        Force entry point address  -x, --dumpall         Load base dll  -b, --dumpblocs       Log disasm blocks  -z, --singlestep      Log single step  -d, --debugging       Debug shell  -g GDBSERVER, --gdbserver GDBSERVER                        Listen on port @port  -j JITTER, --jitter JITTER                        Jitter engine. Possible values are: tcc (default),                        llvm, python  -q, --quiet-function-calls                        Don't log function calls  -i, --dependencies    Load PE and its dependencies  -s, --usesegm         Use segments  -o, --load-hdr        Load pe hdr  -y, --use-seh         Use windows SEH  -l, --loadbasedll     Load base dll (path './win_dll')  -r, --parse-resources                        Load resources

Here, the interesting options are:

  • -s (--usesegm) to use segmentation
  • -y (--use-seh) to generate minimalistic windows structures (yes, thename is sadly chosen)
  • -l (--loadbasedll) to arbitrarily load a bunch of modules/dll (more onthis later)
  • -b (--dumpblocs) to display a block trace.

As mentioned before, we can force the libraries to be loaded from a default list:

# Sanbox.ALL_IMP_DLLALL_IMP_DLL=["ntdll.dll","kernel32.dll","user32.dll","ole32.dll","urlmon.dll","ws2_32.dll",'advapi32.dll',"psapi.dll",]

We will modify the script to load and start the execution at the shellcodeaddress:

...# Parse argumentsparser=Sandbox_Win_x86_32.parser(description="PE sandboxer")parser.add_argument("filename",help="PE Filename")# Get the shellcode from the second argumentparser.add_argument("shellcode",help="shellcode file")options=parser.parse_args()# Create sandboxsb=Sandbox_Win_x86_32(options.filename,options,globals())# Load the shellcodedata=open(options.shellcode).read()run_addr=0x40000000sb.jitter.vm.add_memory_page(run_addr,PAGE_READ|PAGE_WRITE,data)sb.jitter.cpu.EAX=run_addr# Runsb.run(run_addr)

Here is the command line to run this script (here we usebox_upx.exe as host executable):

python-irun_sc.py-b-s-l-ymiasm/example/samples/box_upx.exeshellcode.bin

Note that you will need a directory namedwin_dll containingDLLs (forinstance, the ones of windows XP). Here is the output:

[INFO]:Loadingmodule'ntdll.dll'[INFO]:Loadingmodule'kernel32.dll'[INFO]:Loadingmodule'user32.dll'[INFO]:Loadingmodule'ole32.dll'[INFO]:Loadingmodule'urlmon.dll'[INFO]:Loadingmodule'ws2_32.dll'[INFO]:Loadingmodule'advapi32.dll'[INFO]:Loadingmodule'psapi.dll'[WARNING]:Createdummyentryfor'msvcrt.dll'[WARNING]:Createdummyentryfor'iertutil.dll'[WARNING]:Createdummyentryfor'oleaut32.dll'[WARNING]:Createdummyentryfor'rpcrt4.dll'[WARNING]:Createdummyentryfor'shlwapi.dll'[WARNING]:Createdummyentryfor'gdi32.dll'[WARNING]:Createdummyentryfor'ws2help.dll'INFO:Addmodule0''INFO:Addmodule400000'box_upx.exe'INFO:Addmodule45180000'urlmon.dll'INFO:Addmodule7c800000'kernel32.dll'INFO:Addmodule77da0000'advapi32.dll'INFO:Addmodule7c910000'ntdll.dll'INFO:Addmodule774a0000'ole32.dll'INFO:Addmodule719f0000'ws2_32.dll'INFO:Addmodule76ba0000'psapi.dll'INFO:Addmodule7e390000'user32.dll'INFO:Ldr342f00

Here, Miasm tries to load the required modules (ntdll.dll, …). Some ofthem are present inwin_dll/ and are loaded, some are not. For thosewhich are not present, Miasm will create a dummy base address and dummy exportedaddresses (near0x7111XXXX). Next, Miasm loads the host binary(box_upx.exe). Here is an extract of the block trace:

...PUSH0xEC0E4E8EPUSH0x6E2BCA17CALLloc_00000000400002CA:0x400002ca->c_next:loc_0000000040000076:0x40000076loc_00000000400002CA:0x400002caPOPECXCALLloc_00000000400002D9:0x400002d9->c_next:loc_00000000400002D0:0x400002d0loc_00000000400002D9:0x400002d9PUSHADXOREAX,EAXMOVEDX,DWORDPTRFS:[EAX+0x30]MOVEDX,DWORDPTR[EDX+0xC]MOVEDX,DWORDPTR[EDX+0x14]MOVESI,DWORDPTR[EDX+0x28]

This is the part which extracts imports from thePEB structure. Theshellcode finds its dependencies using function andDLL hashes (0xEC0E4E8Eand0x6E2BCA17). This code is typical for a trained eye:

LODSBTESTAL,ALJZloc_0000000040000342:0x40000342->c_to:loc_0000000040000342:0x40000342c_next:loc_000000004000033B:0x4000033bloc_0000000040000337:0x40000337TESTAL,ALJZloc_0000000040000342:0x40000342->c_to:loc_0000000040000342:0x40000342c_next:loc_000000004000033B:0x4000033bloc_000000004000033B:0x4000033bROREDI,0xDADDEDI,EAX

This code snippet walks theInLoadOrderModuleList linked list and finds amodule whose name’s hash matches the provided one. In this case, it will bekernel32.dll. Then it walks the export directory of this module the same wayto find an expected export. For the moment, we don’t know the searched functionbut if we look at the next logs:

ADDEAX,EBPMOVDWORDPTR[ESP+0x1C],EAXPOPADRET0x8loc_00000000400002D6:0x400002d6PUSHECXJMPEAX[INFO]:kernel32_LoadLibraryA(dllname=0x13ffe0)retaddr:0x40000076loc_0000000040000076:0x40000076

We have an information from the jitter that the code called the functionLoadLibraryA from the modulekernel32. This is the resolvedfunction. But how does Miasm know this?

In fact each time you load a library in memory, Miasm adds a breakpoint on eachof its exported addresses, and remembers the relation between the address andthe exported name. When the emulated program counter reaches one of thesebreakpoints, the emulation is paused. Miasm then tries to find a Python functionwhose name has the formModuleName_ModuleFunction and calls it.

In this case, we implement a minimalistic set ofWindows functions which, oncecalled, will have the same side effects on the sandbox as the real function onthe registers/memory. For example, if a binary callsrand, we can force itsreturn value to make it less random:

defmsvcrt_rand(jitter):ret_ad,_=jitter.func_args_cdecl(0)jitter.func_ret_stdcall(ret_ad,0x666)

Those default functions are defined in the modulemiasm2.os_dep.win_api_x86_32. Here is the code ofLoadLibraryA:

defkernel32_LoadLibraryA(jitter):# jitter.func_args_stdcall is a helper which knows the current calling# convention (stack based here), and will unstack the return address# and one parameter (dllname). dllname is a pointer to the dll name# string in memory.ret_ad,args=jitter.func_args_stdcall(["dllname"])libname=get_str_ansi(jitter,args.dllname,0x100)log.info(libname)ret=winobjs.runtime_dll.lib_get_add_base(libname)log.info("ret%x",ret)# jitter.func_ret_stdcall is another helper which will set the program# counter to the value ret_ad and the return value (EAX in this# convention) to ret.jitter.func_ret_stdcall(ret_ad,ret)

The jitter will then resume the execution to the fresh program counter, and theexecution resumes as if theWindows function had been called. This mechanismallows us to script or simulate any function in Python!

By the way, if you implement the previous two helpers forARM, you can usethe same Python code to simulateLoadLibraryA onWindows for thisarchitecture.

Note that if you want to get the module name, you can modify the script to logit, or put a breakpoint at0x40000076 to stop the execution and retrieve themodule name manually. Here is the modification:

defstop_exec(jitter):returnFalsesb.jitter.add_breakpoint(0x40000076,stop_exec)# Run the shellcodesb.run(run_addr)

And the live analysis:

python-irun_sc.py-b-s-l-ymiasm/example/samples/box_upx.exeshellcode.bin...>>>sb.jitter.get_str_ansi(0x13ffe0)'urlmon'

Party Hard

What’s next? Another crash, obviously!

loc_0000000040000083:0x40000083PUSHEAXPUSH0x6PUSH0x0PUSH0xDC8061BPUSH0x2E773AE6CALLloc_00000000400002CA:0x400002ca->c_next:loc_0000000040000097:0x40000097Traceback(mostrecentcalllast):...raiseValueError('unknown api',hex(jitter.pc),repr(fname))ValueError:('unknown api','0x774c1473L',"'ole32_CoInitializeEx'")

What happened here? The function at address0x400002ca is the one whichresolves a function by hash. So the code resolved another function and tries tocall it. By the way, if you think that the log output is not really humanfriendly, you can add some symbols to enhance it. For exemple:

...# Links address 0x400002ca to the label name resolve_by_hashsb.jitter.ir_arch.symbol_pool.add_label('resolve_by_hash',0x400002ca)# Run the shellcodesb.run(run_addr)

Result:

loc_0000000040000083:0x40000083PUSHEAXPUSH0x6PUSH0x0PUSH0xDC8061BPUSH0x2E773AE6CALLresolve_by_hash:0x400002ca->c_next:loc_0000000040000097:0x40000097Traceback(mostrecentcalllast):

That’s a bit clearer. So what’s the problem now? Miasm reaches an internalbreakpoint on the functionole32_CoInitializeEx. Unluckily, this function isnot implemented in the default library. But are we really stuck here? Notreally. If you read theMsdn documentation, this function is used to initializeaCOM object and returns0x1 if everything is ok. Fine, let’s implement aminimalistic function in our script. Don’t you have the feeling of reimplementing theWindows API using architecture independent code here?

defole32_CoInitializeEx(jitter):ret_ad,args=jitter.func_args_stdcall(["pvReserved","dwCoInit"])jitter.func_ret_stdcall(ret_ad,1)

WARNING: the function declaration position is important: it must be defined inthe scriptbefore the instanciation of the sanbox. This way, the declarationbelongs to theglobals(). The logs are now:

PUSH0xDC8061BPUSH0x2E773AE6CALLresolve_by_hash:0x400002ca->c_next:loc_0000000040000097:0x40000097[INFO]:ole32_CoInitializeEx(a=0x0,b=0x6)retaddr:0x40000097

Ok, now we have emulated the function. But there is more:

PUSH0x91AFCA54PUSH0x6E2BCA17CALLresolve_by_hash:0x400002ca->c_next:loc_00000000400000B0:0x400000b0[INFO]:kernel32_VirtualAlloc(lpvoid=0x0,dwsize=0x1000,alloc_type=0x1000,flprotect=0x40)retaddr:0x400000b0

The shellcode resolved and called the functionkernel32_VirtualAlloc, whichis already implemented in Miasm library. Then there is a call to anotherfunction:

PUSH0xCFD98161PUSH0x6E2BCA17CALLresolve_by_hash:0x400002ca->c_next:loc_00000000400000C0:0x400000c0[INFO]:kernel32_GetVersion()retaddr:0x400000c0loc_00000000400000C0:0x400000c0CMPAL,0x6JLloc_00000000400000D4:0x400000d4

Hey, it seems the shellcode has a different behavior depending on theWindowsversion. Note that defining a customkernel32_GetVersion will override theone defined in Miasm library, and so you can play with its behavior to see theimpact on the shellcode. And now, another crash:

PUSH0xD7834A7EPUSH0xAD74DBF2CALLresolve_by_hash:0x400002ca->c_next:loc_0000000040000184:0x40000184Traceback(mostrecentcalllast):raiseValueError('unknown api',hex(jitter.pc),repr(fname))ValueError:('unknown api','0x7c936102L',"'ntdll_swprintf'")

The script tries to resolve and executentdll_swprintf. This one will be abit harder. First step, let’s only dump the format string:

defntdll_swprintf(jitter):ret_ad,args=jitter.func_args_stdcall(["dst","pfmt"])fmt=jitter.get_str_unic(jitter,args.pfmt)printrepr(fmt)returnFalse

Here is the output:

PUSH0xD7834A7EPUSH0xAD74DBF2CALLresolve_by_hash:0x400002ca->c_next:loc_0000000040000184:0x40000184[INFO]:ntdll_swprintf(dst=0x20000000,pfmt=0x13ffc8)retaddr:0x40000184'%S'

As the format string is really simple, let’s implement a minimalistic versionofswprintf:

defntdll_swprintf(jitter):ret_ad,args=jitter.func_args_stdcall(["dst","pfmt"])fmt=jitter.get_str_unic(args.pfmt)print"FMT:",repr(fmt)iffmt=="%S":psrc=jitter.pop_uint32_t()src=jitter.get_str_ansi(psrc)out="%s"%srcelse:raiseRuntimeError("unknown fmt%s"%fmt)print"OUT:",repr(out)jitter.set_str_unic(args.dst,out)# Returns the string len in wchar unitjitter.func_ret_stdcall(ret_ad,len(out)/2)

Let’s have a look at the new output:

PUSH0xD7834A7EPUSH0xAD74DBF2CALLresolve_by_hash:0x400002ca->c_next:loc_0000000040000184:0x40000184[INFO]:ntdll_swprintf(dst=0x20000000,pfmt=0x13ffc8)retaddr:0x40000184FMT:'%S'OUT:'hXXp://efyjlXXXXXXXXXXXXXXXXXXin.net/fXXXXXXXXXXXXXXX8867XXXX5'loc_0000000040000184:0x40000184...PUSHESIPUSHEDIPUSHECXCALLDWORDPTR[EBP+0xFFFFFFFC]->c_next:loc_0000000040000161:0x40000161Traceback(mostrecentcalllast):raiseValueError('unknown api',hex(jitter.pc),repr(fname))ValueError:('unknown api','0x451b65b3L',"'urlmon_URLDownloadToCacheFileW'")

Note: we deliberately changed the output of the script to avoid being flagged as a bad host.

Here is a minimalistic implementation ofURLDownloadToCacheFileW:

...defurlmon_URLDownloadToCacheFileW(jitter):ret_ad,args=jitter.func_args_stdcall(["lpunkcaller","szurl","szfilename","ccfilename","reserved","pbsc"])url=jitter.get_str_unic(args.szurl)print"URL:",urljitter.set_str_unic(args.szfilename,"toto")jitter.func_ret_stdcall(ret_ad,0)

This will inform the shellcode we have correctly downloaded a binary and storedit in a file namedtoto. And here is the final log:

PUSHEDIPUSHECXPUSHEAXPUSHEAXPUSHEAXPUSHEAXPUSHEAXPUSHEAXPUSHEAXPUSHDWORDPTR[EBP+0x8]PUSH0x16B3FE88PUSH0x6E2BCA17CALLresolve_by_hash:0x400002ca->c_next:loc_00000000400002C5:0x400002c5Traceback(mostrecentcalllast):raiseValueError('unknown api',hex(jitter.pc),repr(fname))ValueError:('unknown api','0x7c802336L',"'kernel32_CreateProcessW'")

Look at the first argument:

>>>sb.jitter.get_str_unic(sb.jitter.get_stack_arg(1))'toto'

The shellcode tries to execute the freshly downloaded binary.

Final words

First of all, congratulations to the readers who reached this point: that was abig post. We have done a dynamic analysis of a shellcodeà la try’n diestyle. You have a good idea of Miasm’s internals as well. I admit the ‘cost’ fora Miasm’s newcomer is a bit expensive, and I realized it again while writingthose lines, but you may end up with a flexible tool to do such analysis. As aremark, try to modify thekernel32_myCreateProcess to make it fail. Theshellcode behavior is modified. This type of approach is clearly not thesolution to all problems, but it can help on specific analysis. Note the scriptcan also be used on shellcodes belonging to the same campaign. As a bonus, youhave a second shellcode in the linked archive: Give it a try!

Posted by serpilliere
Tags:disasm,emulation,IR

Recent Posts

Search

Contact:

    contact at miasm re

Documentation: