Artificial truth

The more you see, the less you believe.

[archives] [latest] | [homepage] | [atom/rss]

quick reversing of Wirenet
Mon 24 September 2012 — download

Thanks to r00tBSD, I've now got a access to the database of malware.lu. To celebrate this, I've try to reverse a dead-simple sample: the infamous BackDoor.Wirenet.1!

Preliminary analysis

$ file 9a0e765eecc5433af3dc726206ecc56e
9a0e765eecc5433af3dc726206ecc56e: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=0xeb836a1de23ce2cbe86a30064bc20e9f2c8b024c, stripped

Ho, shared libs, nice.

The string command give us a lot of informations :

  • The binary doesn't seems to be packed!?
  • AES and RC4 for crypto
  • Password stealing for Mozilla Firefox/Thunderbird/Seamonkey, Pidgin, Opera and Chromium?
  • Xorg stuffs, likely screenshots
  • Keylogger
  • A remote shell using bash is available, or sh if not.
  • Autostart/persistence
  • SOCKS4/5 proxy
  • No IP/login/filename/... in cleartext
$ strings 9a0e765eecc5433af3dc726206ecc56e | grep cp

This command give a quick overview of wirenet's capabilities.

Functionalities overview

A quick look into the disassembly code tells us that the malware is also able to control the mouse, download/upload/execute/rename/move files, update/uninstall itself, list/kill processes, get a windows list, change windows names, ...

Analysis of interesting functions

DecryptSettings

Let's go for dead-simple crypto !

.text:08052DEC
.text:08052DEC                 public DecryptSettings
.text:08052DEC DecryptSettings proc near               ; CODE XREF: ReadSettings+Ap
.text:08052DEC
.text:08052DEC var_114         = byte ptr -114h
.text:08052DEC
.text:08052DEC                 push    ebx
.text:08052DED                 sub     esp, 11Ch
.text:08052DF3                 push    10h
.text:08052DF5                 push    offset BuilderEncryptionKey
.text:08052DFA                 lea     ebx, [esp+128h+var_114]
.text:08052DFE                 push    ebx
.text:08052DFF                 call    RC4Setup
.text:08052E04                 add     esp, 0Ch
.text:08052E07                 push    0FFh
.text:08052E0C                 push    offset ConnectionString
.text:08052E11                 push    ebx
.text:08052E12                 call    RC4Crypt
.text:08052E17                 add     esp, 0Ch
.text:08052E1A                 push    0FFh
.text:08052E1F                 push    offset ProxyString
.text:08052E24                 push    ebx
.text:08052E25                 call    RC4Crypt
.text:08052E2A                 add     esp, 0Ch
.text:08052E2D                 push    20h
.text:08052E2F                 push    offset Password
.text:08052E34                 push    ebx
[...]
.text:08052EBB                 call    RC4Crypt
.text:08052EC0                 add     esp, 128h
.text:08052EC6                 pop     ebx
.text:08052EC7                 retn
.text:08052EC7 Decry

It seems to decrypt settings strings with RC4, and the key is located at BuilderEncryptionKey. Let's decrypt all this mess with some pythonic magic !

import sys
from Crypto.Cipher import ARC4

tab = [
        {'name':'ConnectionString', 'size':256, 'offset':0xf610},
        {'name':'ProxyString', 'size':256, 'offset':0xf510},
        {'name':'Password', 'size':32, 'offset':0xf4ec},
        {'name':'HostId', 'size':16, 'offset':0xf4c4},
        {'name':'MutexName', 'size':8, 'offset':0xf4b8},
        {'name':'InstallPath', 'size':128, 'offset':0xf434},
        {'name':'SetupKeyName1', 'size':16, 'offset':0xf420},
        {'name':'SetupKeyName2', 'size':38, 'offset':0xf3f8},
        {'name':'KeyloggerFileName', 'size':128, 'offset':0xf374},
        {'name':'BoolSettingByte', 'size':4, 'offset':0xf370},
        {'name':'ConnectionType', 'size':4, 'offset':0xf36c},
        ]

if len(sys.argv) != 2:
    print('Usage: %s wirenet_sample') % sys.argv[0]
    sys.exit()

fd = open(sys.argv[1], 'r')

#the RC4 key is at offset 0xf4d8
fd.seek(0xf4d8)
key = fd.read(16)

for s in tab:
    rc4 = ARC4.new(key)
    fd.seek(s['offset'])
    data = fd.read(s['size'])
    decyphered = rc4.decrypt(data)
    clear_text = decyphered.split('\x00')[0]
    print('%s: %s') % (s['name'], clear_text)

This prints :

ConnectionString: 212.7.208.65:4141;
ProxyString: -
Password: sm0k4s523syst3m523 #AES key
HostId: LINUX
MutexName: vJEewiWD
InstallPath: %home%/WIFIADAPT
SetupKeyName1: WIFIADAPTER  #Key used for autostart
SetupKeyName2: -
KeyloggerFileName: %Home%\.m8d.dat
BoolSettingByte: 237I
ConnectionType: 001

InstallHost

First, a mutex named vJEewiWD is created, and depending of options, multiples things can happens:

  • The malware can be installed in "InstallPath", which is %home%/WIFIADAPT
  • The malware can be automatically started on login, by creating a file named %HOME%/.config/autostart/WIFIADAPTER.desktop.
  • The malware can be also be automatically started by a line into the %HOME%/.xinitrc.
  • The malware can be run as a daemon, by forking itself
  • The keylogger may be immediately started in a separated thread.

Password stealer

Nothing hardcore, nor noticeably cleaver here. I don't get why the author didn't use the sqlite library instead of hackish crap.

GetMozillaProductPasswords

This function recovers logins/passwords from Thunderbird, Firefox and Seamonkey. The procedure is common for the 3 products:

  1. Get profile path by looking into %HOME%/[.mozilla[/firefox|/seamonkey]|.thunderbird]/profiles.ini for the Path variable
  2. Open the signons.sqlite which contains logins/passwords
  3. Execute "select * from moz_logins" to get every passwords using Mozilla's own functions:
    1. PK11_GetInternalKeySlot
    2. PK11_Authenticate
    3. NSSBase64_DecodeBuffer
    4. PK11SDR_Decrypt
    5. PK11_FreeSlot
    6. NSS_Shutdown<

GetGoogleChromePasswords

Data are either in %HOME%/.config/google-chrome/Default/Login Data for Google Chrome, or in %HOME%/.config/chromium/Default/Login Data for Chromium. This time, no sql request : the sqlite table is decoded as it.

GetOperaWand

This time, the whole file (%HOME%/.opera/wand.dat) containing logins/passwords is sent to the C&C, not a dumped list. Maybe the author was to lazy to write a decrypted?

GetPidginPasswords

Some XML parsing of %HOME%/.purple/accounts.xml.

Keylogging

On GNU/Linux, deploying a keylogger doesn't require a root account. The routine used in this malware is quite common:

  1. XOpenDisplay() to get the Display
  2. XQueryExtension([..]XInputExtension[..]) to check if the XInputExtension is present
  3. XListInputDevices() to get input devices, looking for a "AT" or a "System Keyboard" one.
  4. XOpenDevice() to handle the device
  5. XSelectExtensionEvent() to get interesting events
  6. An infinite loop calling XNextEvent() to get events. If an event is found, it's logged into %HOME%\.m8d.dat using the (ugly) LogKey(XKeyEvent *, Display *) function.

CaptureScreen

This function does the following:

  1. Get an XImage using XGetGeometry then XGetImage.
  2. Convert the XImage to BMP.
  3. Convert the BMP to JPEG.

Misc. functions

  • cpGetComputerName: using gethostname()
  • cpGetOSVersion: looking into /etc/lsb-release
  • cpGetUsername: using getenv("USER")
  • ChangeWindowTitle: using XChangeProperty()

Communication with the C&C

Nothing funky here. The C&C's IP is 212.7.208.65:4141, everything is transmitted using AES, with "sm0k4s523syst3m523" as key. The authentication packet is "RGI28DQ30QB8Q1F7" (in clear in the binary).

A whois tells us that this is a Dediserv server (PL) routed via LeaseWeb (NL). Let's see what nmap's got for us:

# nmap [..] 212.7.208.65
22/tcp  filtered ssh
80/tcp  filtered http
111/tcp open     rpcbind
|  rpcinfo:
|  100000  2,3,4    111/udp  rpcbind
|  100024  1      41019/udp  status
|  100000  2,3,4    111/tcp  rpcbind
|_ 100024  1      28564/tcp  status
135/tcp filtered msrpc
139/tcp filtered netbios-ssn
445/tcp filtered microsoft-ds
No exact OS matches for host

Conclusion

Nothing groundbreaking, but since this was my first malware reversing under GNU/Linux, I had a lot of fun.