Analysis of functions used to encode strings in Flame (GDB script)

Published on 2012-06-21 14:00:00.

This article deals with another way to reuse ASM instruction based on GDB script. The idea is to launch a executable in a sandbox and control its flow.

Tools

GDB provides python API. We can use it thougth Cygwin on Windows. In the laster version GDB in Cygwin don’t contain the gdb server. But we can get an old package (6.8) provide the server. It’s still available on some mirror:

image

More information about “Scripting GDB using Python” is available on the GDB website: http://sourceware.org/gdb/onlinedocs/gdb/Python.html

Analysis of the decode functions in Flame

We will script gdb in order to decoded string in the main module flame

md5 : bdc9e04388bda8527b398a8c34667e18

Static analysis

In the main module of flamer, we can distinguish two functions which seem to be decoders:

This two functions are very similar, receive a structure as argument and return a string. The argument in function decode1 will be load into ebx and the argument will be load into esi in decode2.

image

Encoded strings addresses

To obtain a list of argument addresses, we can research in the code each function call and read addresse pushed on the stack. In this case, the argument are pushed in two way:

image

image

We make the following script to gather addresses into a file. The script use the disassembler library diStorm and pefile.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import distorm3
import pefile

addrs_f = open('listAddrDecode1.txt', 'w')

def getAddrArg(instruction,i,i_match):
  for j in range(i-1,i_match,-1):
    (offset,size,instr,hexdump) = instruction[j]
    if instr[:4]=='PUSH':
      res = ''
      res = instr.split(' ')[2]
      if res[:2] != "0x":
        return None
      return res 
    elif instr[:16] == "MOV DWORD [ESP],":
      return instr.split(' ')[3]
  return None

def main():
  pe = pefile.PE("bdc9e04388bda8527b398a8c34667e18")
  pe_memory_mapped = pe.get_memory_mapped_image()
  baseAddr = pe.OPTIONAL_HEADER.ImageBase
  sect_text = pe.sections[0]
  # Load code section
  code = pe_memory_mapped[\
             sect_text.PointerToRawData:\
             sect_text.PointerToRawData+sect_text.SizeOfRawData]
  instruction = distorm3.Decode(baseAddr+sect_text.PointerToRawData,\
                            code,distorm3.Decode32Bits)
  nb_instruction = len(instruction)
  i_match = 0
  addrs=[]
  for i in range(len(instruction)):
    (offset,size,instr,hexdump) = instruction[i]
    # Stop on decode1 call
    if instr == 'CALL 0x1000e431':
          # Attempt to get the argument
      addr=getAddrArg(instruction,i,i_match)
      if addr not in addrs:
                  addrs.append(addr)
      i_match=i
  addrs.sort()
  for addr in addrs:
          addrs_f.write("%s\n"%addr)

if __name__=="__main__":
  main()

Decode string dynamically with GDB

We make a GDB script which reads a addresses file, call decode1 function save the result.

#/usr/bin/python
# -*- coding:UTF-8 -*-
import gdb
import struct

class flameDecode1(gdb.Command):
  def __init__(self):
    super(flameDecode1, self).__init__("flameDecode1",\
                                        gdb.COMMAND_OBSCURE,\
                                        gdb.COMPLETE_NONE,\
                                        True)
  def invoke(sel, arg, from_tty):
    addrs_f = open("listAddrDecode1.txt","r")
    result_f = open("resultDecode1.txt","w")
    # Breakpoint after mov argument into ebx
    gdb.Breakpoint("*0x1000E442").silent=True
    # Breakpoint at the end of decode1 function
    gdb.Breakpoint("*0x1000E476").silent=True
    inferior = gdb.selected_inferior()
    addrs = addrs_f.readlines()
    for addr in addrs:
      addr = addr.replace("\n",'')
      #addr = addr.split(' ')[0]
      # Set EIP at the beginning of decode1 function
      gdb.execute("set $eip=0x1000E431")
      gdb.execute("c")
      # Replace ebx with the desired argument
      gdb.execute("set $ebx=%s"%addr)
      gdb.execute("c")
      string=""
      i=0
      # Save the decoded string
      result_addr=int(str(gdb.parse_and_eval("$eax")))
      while i<64:
        res=inferior.read_memory(result_addr, 1)
        if ord(res[0]) == 0:
          break
        else:
          string+=res[0]
        i+=1
        result_addr+=1
      string=string.replace("\n","\\n")
      string=string.replace("\t","\\t")
      string=string.replace("\r","\\r")
      result_f.write("%s %s\n"%(addr,string))
flameDecode1()

Before start gdb server in the sandbox, we make sure that the binary hasn’t DLL flaged with LordPE.

image

We start the gdb server:

user@malware-lu ~
$ gdbserver.exe host:1234 /cygdrive/c/bdc9e04388bda8527b398a8c34667e18
Process /cygdrive/c/bdc9e04388bda8527b398a8c34667e18 created; pid = 732
Listening on port 1234

We connect to the remote target and execute the GDB script:

(gdb) target remote 192.168.56.101:1234
Remote debugging using 192.168.56.101:1234
0x7c91120f in ?? ()
(gdb) source script/gdb_flame_decode1.py 
(gdb) flameDecode1
Breakpoint 1 at 0x1000e442
Breakpoint 2 at 0x1000e476
(gdb) shell head resultDecode1.txt
0x10256c54 vsmon.exe
0x10256c74 zlclient.exe
0x10256c98 ProductName
0x10256cb8 ZoneAlarm
0x10256cd8 vsmon.exe
0x10256cf8 zlclient.exe
0x10256d1c CurrentVersion
0x10256d40 SOFTWARE\Zone Labs\ZoneAlarm
0x10256d74 SOFTWARE\Zone Labs\ZoneAlarm\Registration\
0x10256db4 SOFTWARE\Zone Labs\ZoneAlarm\Registration\