Win32.Neurevt, circulating under the name Beta Bot is an HTTP bot that entered in the underground market in the first part of 2013.

As we can see the information from the Sales Thread, this piece of code which written in C++ has many functionalities. Among others, we found:

  • AV-Disabling
  • Bot Killer
  • Ring3 Rootkit
  • Custom Injection Techniques
  • Proactive Defense Mode

At less than €500, Beta Bot is sold relatively cheap, considering its vast feature list. It includes also standard features for today’s bots, like different DOS-attack methods, DNS Blocker, etc.

In this post, I hope to cover a deep analysis of how Beta Bot v1.7.0.1 – which has been leaked – works from top to bottom. Its custom methods of injection, its sandbox/VM detection, its persistence mechanism from removal or termination and also a detailed look at this bot’s infrastructure, communication protocol and encryption schemes.

Here is a screenshot of the command and control administration panel:

The infection vector is based mainly in exploits kits such as Nuclear or Silent Exploit Pack.

Tools and Downloads:

  • OllyDBG / IDA Pro / PETools / Process Explorer.


Let’s go, open the sample under OllyDBG debugger, the first step the malware is doing is to call GetModuleHandle to get the module handle, then it searches for some bytes from the base address. When found, it calls VirtualProtect with PAGE_EXECUTE_READWRITE attribute, and then performs some XOR encryption and call VirtualAlloc that returns 00350000.

The second step is the CALL at VA 004011A9 which is responsible for unpacking the malware. Just step over it and see at the dump windows at VA 00350000 the ‘MZ’ magic appearing. We can dump it with right-click -> Backup -> save data to file. The file generated is a full dump of the memory region.

Afterwards, the malware is looking again in the unpacked code for some hardcoded DWORDS and when found, saves the offset of where these values are located and process the CALL at 0040124B with that offset as parameter. Next, it will call again VirtualAlloc, copy the PE header first from the previous memory allocated space, and then copy section by section and transfer control then to the decrypted code.

Then, the imports are fixed so the malware can use the imported API’s. To resolve the import addresses, BetaBot use the PEB_LDR_DATA structure, to refresh your memory, the PEB (Process Environment Block) is pointed to by the TIB (Thread Information Block), which is always located at FS:[0]. One of the PEB entries is a pointer to a structure called PEB_LDR_DATA.

This structure contains information about all of the loaded modules in the current process. At offset 0x1C of PEB_LDR_DATA is the pointer of InInitializationOrderModuleList along the link list of InIntializationOrderModuleList where you can find loaded DLL, Betabot is looking for both kernel32.dll and ntdll.dll, after it them, for instance, offset 0x08 hold the base address of kernel32.dll in memory, offset 0x3C is the PE header of kernel32.dll and finally offset 0x78 of PE header is the pointer to export function address table.

Given the pointer to the EAT, you will get inside a loop that parses the EAT to look for API’s addresses. Furthermore, instead of hardcoding APIs names which BetaBot is interested to import, it uses the Adlerr-32 algorithm and hashes the: “dll_name + “.” + function_name” and compare the result to hardcoded hashed value.

As far as I am concerned, the other DLLs like secur32.dll and crypt32.dll are loaded with LoadLibrary. You will get inside a loop that loads all these DLLs, once you’re done, you could see the IAT built.

Then at VA 00436427, there are a few hashes not stored in the table, including:

  • KiFastSystemCall
  • KiIntSystemCall

Also, some kernel32 API
is loaded with GetProcAaddress and two more DLLs
loaded with LoadLibrary:

  • “InitializeProcThreadAttributeList”
  • “UpdateProcThreadAttribute”
  • “ChangeWindowMessageFilter”
  • “CreateProcessWithTokenW”
  • “ObtainUserAgentString”
  • “ObtainUserAgentString”
  • “NetUserGetInfo”
  • “Netapi32.dll”
  • “Urlmon.dll”

Let’s come back to our dumped file. Fire up IDA Pro and load our dumped file. At the strings window, locate a list of 14 DLLs (figure 1). Click on the latest DLL name, then scroll down until you see (figure 2). They are of the format hash: DWORD/offset: DWORD / DWORD, but IDA will likely show them as 8 db and an offset.

Now, load the script below, which will set the correct API names in your sample to make static analysis easier. I uploaded for you the IDB file if you want to follow the analysis in my database file.

# Hash function setup for Beta Bot v1.7




# This script will build a hash list of all the functions in all DLLs used by

# Beta Bot, and then set the names for all the offsets in the sample in IDA.


# Beta Bot uses another field to determine the DLL to which the hash

# belongs, but where there’s very few collisions this value wasn’t used.


# There are a few hashes not stored in the table, including:

# * KiFastSystemCall

# * KiIntSystemCall

# If you see these or others in the code they can be entered manually.


# Requires pefile










# These follow the shortly after a list of dlls in the .data section.


# They are of the format hash:32/offset:32/dword, but IDA will likely

# show them as 8 db and an offset. Convert some of the 8 db into 2 dd

# until you can see the list correctly. The result will look like:


# dd 4F2B0775h

# dd offset unk_43E064

# dd 0Ah


# dd 0C9250C68H

# dd offset word_43E06C

# dd 0Dh

# …


# start = 0x003785f0

# last = 0x00379d78

start =

last =

not start or
not last:

“Error: Must set start and last”


dlls = [
















sys32 = os.environ[‘WINDIR’] +

hash_fun(dll_name, fun_name):

    name = dll_name +
+ fun_name

    x =

    y =

    for letter in name:

        y = (y +
ord(letter)) %

        x = (x + y) %

    return (x<<16|y)

hashes = {}

for dll_name in dlls:

    dll = pefile.PE(sys32 + dll_name)

    for export in dll.DIRECTORY_ENTRY_EXPORT.symbols:


            h = hash_fun(dll_name,

            existing =

            if h in hashes:

                existing = hashes[h] +

            hashes[h] = existing +

# These hashes existed in my samples outside the normal list:

for h in [0x583D0800, 0x860E09F1, 0x856309FA, 0x7BB00997, 0x6bf308a4, 0x0B30B0B4E, 0x6BF308A4]:

    if h in hashes:

“%x -> %s”%(h, hashes[h])

unit = start

while (unit <= last):

    hashv = Dword(unit)

    offset = Dword(unit +

    if hashv in hashes:

“SET %x: %s”%(offset, hashes[hashv])

        MakeName(offset, hashes[hashv])

    unit +=

“Success !”

Bot Analysis:

BetaBot checks then if its code is lunched from firefox.exe, if true, it will load nspr4.dll / nss3.dll, these dlls are for network communication, hooking some APIs in this library will make malicious activities such as web injection possible. Following, it checks for Sandboxie by trying to load SbieDll.dll, also it will load mscoree.dll which is the Microsoft .NET Runtime Execution Engine.

Ethical Hacking Training – Resources (InfoSec)Ethical Hacking Training – Resources (InfoSec)

Afterwards, in the first hand, BetaBot uses the ZwQueryInformationProcess with ProcessDebugPort for debugger detection and in the second hand; it attempts to patch the ntdll.DbgBreakPoint with a NOP instead of INT 3. The rest of this function collects a whole lot of information about the system, machine, installed applications, etc.

char __usercall sub_40DB20<al>(int a1<eax>, int a2<ebp>)


int v2; // esi@1

HMODULE v4; // eax@10

v2 = a1;

*(_BYTE *)(a2
1) =

dwProcessId = GetCurrentProcessId();

if ( !sub_40E6A4(v2) )


InitializeCriticalSectionAndSpinCount((LPCRITICAL_SECTION)&unk_43F310, 0xFA0u);

*(_DWORD *)(a2
8) =

*(_DWORD *)(a2
12) =

if ( ZwQueryInformationProcess(1, 7, a2
8, 4, a2
12) >=
*(_DWORD *)(a2
8) )// ProcessDebugPort

*(_BYTE *)(a2
1) =
1; // Debugger Detected

hModNtdll = (int)GetModuleHandleA(“ntdll.dll”);

hModKernel32 = GetModuleHandleA(“kernel32.dll”);

hModUser32 = (int)GetModuleHandleA(“user32.dll”);

hModWs2_32 = (int)GetModuleHandleA(“ws2_32.dll”);

hModWininet = (int)GetModuleHandleA(“wininet.dll”);

GetSystemTime((LPSYSTEMTIME)(large_buffer +


SHGetFolderPathW(0, 32794, 0, 0, (LPWSTR)(large_buffer +

SHGetFolderPathW(0, 32808, 0, 0, (LPWSTR)(large_buffer +

SHGetFolderPathW(0, 32803, 0, 0, (LPWSTR)(large_buffer +

SHGetFolderPathW(0, 32781, 0, 0, (LPWSTR)(large_buffer +

SHGetFolderPathW(0, 32782, 0, 0, (LPWSTR)(large_buffer +

SHGetFolderPathW(0, 32807, 0, 0, (LPWSTR)(large_buffer +

SHGetFolderPathW(0, 32806, 0, 0, (LPWSTR)(large_buffer +

SHGetFolderPathW(0, 32810, 0, 0, (LPWSTR)(large_buffer +

// Returns bitmask of:

// |= 0x2 (dotnet version >= 2)

// |= 0x4 (Registry “HKEY_CLASSES_ROOT, jarfile\\shell\\open\\command” is present)

// |= 0x80 (Any of these is present: (HKCU\\Software{Sysinternals;mIRC;Hex-Rays;Immunity Inc;CodeBlocks;7-Zip;PrestoSoft;Nmap}, HKLMSoftwarePerl, HKCR{.vcproj;.5vw})

// |= 0x20 ((*(large_buffer+0x0A) == 0x20) and (OS != workstation5.1||5.2))

// |= 0x100 (SYSTEM_POWER_STATUS->BatteryFlag < 0x80 (High/Low/Critical/Charging)

// |= 0x20000200 ((workstation6.0||6.1||6.2||server6.1||6.2), and (ELEVATION_UAC_ENABLED == 1))

// |= 0x8 (Length of (REG_SZ) ValueData “HKEY_CURRENT_USERSoftwareValveSteamLanguage” > 1)

// |= 0x1 (Value of (REG_DWORD) ValueData of “HKEY_CURRENT_USERSoftwareClassesCLSID{E9157F45-692A-5640-B0F5-E52ADF2EED29}16560361CG1BIS” is 1)

// |= 0x40 ((MEMORYSTATUS.dwTotalPhys >= 0x1800) and (NumOfCPU >= 4))

// |= 0x400 (IP of most recent Remote Desktop connection is found: ValueData of “HKEY_CURRENT_USERSoftwareMicrosoftTerminal Server ClientDefaultMRU0”)

dwMiscInfo = sub_40CEBA();

// |= 1 (if current executable name is “explorer.exe”)

// |= 4 (sub_00160CAD())

// |= 2 (if GetModuleHandle(“iexplore.exe”||”firefox.exe”||”chrome.exe”))

// |= 8 (if GetModuleHandle(“mscoree.dll”))

dword_43F2F4 = sub_40D035();

// Fill szUserName[] (GetUserNameEx()) and

// szDefaultBrowser[] (“HKEY_CLASSES_ROOTHTTPshellopencommand”)


if ( sub_40D774(*(_WORD *)(large_buffer +
4)) ==
1 )

// Returns bitmask of:

// |= 0x4 (Folder “%userprofile%jagexcache” is present)

// |= 0x8 (Folder “%appdata%.minecraft” is present)

// |= 0x40 (Folder “%programfiles%League of Legends” is present)

// |= 0x1 (Length of (REG_SZ) ValueData “HKEY_CURRENT_USER, Software\\Valve\\SteamLanguage” > 1)

// |= 0x200 (Registry “HKEY_CLASSES_ROOT, jarfile\\shell\\open\\command” is present)

// |= 0x2 (Registry “HKEY_LOCAL_MACHINE, SOFTWARE\\Classes\\origin” is present)

// |= 0x10 (Registry “HKEY_CURRENT_USER, SOFTWARE\\Blizzard Entertainment” is present)

// |= 0x400 (Registry “HKEY_CURRENT_USER, SOFTWARE\\Skype” is present)

// |= 0x800 (Registry “HKEY_CURRENT_USER, SOFTWARE\\Microsoft\\VisualStudio” is present)

// |= 0x1000 (Registry “HKEY_CURRENT_USER, SOFTWARE\\VMware, Inc.” is present)

dwGames = sub_40D0AF();


dwGames =

dwK32GetMappedFileNameW = (int)GetProcAddress(hModKernel32, “K32GetMappedFileNameW”);

if ( !dwK32GetMappedFileNameW )


v4 = LoadLibraryA(“Psapi.dll”);

dwK32GetMappedFileNameW = (int)GetProcAddress(v4, “GetMappedFileNameW”);


memset(&stru_440028, 0, 24);

InitializeCriticalSectionAndSpinCount(&stru_440028, 0x3E8u);


// Fill impt_apps with this format:

// [USHORT 0x0212][UINT 0x0][UINT 0x2][WCHAR[0x104]=”skype.exe”]

// [USHORT 0x0212][UINT 0x70][UINT 0xA][WCHAR[0x104]=”origin.exe”]

// [USHORT 0x0212][UINT 0x70][UINT 0xA][WCHAR[0x104]=”steam.exe”]

// [USHORT 0x0212][UINT 0x0][UINT 0x1][WCHAR[0x104]=”winlogon.exe”]

// [USHORT 0x0212][UINT 0x0][UINT 0x1][WCHAR[0x104]=”csrss.exe”]

// [USHORT 0x0212][UINT 0x0][UINT 0x1][WCHAR[0x104]=”services.exe”]

// [USHORT 0x0212][UINT 0x0][UINT 0x1][WCHAR[0x104]=”lsass.exe”]

// [USHORT 0x0212][UINT 0x0][UINT 0x1][WCHAR[0x104]=”spoolsv.exe”]

// [USHORT 0x0212][UINT 0x70][UINT 0x1][WCHAR[0x104]=”dwm.exe”]


// Fill (WCHAR*)(large_buffer+0x1746) with “%userprofile%Low_00FEC012”

sub_4283B6(large_buffer +

if ( sub_40D774(*(_WORD *)(large_buffer +
4)) ==
1 )

// Patch ntdll.DbgBreakPoint() with “NOP” instead of “INT 3”


memset(large_buffer +
9754, 0, 60);

if ( v2 &&
*(_WORD *)v2 ==
*(_DWORD *)(v2 +
9758) )


sub_401493(large_buffer +
9754, v2 +
9754, 60);




// Fills large_buffer+0x1BFA with ValueData (BINARY, raw) of “HKEY_CURRENT_USER, Software\\Win7zip\\Uuid”

// Fills large_buffer+0x1C0A with ValueData (BINARY, hex) of “HKEY_CURRENT_USER, Software\\Win7zip\\Uuid”

if ( !sub_410623() )



if ( *(_BYTE *)(a2
1) ==
1 )


*(_DWORD *)(a2
12) = ZwQuerySection;

if ( __readfsdword(192) )

__writefsdword(192, *(_DWORD *)(a2




Ethical Hacking Training – Resources (InfoSec)Ethical Hacking Training – Resources (InfoSec)

Then I have seen some APIs to play around ACL stuff, trying to call BuildExplicitAccessWithName, SetEntriesInAclA and SetSecurityInfo to assign permissions to a specific folder with “EVEYONE” access. I have also come across another anti-analysis trick:

Moving on, we see another typical technique for detecting sandboxing environmentsThe malware reads the HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProductID key and grabs the product ID for this Windows version. It then compares this with the following list of product IDs:

Googling these values will shed some light on which product the sandbox detection is for. The above values are all linked to specific Windows installations in different sandboxes (Joebox, GFI, Kaspersky, CWSandbox, Anubis, etc.). Then, it checks for BitDefender AV by loading avcuf32.dll. If any of these checks return TRUE, BetaBot will stop execution by calling ZwTerminateProcess.