MadProtect, not that mad

Data publikacji: 03/02/2016, mak

Some weeks ago we stumbled on a packer that our tools could not break. Surprisingly, this is actually not that common since most of the malware in the wild uses some sort of RunPE technique which is relatively trivial to break using simple memory tracing.

MadProtect is not any different, it looks like a “HackingForums-grade” packer – nevertheless our tools failed to handle it properly. At first we did not look into the original binary, which was a mistake that could have saved us a lot of unnecessary effort into debugging our code. Instead, it turned out to be enough to look at the logs from tracer and binaries it produced.

The dumped binaries looked somewhat weird with a bunch of nops and other junk code which seems to do nothing. What struck us as odd was the regularity of nop-blocs: all of them seemed to be 0×10 bytes long (yes, we know we cannot count  ), and we can see a lot of 0×10 bytes writes in tracer logs: coincidence?

2015-08-31 19:06:06,561 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]called[pre] NtWriteVirtualMemory(poudf22ouytbbaa.exe,41d060,16) src: 394e60
2015-08-31 19:06:06,576 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]Memdump hash[41d060 – 16]: b5590be427a581e72c947ccd59accf38
2015-08-31 19:06:06,779 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]called[pre] NtProtectVirtualMemory(pid:1744,41d070,16,RWX —)
2015-08-31 19:06:06,796 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]called[pre] NtProtectVirtualMemory(pid:1744,41d000,4096,RWX —)
2015-08-31 19:06:06,811 [dbg] INFO: NtWriteVirtualMemory
2015-08-31 19:06:06,826 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]called[pre] NtWriteVirtualMemory(poudf22ouytbbaa.exe,41d070,16) src: 394e70
2015-08-31 19:06:06,826 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]Memdump hash[41d070 – 16]: b5590be427a581e72c947ccd59accf38
2015-08-31 19:06:06,858 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]called[pre] NtProtectVirtualMemory(pid:1744,41d080,16,RWX —)
2015-08-31 19:06:07,046 [dbg] INFO: [poudf22ouytbbaa.exe – 1060][1044]called[pre] NtProtectVirtualMemory(pid:1744,41d000,4096,RWX —)

We assumed not and proceeded with this in mind. The first theory was it was used to clear out code from the packer but it doesn’t make much of a sense because it is a concise binary, dumped based on some heuristics. So lets try to explore another approach: the binary we are seeing is merely a skeleton for a real code, that is being written on top of those nop-blocks. This is almost cool.

The problem is the size of writes: we tend to ignore every remote-memory-write (ie using ntdll!NtWriteVirtualMemory) that is smaller than some threshold, so being something about hundreds of bytes, some re-coding was required!

During this rewrite we found out that those memory writes are too concise to be used as bunch of patches, it was just one block being written 0×10 bytes at a time: perhaps some block-crypto was in play? Problem solved, just dump those writes and concatenate together and from the concatenation obtain a valid payload. What should work in theory, collapsed in coding, there was always some bytes missing! So we looked inside the binary instead …

Inside IDA, first we notice that there is the same nop-block and junk code as in dropped bin, so it’s just obfuscation. Looking around we can’t find any meaningful API calls and strings so API is resolved dynamically and strings are encoded somehow. Encryption scheme looks kind of strange so let’s not go there yet. Fortunately API resolving is nicely packed in one function that takes only one integer. It’s perfect for Appcall mechanism: just fire IDA debugger skip some initialization stuff and resolve all functions like this:

Python>resolve_api = Appcall.proto(„resolve_api”,”int __cdecl resolve_api(int);”)
Python>for i in range(20): print Name(resolve_api(i))

With clear API calls we clearly see there’s not much going on: this is a simplest RunPE possible. One thing that stands out is that memory which will be written is decrypted on-fly one chunk at a time. Chunk is obviously 0×10 bytes … Ok let’s delve into encryption, quick scan for crypto with IDAscope revealed constants characteristic to AES, but the code doesn’t look like AES. Let’s go out on a limb and assume its AES, but where is the key? Looking at IDA doesn’t yield much, mostly because decompilation is somewhat a kind of mess.

But … there’s this odd looking loop for 0×0f to 0×4f: and the same loop is near the API resolution, care to make another guess? Yes, this is the key!

So we have the AES encrypted payload: let’s write a decoder for this. We know the key, but we don’t know where our payload is, and scanning for opcodes that are using it is not the best idea, as the code leaves much to be desired. Lets try something else: take the first chunk of data, when decrypted it’s part of an MZ header and there is a big chance it won’t change anytime soon.

This gives us quite a unique pattern we can search for. If in doubt we can always decrypt first 0×400 bytes and check if this is a real PE header


To wrap up all we have to do is put it all into a script and run it. It will reveal a similar binary which can be decoded by the same script… and the final payload is … Netwire.

binary: 989b29681f22c0c7561e441bbf6cb64c,
password: 36b&^%rUmLV8FN#{}r\”#V)}Hc`$?}j,
filename: ESET-%Rand%,
reg-key: avast,
mutex: avast,
urls: [
port: 3838
port: 3837
flags: [
dir-path: %AppData%\\Logs\\1\\2\\,
type: netwire,
yara_hits: [
view raw netwire.json hosted with ❤ by GitHub

Here are some hashes,


Yara rule:

rule MadProtect : packer { meta: author = „mak” strings: $enc_hdr = { 23 59 90 70 e9 c1 ec 82 b4 87 b3 4e 03 10 6c 2e} $key_loop0 = { B0 0F 88 01 04 02 41 3C 4F 72 F7 } $key_loop1 = { B0 0F EB 02 [2] 01 04 02 41 3C 4F 72 F7 } $key_loop2 = { B0 0F EB 03 [3] 01 04 02 41 3C 4F 72 F7 } $key_loop3 = { B0 0F EB 04 [4] 01 04 02 41 3C 4F 72 F7 } $key_loop4 = { B0 0F EB 05 [5] 01 04 02 41 3C 4F 72 F7 } $key_loop5 = { B0 0F EB 06 [6] 01 04 02 41 3C 4F 72 F7 } $key_loop6 = { B0 0F EB 07 [7] 01 04 02 41 3C 4F 72 F7 } $key_loop7 = { B0 0F EB 08 [8] 01 04 02 41 3C 4F 72 F7 } $key_loop8 = { B0 0F EB 09 [9] 01 04 02 41 3C 4F 72 F7 } $key_loop9 = { B0 0F EB 0a [10] 01 04 02 41 3C 4F 72 F7 } $key_loop10 = { B0 0F EB 0b [11] 01 04 02 41 3C 4F 72 F7 } $key_loop11 = { B0 0F EB 0c C 4F 72 F7 } $key_loop12 = { B0 0F EB 0d C 4F 72 F7 } $key_loop13 = { B0 0F EB 0e C 4F 72 F7 } $key_loop14 = { B0 0F EB 0f C 4F 72 F7 } $pdb = „C:\\Users\\prick\\Documents\\Visual Studio 2010\\Projects\\MadProtect\\Release\\MadProtect.pdb” fullword nocase $s0 = „CoInitializeEx failed: %x” fullword $s1 = „CoInitializeSecurity failed: %x” fullword condition: $enc_hdr or (1 of ($key_loop*)) and (1 of ($s*) or $pdb)

view raw mp.yara hosted with ❤ by GitHub

and of course the decoding script.

Now let’s get back to work ;)