{
	"id": "baf4fa96-6675-4f79-a914-21d6e4d1df6b",
	"created_at": "2026-04-10T03:20:57.867255Z",
	"updated_at": "2026-04-10T03:22:18.995055Z",
	"deleted_at": null,
	"sha1_hash": "1a281cfab65629b3c636e3aaae2bb41a0c4ce7af",
	"title": "The DGA of Pykspa - \"you skype version is old\"",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 160363,
	"plain_text": "The DGA of Pykspa - \"you skype version is old\"\r\nArchived: 2026-04-10 03:15:47 UTC\r\nPykspa (also known as Pykse, Skyper or SkypeBot) is a worm that spreads via Skype, see “Take a Deep Breath: a\r\nStealthy, Resilient and Cost-Effective Botnet Using Skype” by Antonio Nappa et al. and “Recognising Botnets in\r\nOrganisations” by Barry Weymes. The malware has a hardcoded list of chat messages which it sends to contacts\r\nof the infected Skype user, trying to lure them into clicking on links that install Pykspa on their computer.\r\nExamples of the chat messages from my sample are:\r\nyou skype version is old Pykspa\r\nI saw you last week. I would like to speak with you Pykspa\r\ni lost my job..\r\ni am idiot..\r\ni want to die.. Pykspa\r\nThis file lists all chat messages in English. All messages are translated, albeit poorly, to the following languages:\r\nGerman, Russian, Ukranian, Romanian, Danish, Polish, Italian, Latvian, French, Slovak, Lithuanian, Spanish,\r\nNorwegian, Estonian, Swedish, Czech.\r\nSince at least October 2013, Pykspa comes with a Domain Generation Algorithm (DGA) to contact its Command\r\nand Control (C\u0026C) servers. Here is an example of the traffic generated on February 16th, 2015:\r\n +--- whatismyip.everdot.org\r\n | www.whatismyip.ca\r\n(1)-+ whatismyipaddress.com\r\n | www.showmyipaddress.com\r\n +--- www.whatismyip.com\r\n(2)----\u003e www.google.com\r\n +--\u003e ejtuxsflbknn.net\r\n | haqwrnonlaj.info \u003c--+\r\n | jlbdgfhi.net \u003c------+\r\n | hiyumk.net \u003c--------+\r\n | ezrmsrnmzddx.net \u003c--+\r\n(3)-+ okqwmayiseaq.com \u003c--+\r\n | lchsxgzmwg.info \u003c---+--- (4)\r\n +--\u003e wrthooba.info |\r\n | wbtkdnfr.info \u003c-----+\r\n | bodlmkjkckx.net \u003c---+\r\n +--\u003e asgwkyaioy.com |\r\n | xcixrwcyesou.net \u003c--+\r\n | ofebrbnbmvfa.info \u003c-+\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 1 of 18\n\n+--\u003e nazihzsljevt.net |\r\n bkmothb.net \u003c-------+\r\nThe IP lookup domains (1) are used to determine the IP and location of the host, probably to select the right\r\nlanguage for the chat messages. The single call to Google (2) is used to determine the current date and time. After\r\nthat follow two sets of interleaved DGA domains. The set (3) likely contains the C\u0026C target, while (4) is probably\r\njust added as noise.\r\nBoth (3) and (4) use the same DGA, but with different seeds. This blog post shows the DGA of Pykspa, lists the\r\ntime dependent seeds, and links to some samples on malwr.com that match the DGA and seeds. I analysed this\r\nsample from malwr.com, more samples that use Pykspa’s DGA are listed in Section Samples on Malwr.com:\r\nMD5\r\n6da71b4317dd664544903cbc872308d7\r\nSHA1\r\ne373766587b78c4ee7cef9d7a7735b3a52cf41bb\r\nSHA256\r\n16cf97e7237828c37c796a8f9e81451f7fc301da7fe2ff66d97ce5b44c7dfb42\r\nSize\r\n1284KB, 1314816 Bytes\r\nCompile Timestamp\r\n2006-12-09 09:18:24 UTC\r\n(Changes 2015-03-11: Also included discussion of the second set of domains.)\r\nSome of the Preliminary Steps\r\nAnti-VM\r\nMost of the recent Pykspa samples use VM detection. If the sample feels like it is running inside a VM, it\r\nimmediately shuts down the machine:\r\n0040DAE6 shutdown:\r\n0040DAE6 call shutdown1\r\n0040DAEB call shutdown2\r\n0040DAF0\r\n0040DAF0 loc_40DAF0:\r\n0040DAF0 call detect_vm\r\n0040DAF5 test al, al\r\n0040DAF7 jnz short shutdown\r\nThe VM detection is based on the exotic “Visual Property Container Extender” assembly call:\r\n0040D408 vpcext 7, 0Bh\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 2 of 18\n\nMy VM was unmasked by this call; I therefore patched the call to the VM detection routine with xor eax, eax :\r\n0040DAF0 loc_40DAF0:\r\n0040DAF0 xor eax, eax\r\n0040DAF2 nop\r\n0040DAF3 nop\r\n0040DAF4 nop\r\n0040DAF5 test al, al\r\n0040DAF7 jnz short shutdown\r\nHost-IP\r\nThe first network traffic that the sample generates are calls to various IP lookup sites. Pykspa probably uses the\r\ninformation from these sites to geolocate the infected client and choose the appropriate language in Skype chats.\r\nCurrent Time\r\nNext, Pykspa enters this code snippet:\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 3 of 18\n\nThese lines first randomly choose one of the following 13 common website (stored at test_domains , see offset\r\n40C214):\r\n- www.google.com\r\n- www.facebook.com\r\n- www.myspace.com\r\n- www.youtube.com\r\n- www.yahoo.com\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 4 of 18\n\n- www.youtube.com\r\n- www.wikipedia.org\r\n- www.blogger.com\r\n- www.adobe.com\r\n- www.bbc.co.uk\r\n- www.imdb.com\r\n- www.baidu.com\r\n- www.ebay.com\r\nThe malware then calls the subroutine connectivity_check which makes a HTTP GET request for the selected\r\ndomain , e.g.,\r\nGET / HTTP/1.1\r\nHost: www.blogger.com\r\nAccept: */*\r\nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; -\u003e\r\n -\u003e rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3\r\nConnection: close\r\nPykspa extracts the Date field of the HTTP reponse’s header:\r\nThe string is then parsed to get the current date and time. For example, these lines convert “Feb” to the month\r\nnumber 2:\r\n0040FC6F loc_40FC6F:\r\n0040FC6F push offset aFeb\r\n0040FC74 push edi\r\n0040FC75 call esi ; lstrcmpiA\r\n0040FC77 test eax, eax\r\n0040FC79 jnz short loc_40FC84\r\n0040FC7B mov [ebp+74h+month], 2\r\n0040FC7F jmp loc_40FD3C\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 5 of 18\n\nFor instance, if the response from www.blogger.com is:\r\nHTTP/1.1 302 Moved Temporarily\r\nP3P: CP=\"This is not a P3P policy! See http://www.google.com/support/accounts/ -\u003e\r\n -\u003e bin/answer.py?hl=en\u0026answer=151657 for more info.\"\r\nContent-Type: text/html; charset=UTF-8\r\nCache-Control: no-cache, no-store, max-age=0, must-revalidate\r\nPragma: no-cache\r\nExpires: Fri, 01 Jan 1990 00:00:00 GMT\r\nDate: Mon, 09 Mar 2015 11:03:14 GMT\r\n...\r\nThen the date is 2015-03-09 11:03:14 . The routine connectivity_check returns this date in unix timestamp\r\nformat in eax , or NULL if the date extraction failed. If a date is returned, it is saved in variable today at offset\r\n40C224. If the date extraction failed, for example because internet went down, the snippet sleep 3 seconds at\r\n40C22B and retries with a different domain for at most 20 times.\r\nAfter the current time is stored in today , the malware starts a stopwatch in line 40c247 which allows it to\r\nestimate the current time using only calls to GetTickCount (see Section Seed).\r\nCallback Loop\r\nAs shown in the beginning of the post, Pykspa generates two sets of interleaved DGA domains. These domains are\r\nindistinguishable due to using the same algorithm. However, the two sets use a different seed and are generated\r\nunder different circumstances.\r\nIn the following I first show which seed is used when and how the seeds are changed. Next, I show how the initial\r\nseed is determined based on the current time. I conclude the section by showing how the DGA actually generates\r\ndomains based on the current seed.\r\nWhen are DGA Calls Made\r\nThe callback loop iterates from 0 to 15999:\r\n00406B7A xor dga1_nr, dga1_nr\r\n(...)\r\n00406B82 mov [ebp+index], dga1_nr\r\n00406B85 next_index:\r\n(...)\r\n00406C64 mov eax, 16000\r\n(...)\r\n00406C7A inc [ebp+index]\r\n00406C7D cmp [ebp+index], eax\r\n00406C80 jl next_index\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 6 of 18\n\nThe above loop is wrapped in an infinite loop, which sleeps 1 second before starting the callback loop over:\r\n.text:00406C86 push one_second ; dwMilliseconds\r\n.text:00406C8C call ds:Sleep\r\n.text:00406C92 jmp restart_all\r\nTo summarize, these are the two loops in pseudo code:\r\nWHILE True DO\r\n // Initialize seeds\r\n FOR index = 0 TO 15999 DO\r\n // DGA calls\r\n END FOR\r\n sleep 1 second\r\nEND WHILE\r\nFirst Seed - The Useful DGA calls: Whenever the index is divisible by 80, the DGA is called based on the first\r\nseed:\r\n00406B85 mov eax, [ebp+index]\r\n00406B88 push 80\r\n00406B8A cdq\r\n00406B8B pop ecx\r\n00406B8C idiv ecx\r\n00406B8E test edx, edx\r\n00406B90 jnz short loc_406BF9\r\n00406B92 mov ecx, [ebp+seed1]\r\n00406B95 mov eax, ecx\r\n00406B97 lea ebx, [dga1_nr+1]\r\n00406B9A div ebx\r\n00406B9C lea eax, [ecx+edx+1]\r\n00406BA0 push eax ; seed\r\n00406BA1 mov [ebp+seed1], eax\r\n(DGA related lines)\r\n00406BE6 mov dga1_nr, ebx\r\nThese lines decompile to:\r\nIF index % 80 == 0 THEN\r\n s = seed1 % (dga1_nr + 1)\r\n seed1 = seed1 + s + 1\r\n // DGA call\r\n dga1_nr = dga1_nr + 1\r\nEND IF\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 7 of 18\n\nThe dga1_nr starts at 0 (see offset 00406B7A ). The initial value of the seed will be discussed later on. Because\r\nthe index runs from 0 to 15999, there are 200 different domains generated by seed1 .\r\nSecond Seed - The Noisy DGA calls: The callback loop can make second DGA calls independent of the above\r\nDGA calls. These second DGA calls are based on an independent second seed. This seed is changed every\r\niteration, but DGA calls are only made in about 5% of all iterations:\r\n.text:00406BF9 loc_406BF9:\r\n.text:00406BF9 mov ecx, [ebp+seed2]\r\n.text:00406BFC mov ebx, [ebp+dga2_nr]\r\n.text:00406BFF mov eax, ecx\r\n.text:00406C01 xor edx, edx\r\n.text:00406C03 inc ebx\r\n.text:00406C04 div ebx\r\n.text:00406C06 lea eax, [ecx+edx+1]\r\n.text:00406C0A mov [ebp+seed2], eax\r\n.text:00406C0D call _rand\r\n.text:00406C12 push 20\r\n.text:00406C14 cdq\r\n.text:00406C15 pop ecx\r\n.text:00406C16 idiv ecx\r\n.text:00406C18 test edx, edx\r\n.text:00406C1A jnz short loc_4\r\nThese line decompile to this pseudo code:\r\ns = seed2 % (dga2_nr + 1)\r\nseed2 = seed2 + s + 1\r\nIF rand() % 20 == 0 THEN\r\n // DGA call\r\nEND IF\r\ndga2_nr = dga2_nr + 1\r\nNotice that the seed2 is changed regardless of whether the seed2 is actually used to generate a new domain.\r\nTherefore, there exist 16000 different domains from seed2, of which only about 5% or 800 domains are actually\r\ngenerated and used.\r\nThe rand() call used to determine if a domain is generated or not is based on the current tick count:\r\n.text:00406ABE call ds:GetTickCount\r\n.text:00406AC4 push eax\r\n.text:00406AC5 call set_seed\r\nThis makes the rand() function unpredictable. Because each of the domains from the second seed only has a 5%\r\nchance of being used, I assume these domains are meant merely to produce noise and not be registered as actual\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 8 of 18\n\nC\u0026C servers.\r\nSeed\r\nThe intial seeds for both DGA sets are generated almost the same way. First, the current time is determined:\r\n00406ACA call get_timestamp\r\nThis routine uses the unix timestamp in today , which was determined during the connectivity check, and adds\r\nthe number of seconds that passed since then:\r\n.text:00413F2B get_timestamp proc near\r\n.text:00413F2B call ds:GetTickCount\r\n.text:00413F31 sub eax, tick_count_after_connectivity_check\r\n.text:00413F37 xor edx, edx\r\n.text:00413F39 mov ecx, 1000\r\n.text:00413F3E div ecx\r\n.text:00413F40 add eax, today\r\n.text:00413F46 retn\r\n.text:00413F46 get_timestamp\r\nThis gives an estimate of the current time as unix timestamp. This value — in eax — is then divided:\r\n.text:00406ACF xor edx, edx\r\n.text:00406AD1 mov ecx, 1728000 ; 20 days ...\r\n.text:00406AD6 div ecx\r\n.text:00406AD8 mov [ebp+time_divided_by_20days], eax\r\nThe divisor is the only difference in creating the first and second seed:\r\nFor the first set of domains the divisor is 1728000. This is the number of seconds in 20 days.\r\nFor the second set of domains the divisor is 86400. This is the number of seconds in 1 day.\r\nThe seed is based on the resulting quotient, and will therefore change once every 20 days for the useful first set of\r\ndomains, and daily for the noisy second set of domains. The seed initialization continues by creating a 64 bytes\r\nlong ASCII hex string:\r\n.text:00406ADB lea eax, [ebp+hash_string]\r\n.text:00406AE1 push eax ; void *\r\n.text:00406AE2 push 4\r\n.text:00406AE4 pop edi\r\n.text:00406AE5 lea eax, [ebp+time_divided_by_20days]\r\n.text:00406AE8 push edi ; int\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 9 of 18\n\n.text:00406AE9 push eax ; int\r\n.text:00406AEA call create_hash_string\r\nThe routine create_hash_string is complicated and I didn’t reverse engineer the code. It probably is a hash\r\nfunction that returns 256 bytes encoded as a hex string. Pykspa then takes a 4 character substring of this hex\r\nstring. The start of the substring is determined by taking the time quotient modulo 50:\r\n.text:00406AEF mov eax, [ebp+time_divided_by_20days]\r\n.text:00406AF2 push edi ; size_t\r\n.text:00406AF3 push 50\r\n.text:00406AF5 pop ecx\r\n.text:00406AF6 xor edx, edx\r\n.text:00406AF8 div ecx ; offset at most 49\r\n.text:00406AFA lea eax, [ebp+edx+hash_string]\r\n.text:00406B01 push eax ; substring\r\nFor instance, on March 10th, 2015 at 8 pm the hash_string for the first set of domains is:\r\nb8b8ae799a67ccc2046e97b6935341680e0e830a8920ec393aa8fe12860b9b09\r\nThe unix timestamp is 1426017600. Divided by twenty days, this becomes 825, which is 25 modulo 50. The\r\nmalware will therefore use the 4 characters starting at offset 25, i.e., “3534”.\r\nThese four hex characters and the entire 64 character hash are then passed to routine calc_seed that will\r\ndetermine the seed:\r\n.text:00406B0B lea eax, [ebp+hash_string]\r\n.text:00406B11 push eax ; hash\r\n.text:00406B12 lea eax, [ebp+seed]\r\n.text:00406B15 push edi ; 4\r\n.text:00406B16 push eax ; target\r\n.text:00406B17 call calc_seed\r\n.text:00406B1C mov eax, [ebp+seed]\r\n.text:00406B1F mov [ebp+original_seed], eax\r\nAgain, this routine is quite complicated. It probably does some sort of decryption of the substring based on the\r\nhash. I couldn’t identify the algorithm, and didn’t have the time to reverse the code from scratch. I opted instead to\r\nlet the code calculate the seeds for me using a small debugger script. The procedure was as follows:\r\n1. Attach a debugger to the malware while inside the callback loop.\r\n2. Set a first breakpoint at offset 0x00406ACF , this is where the current time has just been stored in eax .\r\n3. Set a second breakpoint at offset 0x00406B1F , this is after the first seed has been stored in eax .\r\n4. Iterate over all desired timestamps. For each timestamp do the following: (a) Jump to the beginning of the\r\ncallback routine; (b) Run to the first breakpoint and change eax to the desired timestamp; (c) Run to the\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 10 of 18\n\nsecond breakpoint, get the seed from eax and log the result.\r\nI implemented these steps in the following Immunity Debugger script:\r\nimport immlib\r\nimport time\r\nfrom datetime import datetime\r\ndef main(args):\r\n imm = immlib.Debugger()\r\n filename = \"seeds.txt\"\r\n with open(filename, \"w\") as w:\r\n w.write(\"first time;last time;seed\\n\")\r\n # addresses\r\n start_of_callback = 0x00406A7C\r\n after_get_timestamp = 0x00406ACF\r\n result = 0x00406B1F\r\n # time values\r\n twenty_days = 3600*24*20\r\n timestamp = time.mktime((2008,1,1,0,0,0,1,1,-1))\r\n timestamp = int(((timestamp//twenty_days)*twenty_days))\r\n end_timestamp = time.mktime((2016,1,1,0,0,0,4,1,-1))\r\n # setting breakpoints\r\n imm.log(\"setting breakpoints ...\")\r\n imm.setBreakpoint(after_get_timestamp)\r\n imm.setBreakpoint(result)\r\n while timestamp \u003c end_timestamp:\r\n first_time = datetime.fromtimestamp(timestamp).strftime(\"%Y-%m-%d %H:%M:%S\")\r\n last_time = datetime.fromtimestamp(timestamp + twenty_days - 1).\\\r\n strftime(\"%Y-%m-%d %H:%M:%S\")\r\n imm.log(\"getting seed for {}\".format(first_time))\r\n imm.setReg('EIP', start_of_callback)\r\n imm.run()\r\n imm.setReg('EAX', timestamp)\r\n imm.run()\r\n seed = imm.getRegs()['EAX']\r\n with open(filename, \"a\") as a:\r\n a.write(\"{};{};{:x}\\n\".format(first_time, last_time, seed))\r\n timestamp += twenty_days\r\n return \"done extracting seeds\"\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 11 of 18\n\nTo generate the seeds of the second set of domains, simply change the breakpoints to 0x406B27 and 0x406B7C\r\nrespectively.\r\nThe following table lists all seeds of the useful domains for the years 2014 and 2015, including the first five\r\ndomains that the DGA — shown in the following Section — produces: The time periods are in UTC.\r\nperiod seed first domains\r\n2013-12-21 - 2014-\r\n01-10\r\n4ae6a802\r\nyabbpifwawe.info, eszgwhn.net, fgskxmbql.org, ycautv.net,\r\ndlhcdwfif.info\r\n2014-01-10 - 2014-\r\n01-30\r\n3896367b\r\njkdcmitej.com, ccnfrsfseht.net, wwlodun.info, ivdjhcvwvnla.info,\r\nclvcgwxp.net\r\n2014-01-30 - 2014-\r\n02-19\r\n6a2c8eb2\r\nnbmcfivnj.info, wkjywepmjoqx.net, zkkerwhef.com, owvydqimfvl.net,\r\ngwiogocc.com\r\n2014-02-19 - 2014-\r\n03-11\r\nf96bf2a5\r\nbvxbdfe.org, msmyftxyd.info, ncnalaomgmw.com, xvanur.net,\r\nyookeocesq.com\r\n2014-03-11 - 2014-\r\n03-31\r\nfcef457e\r\nzfjobfqorjhm.info, fjzcpepv.net, msseaycyaeak.com, zstalto.net,\r\nxmfzbtyaewxb.net\r\n2014-03-31 - 2014-\r\n04-20\r\ndd370744\r\neugcldt.net, bplvoxgfxb.info, macccwskcmsq.com, zwfelqr.net,\r\nomizdirtly.info\r\n2014-04-20 - 2014-\r\n05-10\r\na1c5400f\r\nvcsvuay.com, emhebepmd.net, ufakhnixqiwr.info, lfsuyhcrlx.info,\r\nymesmsyiymqo.com\r\n2014-05-10 - 2014-\r\n05-30\r\n9d0d436c wcsjaj.net, hmdfbqzor.info, xigkzlxyfen.com, nfinek.net, yicuqmsi.org\r\n2014-05-30 - 2014-\r\n06-19\r\n7bb24638\r\nwjfbhnjedf.net, tltcpr.info, sbfebsuys.net, zrqhcumanzne.info,\r\njilmfepwgrc.com\r\n2014-06-19 - 2014-\r\n07-09\r\na6b042c\r\nlbjafzzofdx.net, ntilnij.info, vcjdxwbxx.com, cyfwjticnep.net,\r\nassoka.org\r\n2014-07-09 - 2014-\r\n07-29\r\nf34c4850\r\nusyytuo.net, hnhdxtxbvd.info, wmsuiuckkagu.com, rosktbi.net,\r\nadgzqqstzn.info\r\n2014-07-29 - 2014-\r\n08-18\r\nf2509f0b\r\nquyeek.com, rprumlqy.net, uuocymsegeog.com, rrrkxsp.net,\r\nvbkubbxralgs.net\r\n2014-08-18 - 2014-\r\n09-07\r\nf0978d0e\r\nmgojzac.info, tkcapzfbrj.net, uscoccqakswe.org, jcrwiwvmq.net,\r\nkexlgqb.net\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 12 of 18\n\nperiod seed first domains\r\n2014-09-07 - 2014-\r\n09-27\r\n44dd4e5f bewbwrj.com, amoudctuy.net, cciksy.com, masvzhrl.net, lkirpo.net\r\n2014-09-27 - 2014-\r\n10-17\r\naac6d7f6\r\nlrvhlp.info, ojxczyten.net, zlyqrojjqhiw.info, yjvmfyxydt.info,\r\ndrdgnmro.info\r\n2014-10-17 - 2014-\r\n11-06\r\nf26296b1\r\nnemnthdcpag.org, rdflml.info, oaiwwgaa.com, mjzknmuztv.net,\r\nwzhpoo.info\r\n2014-11-06 - 2014-\r\n11-26\r\nc950cbfe vehbkr.info, zfvszoiql.net, lcqgvikjtgv.org, vbthttzj.net, kiusqwmsye.org\r\n2014-11-26 - 2014-\r\n12-16\r\n41de3ee2\r\neiiuvwczrb.info, uhorab.net, uqocygsskk.com, ltzzjconbftl.net,\r\nfsoelqbsd.com\r\n2014-12-16 - 2015-\r\n01-05\r\nf085a446\r\nbmnudlx.info, dsyxxfvtyy.net, bshtdy.info, toefqwaaeim.info,\r\nrkskpjb.net\r\n2015-01-05 - 2015-\r\n01-25\r\nffc222d4\r\nxwwglbhkjsl.net, tscdrkt.info, pvdldmlteif.org, hldvfrca.net,\r\nbsvurea.org\r\n2015-01-25 - 2015-\r\n02-14\r\nf3fc72bb\r\ntitjxqdwcmd.com, yglkyz.net, qoumyuce.org, rwhnxqtqftkb.net,\r\nlhheddtsy.com\r\n2015-02-14 - 2015-\r\n03-06\r\n2ff654d0\r\nejtuxsflbknn.net, wrthooba.info, asgwkyaioy.com, nazihzsljevt.net,\r\ntdxbpku.org\r\n2015-03-06 - 2015-\r\n03-26\r\n54d64257\r\nnlpfgnhcb.com, horfdqdqtia.net, oayeww.org, jmydflbsel.net,\r\ndkvyhta.com\r\n2015-03-26 - 2015-\r\n04-15\r\nec343337\r\nrwtyvfrddfk.com, imwgaj.net, mkyaeeye.org, xkvjbgdknfbt.net,\r\nteejnil.org\r\n2015-04-15 - 2015-\r\n05-05\r\nccd10407\r\naaqeemgo.com, gilzxjidxj.net, ywumyoiuuigi.org, uvfpbbsyv.net,\r\ntbvtngvxocp.org\r\n2015-05-05 - 2015-\r\n05-25\r\n91f7e71c\r\nytaxprtas.net, hstrdnpcoznh.info, nqpmsct.com, xulciqvvd.net,\r\nggwjhuxqtklf.info\r\n2015-05-25 - 2015-\r\n06-14\r\n5cea1d7a\r\nveliren.info, xqzupldhuo.net, ooiomymggqom.org, ucovoaxwi.net,\r\nywmuyucg.org\r\n2015-06-14 - 2015-\r\n07-04\r\nb6fcd7ef hwumvsr.com, gipqptued.net, gickck.com, lobmdjpw.net, nedqtxt.org\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 13 of 18\n\nperiod seed first domains\r\n2015-07-04 - 2015-\r\n07-24\r\n7c562f77\r\nuoakekgy.com, ysbcaebxnt.net, oucmkcyaskua.org, agvsjmdoz.net,\r\nomgsgs.com\r\n2015-07-24 - 2015-\r\n08-13\r\n4936d2db\r\nwgascuec.com, goxolemcot.net, gyykawsgmeki.org, uiffdqxsf.net,\r\ngauaam.com\r\n2015-08-13 - 2015-\r\n09-02\r\nb8b2c9e1\r\nuammskmq.org, jqplflktas.info, rybwtr.net, uyznvxlof.info,\r\ngakcmqiw.com\r\n2015-09-02 - 2015-\r\n09-22\r\n31b275c5\r\nmcuomg.org, qdzzziyj.info, cyykckimuy.com, tufbdpbtbxyr.net,\r\nhofzvmd.org\r\n2015-09-22 - 2015-\r\n10-12\r\na13852\r\nhrlefwvvqqpp.info, dthawyxm.net, aucoakiaicoe.com, voqzaaq.net,\r\nehibnxwshs.info\r\n2015-10-12 - 2015-\r\n11-01\r\n6f4f5a0\r\nuauyxamffw.net, mhfira.info, ugfuystwx.net, ffxxnbesitxz.info,\r\nhvhkadugb.org\r\n2015-11-01 - 2015-\r\n11-21\r\n90864d97\r\negmsooqueq.com, ckcdayqewadl.net, bwrdwuhv.info, xkywrr.info,\r\noyiaaweoymgu.com\r\n2015-11-21 - 2015-\r\n12-11\r\n225a3e31\r\nrgdyrur.org, yfthagxeb.info, rwccjihweor.com, xdhgnb.net,\r\negymosgwqiue.org\r\n2015-12-11 - 2015-\r\n12-31\r\nf400ac9c\r\nkzdwohdroo.net, joemwm.info, mkuqmkccgo.org, lqyxezj.net,\r\nmydsxhtrli.info\r\n2015-12-31 - 2016-\r\n01-20\r\n1f5c0eed\r\nlkwokkrehon.org, cwcrlh.info, hcgktapuh.net, dgarcqijrdjc.info,\r\nzufrrmm.com\r\nYou can find more seeds for the useful domains, as well as the seeds for the noisy domains, in the download in the\r\nDGA download.\r\nThe DGA\r\nFinally, this section shows how the domains are generated based on the current seed. First, the length of the\r\ndomains is determined and passed to the dga subroutine get_sld (= get second level domain):\r\n.text:00406BA4 push 7\r\n.text:00406BA6 pop ecx\r\n.text:00406BA7 add eax, dga1_nr\r\n.text:00406BA9 xor edx, edx\r\n.text:00406BAB div ecx\r\n.text:00406BAD lea eax, [ebp+domain]\r\n.text:00406BB0 add edx, 6\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 14 of 18\n\n.text:00406BB3 push edx ; length\r\n.text:00406BB4 push eax ; domain\r\n.text:00406BB5 call get_sld\r\nThis snippet boils down to:\r\nlength = (seed1 + dga1_nr) % 7 + 6\r\ndomain = get_sld(length, seed)\r\nThe DGA routine to generate the second level domain is quite long, you can see the full disassembly here. The\r\ncode boils down to this Python snippet:\r\ndef get_sld(length, seed):\r\n domain = \"\"\r\n modulo = 541 * length + 4\r\n a = length * length\r\n for i in range(length):\r\n index = (a + (seed*((seed % 5) + (seed % 123456) +\r\n i*((seed \u0026 1) + (seed % 4567))) \u0026 0xFFFFFFFF)) % 26\r\n a += length;\r\n a \u0026= 0xFFFFFFFFF\r\n domain += chr(ord('a') + index)\r\n seed += (((7837632 * seed * length) \u0026 0xFFFFFFFF) + 82344) % modulo;\r\n return domain\r\nBecause the seed is passed by value, the assignment in the second to last line won’t change seed1 or seed2 .\r\nNext, the top level domain is chosen randomly from an array of top level domains:\r\n.text:00406BBA add esp, 0Ch\r\n.text:00406BBD push offset a_ ; \".\"\r\n.text:00406BC2 lea eax, [ebp+hostname]\r\n.text:00406BC5 push eax ; lpString1\r\n.text:00406BC6 call esi ; lstrcatA\r\n.text:00406BC8 mov eax, [ebp+asdf]\r\n.text:00406BCB and eax, 3\r\n.text:00406BCE imul eax, 7\r\n.text:00406BD1 add eax, offset tld ; \"com\"\r\n.text:00406BD6 push eax ; lpString2\r\n.text:00406BD7 lea eax, [ebp+hostname]\r\n.text:00406BDA push eax ; lpString1\r\n.text:00406BDB call esi ; lstrcatA\r\nWith the tld array:\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 15 of 18\n\n.data:0042E048 tld db 'com',0 ; DATA XREF: sub_406A7C+155\r\no\r\n.data:0042E048 ; sub_406A7C+1D2o\r\n.data:0042E04C db 0\r\n.data:0042E04D db 0\r\n.data:0042E04E db 0\r\n.data:0042E04F db 'net',0\r\n.data:0042E053 db 0\r\n.data:0042E054 db 0\r\n.data:0042E055 db 0\r\n.data:0042E056 db 'org',0\r\n.data:0042E05A db 0\r\n.data:0042E05B db 0\r\n.data:0042E05C db 0\r\n.data:0042E05D db 'info',0\r\n.data:0042E062 db 0\r\n.data:0042E063 db 0\r\n.data:0042E064 db 'cc',0\r\n.data:0042E067 db 0\r\n.data:0042E068 db 0\r\nSo the top level domain is picked according to:\r\ntlds = ['com', 'net', 'org', 'info', 'cc']\r\ntop_level_domain = tlds[(seed1 \u0026 3)]\r\nNote that only the first four top level domains are reachable, “cc” can’t be picked.\r\nPython Code and Summary\r\nThe Python code in this ZIP download generates the useful domain for any date between 2008 and 2020. It can\r\nalso generate the noisy domains for the period March 2013 to March 2020. The script uses the list of seeds stored\r\nin dga1_seeds.json and dga2_seeds.json . For instance, to get the four DGA domains from set (3) shown in\r\nthe introduction:\r\n$ python dga.py -d 2015-02-16 -n 4\r\nejtuxsflbknn.net\r\nwrthooba.info\r\nasgwkyaioy.com\r\nnazihzsljevt.net\r\nTo confirm that the first noisy domain from (4), i.e., haqwrnonlaj.info is covered by the second seed (the date is\r\noffset by two days, maybe because of timezone differences?):\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 16 of 18\n\n$ python dga.py -d 2015-02-18 -n 1000 -s 2 | grep -n haqwrnonlaj.info\r\n8:haqwrnonlaj.info\r\nThe following table summarizes the properties of the DGA:\r\nproperty useful domains (first seed)\r\nnoisy domains (second\r\nseed)\r\nseed changes every 20 days changes daily\r\ndomains per seed 200 800\r\ntested domains all 5% per round\r\nsequence one after another\r\nskipping over 95% of\r\ndomains\r\nwait time between\r\ndomains\r\nnone same\r\ntop level domain\r\n.com, .net, .org and .info, picked uniformly at\r\nrandom\r\nsame\r\nsecond level characters lower case letters, picked uniformly at random same\r\nsecond level domain\r\nlength\r\n6 to 12 characters same\r\nSamples on Malwr.com\r\nThe DGA was probably first used in October 2013. The first reference to a DGA domain on Google are for the\r\ndomain “mczvyzye.net”, which was active from October 3rd to 22nd, 2013, and is listed in in this analysis. The\r\nfollowing table lists samples on malwr.com that match the DGA and seeding procedure:\r\nmd5 analysis date seed\r\n467a8f934ba9ea9b438a2c89c9f18c1b 21 Feb. 2014 0xf96bf2a5L\r\nf081f266f1800f6192aa662c9cd15da1 21 Feb. 2014 0xf96bf2a5L\r\n45c750a60992e1f0c433713fbff50734 21 Feb. 2014 0xf96bf2a5L\r\n04d5ee33b95c4ec35ee5295fedf155a9 21 Feb. 2014 0xf96bf2a5L\r\n02a4634b2ad3800f2a8a285933f03946 29 May. 2014 0x9d0d436cL\r\n30ea7bfee0a9a5fa1a94265cba336116 04 Jun. 2014 0x7bb24638\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 17 of 18\n\nmd5 analysis date seed\r\n0f223c93889d60de3eeec997a17a5121 18 Jun. 2014 0x7bb24638\r\n1c04b0440ca8fbf2f9e4fdae6783e480 18 Jun. 2014 0x7bb24638\r\n01d57201b405cc2eec8688ceac6861f6 27 Jun. 2014 0xa6b042c\r\n21bbf78287b44331941e99f124326156 03 Jul. 2014 0xa6b042c\r\n02b9fba52d81bc77f92dd6cbdae2eae1 03 Jul. 2014 0xa6b042c\r\n10aa6b8873010c2158342d2f96f11fc1 03 Jul. 2014 0xa6b042c\r\n16a39f6d50deef6614e2e8cabdc56671 03 Jul. 2014 0xa6b042c\r\n28cbcc88b68bba309fab8229fdfb3621 04 Jul. 2014 0xa6b042c\r\nd4fdea06373888645c693ed2c91b9853 08 Jul. 2014 0xa6b042c\r\nf95f4809d1e239da71935728ae588c05 08 Jul. 2014 0xa6b042c\r\n57231e30070e9d500ffa25774809c6b1 13 Jul. 2014 0xf34c4850L\r\n77ce85d6fc611cc533fcc5c235fb71af 20 Jul. 2014 0xf34c4850L\r\n83e4b0ca5ebfa9de4adbc58009629d61 27 Dec. 2014 0xf085a446L\r\nc85ddcad47577b294b13ce3c8f0134bb 14 Jan. 2015 0xffc222d4L\r\n6da71b4317dd664544903cbc872308d7 25 Jan. 2015 0xf3fc72bbL\r\n1667b27c7ca9662330ad15a9ab34dffc 16 Feb. 2015 0x2ff654d0\r\ncb07607586d35e8a5832c0149f6c244c 09 Mar. 2015 0x54d64257\r\n1667b27c7ca9662330ad15a9ab34dffc 09 Mar. 2015R 0x54d64257\r\ncb07607586d35e8a5832c0149f6c244c 09 Mar. 2015R 0x54d64257\r\nR: Reanalysis\r\nMost samples on Malwr were analysed in June and July of 2014, but there are also some recent samples.\r\nSource: https://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nhttps://www.johannesbader.ch/2015/03/the-dga-of-pykspa/\r\nPage 18 of 18",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.johannesbader.ch/2015/03/the-dga-of-pykspa/"
	],
	"report_names": [
		"the-dga-of-pykspa"
	],
	"threat_actors": [],
	"ts_created_at": 1775791257,
	"ts_updated_at": 1775791338,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1a281cfab65629b3c636e3aaae2bb41a0c4ce7af.pdf",
		"text": "https://archive.orkl.eu/1a281cfab65629b3c636e3aaae2bb41a0c4ce7af.txt",
		"img": "https://archive.orkl.eu/1a281cfab65629b3c636e3aaae2bb41a0c4ce7af.jpg"
	}
}