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