The DGA of Ramnit
- December 21, 2014
- reverse engineering
- dga malware analysis
- no comments
- DGA Disassembly
- DGA in Python
- DGA Characteristics
- Observed Seeds in the Wild
- Seed: 0xEF214BBF
- Seed: 0x28488EEA
- Seed: 0x4BFCBC6A
- Seed: 0x79159C10
The malware in this blog post is also known as Nimnul and Ramnit
The DGA in this blog post has been implemented by the DGArchive project.
For more information about the malware in this blog post see the Malpedia entry on Ramnit.
Ramnit is a Zeus-like malware from 2010 used to spy on infected users. Although the malware isn’t as prevalent as it used to be, there are many recent submissions still. Ramnit uses a Domain Generation Algorithm (DGA) to contact its C2-server. Upon infection, the sample starts to make DNS queries for many different domains in rapid succession:
I reverse engineered the underlying DGA as a sunday afternoon RCE exercise. I used a fairly recent Ramnit sample from Malware-Traffic-Analysis.
DGA Disassembly
The DGA is very simple. Here is the disassembly of the routine that generates the domains:
seg074:2001A91C create_next_domain proc near ; CODE XREF: thread_start+36p seg074:2001A91C seg074:2001A91C seed = dword ptr -4 seg074:2001A91C initial_seed = dword ptr 8 seg074:2001A91C hostname = dword ptr 0Ch seg074:2001A91C seg074:2001A91C push ebp seg074:2001A91D mov ebp, esp seg074:2001A91F add esp, 0FFFFFFFCh seg074:2001A922 push ebx seg074:2001A923 push ecx seg074:2001A924 push edx seg074:2001A925 push esi seg074:2001A926 push edi seg074:2001A927 push 12 ; modulus seg074:2001A929 push [ebp+initial_seed] ; seed seg074:2001A92C call rand_int_modulus seg074:2001A931 mov [ebp+seed], edx seg074:2001A934 add eax, 8 seg074:2001A937 mov ecx, eax ; ecx = = length of 2nd level domain seg074:2001A939 mov esi, [ebp+hostname] seg074:2001A93C seg074:2001A93C loc_2001A93C: ; CODE XREF: create_next_domain+2Dj seg074:2001A93C push 25 ; modulus seg074:2001A93E push edx ; seed seg074:2001A93F call rand_int_modulus seg074:2001A944 add al, 'a' ; -> random letter (one of 25) seg074:2001A946 mov [esi], al seg074:2001A948 inc esi seg074:2001A949 loop loc_2001A93C ; modulus seg074:2001A94B mov byte ptr [esi], 0 ; null terminate seg074:2001A94E push offset tld_com ; ".com" seg074:2001A953 push [ebp+hostname] ; lpString1 seg074:2001A956 call j_lstrcatA seg074:2001A95B xor edx, edx seg074:2001A95D mov eax, [ebp+initial_seed] seg074:2001A960 mov ebx, [ebp+seed] seg074:2001A963 mul ebx ; multiply seeds seg074:2001A965 add eax, edx ; add higher dword seg074:2001A967 pop edi seg074:2001A968 pop esi seg074:2001A969 pop edx seg074:2001A96A pop ecx seg074:2001A96B pop ebx seg074:2001A96C leave seg074:2001A96D retn 8 seg074:2001A96D create_next_domain end
The subroutine rand_int_modulus
is:
seg074:2001331C ; =============== S U B R O U T I N E ======================================= seg074:2001331C seg074:2001331C ; Attributes: bp-based frame seg074:2001331C seg074:2001331C rand_int_modulus proc near ; CODE XREF: seg074:2001303Fp seg074:2001331C ; seg074:20013089p ... seg074:2001331C seg074:2001331C rnd_seed = dword ptr 8 seg074:2001331C modulus = dword ptr 0Ch seg074:2001331C seg074:2001331C push ebp seg074:2001331D mov ebp, esp seg074:2001331F push ebx seg074:20013320 push ecx seg074:20013321 mov eax, [ebp+rnd_seed] seg074:20013324 xor edx, edx seg074:20013326 mov ecx, 127773 seg074:2001332B div ecx ; eax = k1 seg074:2001332D mov ecx, eax ; ecx = k1 seg074:2001332F mov eax, 16807 seg074:20013334 mul edx seg074:20013336 mov edx, ecx seg074:20013338 mov ecx, eax seg074:2001333A mov eax, 2836 seg074:2001333F mul edx seg074:20013341 sub ecx, eax ; ix seg074:20013343 xor edx, edx seg074:20013345 mov eax, ecx ; eax =ebx = ix seg074:20013347 mov ebx, ecx seg074:20013349 div [ebp+modulus] seg074:2001334C mov eax, edx ; eax = rand_int(seed) % modulus seg074:2001334E mov edx, ebx ; edx = seed seg074:20013350 pop ecx seg074:20013351 pop ebx seg074:20013352 leave seg074:20013353 retn 8 seg074:20013353 rand_int_modulus endp
This is a Linear Congurential Generator (LCG) that produces random numbers. The parameters and implementation match this article. The subroutine gets the seed as the first argument, and the modulus as the second parameter.
The DGA uses this random number generator to first determine the length of the second level domain by uniformly picking a length between 8 and 19 characters. Next, the DGA determine the second level domain by uniformly picking letters from “a” to “x” (note: letter “z” can’t be picked, this also helps to spot Ramnit domains).
The DGA then appends the static top level domain “.com”. Finally, the seed for the next domain is determined. The first seed is hardcoded:
seg076:20029004 initial_seed dd 79159C10h
DGA in Python
The following Python script generates Ramnit domains for a provided seed (Edit January 18, 2015: fixed a bug in rand_int_modulus
, some of the generated domains were wrong before).:
import argparse class RandInt: def __init__(self, seed): self.seed = seed def rand_int_modulus(self, modulus): ix = self.seed ix = 16807*(ix % 127773) - 2836*(ix / 127773) & 0xFFFFFFFF self.seed = ix return ix % modulus def get_domains(seed, nr): r = RandInt(seed) for i in range(nr): seed_a = r.seed domain_len = r.rand_int_modulus(12) + 8 seed_b = r.seed domain = "" for i in range(domain_len): char = chr(ord('a') + r.rand_int_modulus(25)) domain += char domain += ".com" m = seed_a*seed_b r.seed = (m + m//(2**32)) % 2**32 yield domain if __name__=="__main__": parser = argparse.ArgumentParser(description="generate Ramnit domains") parser.add_argument("seed", help="seed as hex") parser.add_argument("nr", help="nr of domains", type=int) args = parser.parse_args() for domain in get_domains(int(args.seed, 16), args.nr): print(domain)
For example
$ python dga.py 79159C10 10 knpqxlxcwtlvgrdyhd.com nvlyffua.com hgyudheedieibxy.com anrylixwcbnjopdd.com vrndmdrdrjoff.com jhghrlufoh.com tqjhvylf.com hufqifjq.com itktxexjghvvxa.com ppyblaohb.com
DGA Characteristics
<td>
static
</td>
<td>
unlimited
</td>
<td>
unlimited
</td>
<td>
none
</td>
<td>
.com
</td>
<td>
all letters except “z”</br>(picked uniformly at random)
</td>
<td>
between 8 and 19 characters</br>(picked uniformly at random)
</td>
seed |
domains per seed |
tested domains |
wait time between domains |
top level domain |
second level characters |
second level domain length |
Observed Seeds in the Wild
Even without access to the binary one can determine the seed that lead to a given domain: The seed is only 32 bit which makes it possible to enumerate all possible domains and match them with known network traffic. Using this script I analysed three different sets of DGA domains to find the underlying seeds. (Edit January 18, 2015: some domains were wrong before). (Edit September 14, 2015: found a fourth seed).
Seed: 0xEF214BBF
I found this seed by analysing a malware from malware-traffic-analysis.net. The sample can be downloaded from malwr.com. The first 30 domains for this seed are:
- mtsoexdphaqliva.com
- uulwwmawqjujuuprpp.com
- twuybywnrlqcf.com
- wcqqjiixqutt.com
- ubgjsqkad.com
- iihsmkek.com
- tlmmcvqvearpxq.com
- flkheyxtcedehipox.com
- edirhtuawurxlobk.com
- tfjcwlxcjoviuvtr.com
- bfmbrrihwsfkqy.com
- fyodimwialsoobu.com
- crudcqgf.com
- toixqdmkicnrdhseduj.com
- ssofasuafn.com
- edaqpyrppopwldv.com
- qbuyqrogyglljk.com
- hpmnvvbhnoomnkj.com
- rgggwpfcwueytjoxfb.com
- cslbnrypemnx.com
- hycrotqqubvplffbk.com
- ytxtdhjlme.com
- pxgmtqfoluw.com
- lqenouffubcjpvtmf.com
- eqhpcqjxpeqhjravy.com
- iqlibdbawapeb.com
- opfgjnhe.com
- xlrcqwuyehrsqu.com
- arnsjlbjaqsswum.com
- neqkjkopo.com
Seed: 0x28488EEA
This seed was used first in March 2012. Some of the samples are Malwr, Sonicwall, Sophos, another Sophos, Lavasoft. The first 30 domains for this seed are:
- htmthgurhtchwlhwklf.com
- jiwucjyxjibyd.com
- khddwukkbwhfdiufhaj.com
- snoknwlgcwgaafbtqkt.com
- tfgyaoingy.com
- ukiixagdbdkd.com
- swbadolov.com
- ouljuvkvn.com
- tiqfgpaxvmhsxtk.com
- cxatodxefolgkokdqy.com
- ubkfgwqslhqyy.com
- caytmlnlrou.com
- qbsqnpyyooh.com
- vrguyjjxorlyen.com
- nvepdnpx.com
- vwaeloyyutodtr.com
- gokbwlivwvgqlretxd.com
- mukevipvxvrq.com
- empsqyowjuvvsvrwj.com
- duomyvwabkuappgqxhp.com
- voohnyqdinl.com
- ncxphtrpiawmchfylsy.com
- xwrmquiqjdsxk.com
- ldiogjdyyxacm.com
- kuetvxnntsk.com
- lsawmyxqxvmogvxifm.com
- ppdbeidwufrb.com
- tfipmwkcgigiey.com
- pgahbyurf.com
- yaesbfejdxs.com
Seed: 0x4BFCBC6A
This seed probably first appeared early 2013. There are a couple of Sophos reports that correspond to this seed: Ramnit-B, Ramnit-FS, Ramnit-DD, Ramnit-DA, Ramnit-DB.
The first 30 generated domains are:
- rkjtwjwmesvwhpc.com
- axigleyldgeq.com
- wxsssfvmqi.com
- nhedwmmg.com
- axswdqnjgrnryt.com
- roiornfvclppad.com
- sqhofbxqksckbfrs.com
- rwtxpiehuiiucxkfckw.com
- pmyadxuvmfmcajv.com
- uejgdopjiyxnnvws.com
- nbfplqkemrpedccrcyp.com
- ywyqjdqktqxsxkt.com
- gveejaqxpyrb.com
- vuxrkjrewjwl.com
- mgnodqfisg.com
- dhlpcscshdrvpcpp.com
- xygkltvhkvbje.com
- ijxahlsdiw.com
- jhchibrcyo.com
- sdcepuelyqary.com
- nqbvanrafsi.com
- txnhnwwxfam.com
- jpdvnajhhv.com
- kkiykxbsc.com
- hxlsxpmmtdqqvo.com
- cnlbabnssw.com
- dthjrnnicjkdetclt.com
- eewbwvjommryy.com
- vjckfodjtbobafxmc.com
- yswkdrulyic.com
Seed: 0x79159C10
This seed was used first in April 2014. Here are some of the samples that use this seed: Malwr, Sophos Ramnit-BZ, many samples on Virustotal. The first 30 domains for this seed are:
- knpqxlxcwtlvgrdyhd.com
- nvlyffua.com
- hgyudheedieibxy.com
- anrylixwcbnjopdd.com
- vrndmdrdrjoff.com
- jhghrlufoh.com
- tqjhvylf.com
- hufqifjq.com
- itktxexjghvvxa.com
- ppyblaohb.com
- ectdsitvvoydawmfni.com
- vbvqbnwyurqem.com
- jetuergatod.com
- anxsmqyfy.com
- ckyioylutybvcxv.com
- khllpmpmare.com
- riaaiysk.com
- vfrpojablskkqrx.com
- egopuefrdsefc.com
- fycecyuksgjfxy.com
- qyuylvjwh.com
- wldlrwlygck.com
- lcqavndroo.com
- acuhjbadvnmhthwnlxv.com
- qvberjspofqsxdnr.com
- euvyalbkwahxxjn.com
- typmyloijdcxtdxd.com
- hjahmduyebf.com
- ebrfoyrs.com
- uvenqtbfbeyvebqeb.com