Fork me on GitHub

Disassembling network traffic into asm instructions v0.3.0

The fresh released version of haka (v0.3.0) features a new module allowing to disassemble network data into instructions. This is useful to detect obfuscated shellcodes at network level as suggested in [raid05]1 for instance. The disassembler leverages on Capstone engine which supports several architecture (x86, arm, mips, etc.).

Here we will try to answer to question 8 of a well-known network forensic challenge. The challenge provides a pcap and asks to dump the shellcode used to exploit a vulnerability and then to provide the list of actions done by this shellcode.

Shellcodes are usually prepended with a nop sled as shown by the following figure. We assume in our first attempt that the nop sled is made of nop instructions (opcode 0x90).

shellcode

For those who had already done this challenge, we know, from question 5, that the attack exploits a vulnerability present in smb service (port 445). So we write a security rule that inspects only that flow. If a shellcode is detected, then we start the disassembly in order do dump its content:

local tcp_connection = require('protocol/tcp_connection')

local rem = require('regexp/pcre')
local re = rem.re:compile('%x90{100,}')

local asm = require('misc/asm')
local dasm = asm.new_disassembler('x86', '32')
dasm:setsyntax('att')

haka.rule {
    hook = tcp_connection.events.receive_data,
    options = {
        streamed = true,
    },
    eval = function (flow, iter, direction)
        if flow.dstport ~= 445 then return end
        if re:match(iter, false) then
            -- raise an alert
            haka.alert{
                description = "nop sled detected",
                targets = { haka.alert.service(flow.dstport, "smb") }
            }
            -- dump instructions following nop sled
            dasm:dump_instructions(iter)
        end
    end
}

We use a regular expression to detect a nop sled made of more than 100 nops and raise an alert in case of match. The pattern matching function updates the iterator which points at the beginning of the shellcode as shown by the previous figure. Then, we use our disassembler module in order to dump all instructions

haka@haka:~/workspace/haka/$ hakapcap dump-shellcode.lua attack-trace.pcap
...
alert: id = 1
    time = Tue Nov 25 10:09:01 2014
    description = nop sled detected
    targets = {
        service: 445, smb
    }

0x00000000 jmp      0x12                             eb 10
0x00000002 popl     %edx                             5a
0x00000003 decl     %edx                             4a
0x00000004 xorl     %ecx, %ecx                       33 c9
0x00000006 movw     $0x17d, %cx                      66 b9 7d 01
0x0000000a xorb     $-0x67, (%edx, %ecx)             80 34 0a 99
0x0000000e loop     0xa                              e2 fa
0x00000010 jmp      0x17                             eb 05
0x00000012 calll    2                                e8 eb ff ff ff
0x00000017 jo       0xffffffffffffffae               70 95
0x00000019 cwtl                                      98
...

For those who are familiar with shellcode obfuscation techniques, this is a polymorphic shellcode that starts with a decipher routine (see figure below).

polymorphic shellcode

The first instructions retrieve the address of the ciphered shellcode (starting at offset 0x17) in edx register. Then, we have a description loop that xor each byte of ciphered shellcode (of size 0x17d) with key 0x99.

In the following, we write a second security rule that will collect the shellcode in a buffer, decipher its content and then outputs the shellcode instructions:

local tcp_connection = require('protocol/tcp_connection')

local rem = require('regexp/pcre')
local re = rem.re:compile('%x90{100,}')

local asm = require('misc/asm')
local dasm = asm.new_disassembler('x86', '32')
dasm:setsyntax('att')

haka.rule {
    hook = tcp_connection.events.receive_data,
    options = {
        streamed = true,
    },
    eval = function (flow, iter, direction)
        if flow.dstport ~= 445 then return end
        if re:match(iter, false) then

            -- shellcode info extracted from previous dump
            local key = 0x99
            local decipher_routine_size = 0x17
            local shellcode_size = 0x17d

            -- fill shellcode buffer from stream
            local code = haka.vbuffer_allocate(0)
            local size = 0

            local sub
            for sub in iter:foreach_available() do
                code:append(haka.vbuffer_from(sub:asstring()))
                size  = size + #sub
                if size >= shellcode_size then break end
            end

            -- remove superfluous data
            code:sub(decipher_routine_size + shellcode_size):erase()

            -- decipher shellcode
            local byte
            for i = decipher_routine_size, #code-1 do
                byte = bit32.bxor(code:sub(i):asbits(0, 8), key)
                code:sub(i):setbits(0, 8, byte)
            end

            -- dump shellcode
            local start = code:pos(decipher_routine_size)
            dasm:dump_instructions(start)
        end
    end
}

The dump reveals the real instructions of the shellcode.

haka@haka:~/workspace/haka/$ hakapcap dump-shellcode.lua attack-trace.pcap
...
0x00000000 jmp      0x111                            e9 0c 01 00 00
0x00000005 popl     %edx                             5a
...
...
0x00000111 calll    5                                e8 ef fe ff ff
0x00000116 incl     %edi                             47
0x00000117 je       0x16a                            65 74 50
0x0000011a jb       0x18b                            72 6f
0x0000011c arpl     %ax, 0x64(%ecx)                  63 41 64
...
...
0x00000175 jae      0x1e7                            65 73 6f
0x00000178 arpl     %bp, 0x65(%ebx)                  63 6b 65
0x0000017b je       0x17d                            74 00

Note that the above code leverages on bit32 lib which is available only with Lua 5.2. As Haka interprets Lua code using LuaJit by default, you should select Lua option (-DBUILD=lua) while building Haka from source or find another way to xor bytes.

The shellcode starts by storing address 0x00000116 on edx register. This address seems to hold data. A slight modification to the end of the above security rule confirms our assumption:

--local start = code:pos(decipher_routine_size)
--dasm:dump_instructions(start)
local data = code:sub(decipher_routine_size + 0x116):asstring()
print(safe_string(data))

Haka outputs the list of primitives and libraries used by the shellcode. More precisely, this is a classical bindshell code:

  • GetProcAddress
  • CreateProcessA
  • ExitThread
  • LoadLibraryA
  • ws2_32
  • WSASocketA
  • bind
  • listen
  • accept
  • closesocket
  1. [raid05] Kruegel, C., Kirda, E., Mutz, D., Robertson, W., Vigna, G.: Polymorphic Worm Detection Using Structural Information of Executables. In: Recent Advanced in Intrusion Detection, pp. 207-226 (2005)