assembly, c-sharp, anti-sandbox, anti-antivirus, anti-debug, and malware research

Hello fellow readers!

You all are probably wondering what the hell I’ve been up to this past month. Lot’s of stuff. This post is all over the place with code and slides and malware and general wackiness. Rather than spreading it out over several blog posts, I decided to just get it all over with so I can focus on cooler things in the future.

I saw an interesting webinar on sandbox detection techniques employed by malware by Cyphort. They haven’t released their slides like they said they would, so here are the ones I took. These are cool and all, but I felt like I could contribute.

I read an awesome paper on bypassing antiviruses by employing a number of code based tricks. The idea behind them was that AV’s will skip binaries based on certain behaviors. One thing missing though – an AV will skip the “dropper” heuristic if the file ends in ‘.msi’. All the code I saw was in C/C++. I figured why not try and convert it to assembly? Next thing to do is make a patcher that can inject these into pre-compiled binaries. A future project perhaps? Anyways, I only did 2 before I lost interest. Read the article here.

;AV bypass 1
xor     eax, eax
db Caption "Joe"
db Text "Giron"
mov     edx, 5F5E100h
joe:
inc     eax
dec     edx
jnz     joe
cmp     eax, 5F5E100h
jnz     short urafag
push    0               ; MB_OK
push    offset Caption
push    offset Text   
push    0               ; hWnd 
call    MessageBoxA
urafag:
xor     eax, eax
retn

;AV bypass 1.5
; same as above, just using the loop instruction instead of branching conditionals
xor     eax, eax
db Caption "Joe"
db Text "Giron"
mov     ecx, 5F5E100h
joe: ; essentially do nothing
mov eax,10
mov ebx,20
xchg eax,ebx
loop joe
; now start code
xor eax,eax
xor ebx,ebx
push    0               ; MB_OK
push    offset Caption
push    offset Text   
push    0               ; hWnd 
call    MessageBoxA
retn

;AV bypass 2
push    ebx
push    edi
push    5F5E100h        ; bytes to alloc
push    40h             ; zero init
call    GlobalAlloc
mov     ebx, eax
test    ebx, ebx
jz      short cleanup
mov     edi, ebx
mov     eax, 0FFFFFFF1h
mov     ecx, 5F5E100h
rep stosb
push    0               ; MB_OK
push    offset Caption  ; "Joe"
push    offset Text     ; "Giron"
push    0               ; hWnd
call    MessageBoxA
push    ebx             ; memory handler
call    GlobalFree
cleanup:                             
xor     eax, eax
pop     edi
pop     ebx
retn

Feels good to put my crappy assembly skills to good use. Especially now that I figured out how to use inline assembly within C#. Sort of. The way it works is by utilizing delegates and cramming code inside an executable code page. Observe this piece of genius:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace InLineAsm
{
    static class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        delegate void JoesAntiDebuggery();

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, IntPtr flAllocationType, IntPtr flProtect);

		static byte[] opcodez = {
		0x55, 0x89, 0xE5, 0x31, 0xC0, 0xBA, 0x00, 0xE1, 0xF5, 0x05, 0x40, 0x4A, 0x75, 0xFC, 0x3D, 0x00,
		0xE1, 0xF5, 0x05, 0x75, 0x14, 0x6A, 0x00, 0x68, 0x12 ,0x70, 0x40, 0x00, 0x68, 0x0C, 0x70, 0x40,
		0x00, 0x6A, 0x00, 0xFF, 0x15, 0xD0, 0x80, 0x40, 0x00, 0x31, 0xC0, 0x68, 0x00, 0x70, 0x40, 0x00,
		0xE8, 0x3B, 0x00, 0x00, 0x00, 0x59, 0x31, 0xC0, 0x89, 0xEC, 0x5D, 0xC3 
		}
		// opcodes taken from disassembled program.
		/*
		__asm
		{
		xor     eax, eax
		mov     edx, 5F5E100h
		joe:
		inc     eax
		dec     edx
		jnz     joe
		cmp     eax, 5F5E100h
		jnz     short urafag
		}
		MessageBox(0,Text, Caption,0);
		__asm
		{
		urafag:
		xor     eax, eax
			
		}
		*/

		static IntPtr codeBuffer = VirtualAlloc(IntPtr.Zero, new UIntPtr((uint)opcodez.Length), (IntPtr)(0x1000 | 0x2000), (IntPtr)0x40);
		// EXECUTE_READWRITE, MEM_COMMIT | MEM_RESERVE
		Marshal.Copy(opcodez, 0,codeBuffer, opcodez.Length);
		JoesAntiDebuggery JoeDbg = (JoesAntiDebuggery)
		Marshal.GetDelegateForFunctionPointer(codeBuffer, typeof(JoesAntiDebuggery));
        
        static void Main(string[] args)
        {
           Console.Write("lol");
           JoeDbg();
        }

    }
}

It’s a thing of beauty – Assembly, C code, op codes / hex, delegates, and C#.

Moving on to what else I’ve been up to – pulling apart malwarez. This one piece gave me trouble for a few days. Namely because of the weird anti-debugging counter measure I encountered. I’m unsure if its even anti-debug as the conditions always seem to equate to false. I mean it’s easy to get around when you see it, but you can’t get around it automatically – you have to patch it. I even took a video of the weird behavior.

Took me some time, but I figured it out.

The following is the sequence called not 5 instructions after the entry point
anti-debuggery

sub_4017CF      proc near               
push    ebp
mov     edi, edx
add     edi, ebx
not     ebx
mov     ebp, esp
add     edi, ebx
add     esp, 0FFFFFF94h
mov     edx, ebx
inc     ebx
mov     ecx, esp
dec     ebx
mov     edi, eax
add     ecx, 48h
mov     ebx, ecx
dec     edi
cmp     eax, ecx
jz      short labelforyou
neg     edx
leave
not     edx
mov     eax, edi
neg     eax
leave
add     edx, edi
not     edx
retn
labelforyou:                          
leave
retn

The first thing you may notice about this procedure is the weird stack frame setup. Most of the time, the intro stack frame will be “push ebp” followed directly by “mov ebp, esp”. This one is different in that it plays with the registers a little before the “mov ebp, esp” assembly codes. You may also notice the 2 “leave” instructions at the end of the procedure as opposed to the 1 for the “labelforyou” conditional. The 2 “leave” instructions are why the program jumps to ExitThread. When you leave a stack frame twice and ‘ret’, any windows program jumps to ntdll.RtlExitUserThread. An interesting intrinsic way of quietly exiting without warning.

But what about the code that leads up to the ‘JZ’ branch and the 2 leaves? The comparison is EAX to ECX. Every time I run, EAX always ends up as 1 and ECX as some stack address. I’m postulating that the malware I grabbed was extracted from a dropper. That makes sense given the stack value / pointer points to nothing useful.

If you’re curious what the malware does, it attempts to download and run a ‘doc’ file from a russian host. Inside the ‘doc’ file is HTML code with a meta redirect to a host my DNS server can’t seem to find:
what it do

You can download the malware here. Pass in ‘infected’.

The other piece of malware I went through lacked a DOS sub. Most exe’s have this little DOS application inside that reads “this program cannot be run in DOS mode” and is placed at the start of an exe just in case someone attempts to run an exe on an old DOS system. Its a forward compatibility thing Microsoft does. Compare a normal exe to the binary:
w1

So how the hell do you remove the DOS sub and still maintain functionality? According to TinyPE, you do it in assembly via zeroing out the MZ header with the exception of the ‘e_magic’ field ‘MZ’ at the start and the ‘e_lfanew’ field value at the bottom. The ‘e_lfanew’ field is just a 4 byte offset to where the PE header is located.

mzhdr:
    dw "MZ"                       ; e_magic
    dw 0                          ; e_cblp UNUSED
    dw 0                          ; e_cp UNUSED
    dw 0                          ; e_crlc UNUSED
    dw 0                          ; e_cparhdr UNUSED
    dw 0                          ; e_minalloc UNUSED
    dw 0                          ; e_maxalloc UNUSED
    dw 0                          ; e_ss UNUSED
    dw 0                          ; e_sp UNUSED
    dw 0                          ; e_csum UNUSED
    dw 0                          ; e_ip UNUSED
    dw 0                          ; e_cs UNUSED
    dw 0                          ; e_lsarlc UNUSED
    dw 0                          ; e_ovno UNUSED
    times 4 dw 0                  ; e_res UNUSED
    dw 0                          ; e_oemid UNUSED
    dw 0                          ; e_oeminfo UNUSED
    times 10 dw 0                 ; e_res2 UNUSED
    dd pesig                      ; e_lfanew

But what about doing it to a pre-compiled binary? I just used CFF explorer and HXD. Jot down the ‘e_lfanew’ field offset and zero out the entries between the PE header, the MZ field, and the ‘e_lfanew’ field:
remove_dos_sub

The malware does code running modification and is surprisingly sophisticated, but this blog post is long enough. I’m done for now.

The next post will be much more interesting, however its unfinished and needs more research. Except it soon.

BiNUecP

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.