{
	"id": "13bfdf76-7ceb-4bb8-932e-f236f38403ec",
	"created_at": "2026-04-10T03:21:51.713736Z",
	"updated_at": "2026-04-10T03:22:19.383139Z",
	"deleted_at": null,
	"sha1_hash": "cb45154affb6570952e6a40ea2c2d50aba19caff",
	"title": "The DGA of Banjori",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 105092,
	"plain_text": "The DGA of Banjori\r\nArchived: 2026-04-10 02:43:57 UTC\r\nThis post analyses the domain generation algorithm (DGA) of the banking trojan Banjori, also known as\r\nMultiBanker 2 or BankPatch/BackPatcher. The DGA was active mostly between April and November of 2013 (at\r\nleast thats when I found most seeds). Two blog posts on kleissner.org [1] [2] document many interesting properties\r\nof the malware, including some aspects of the DGA.\r\nAlthough the malware family and its DGA might no longer be relevant, I still liked the malware for two reasons:\r\n1. The malware’s disassembly is very clean and simple to reverse, many parts were probably written in\r\nassembly.\r\n2. The DGA uses a multivariate recurrence relation with some interesting properties.\r\nI reversed this fairly recent sample from malwr.com, shared by the uploader for anyone to download.\r\nThe DGA\r\nThe callback loop for the examined Banjori sample in IDA’s graph view looks as follows:\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 1 of 10\n\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 2 of 10\n\nThe malware tries to post various data with subroutine connects (offset 2B113). The target domain of the POST\r\nis stored in a global variable which does not appear in the above graph view. The first such domain is hard-coded.\r\nIn case the domain name can be resolved, the malware POSTs data to the CnC-server and reads its response (not\r\nshown). Valid responses end with \"chek\" , see offset 2B129. Only if Banjori receives such a response from the\r\nCnC server will it consider the callback a success and return. In all other cases, the malware enters a\r\ncheck_connectivity_loop (offset 2B14B):\r\n000282CB check_connectivity_loop proc near\r\n000282CB\r\n000282CB push offset aWww_google_de ; \"www.google.de\"\r\n000282D0 call ds:gethostbyname ; ws2_32.gethostbyname\r\n000282D6 test eax, eax\r\n000282D8 jnz short locret_282E7\r\n000282DA push 5000\r\n000282DF call ds:Sleep ; kernel32.Sleep\r\n000282E5 jmp short check_connectivity_loop\r\n000282E7 ; -------------------------------------------------------------\r\n000282E7\r\n000282E7 locret_282E7:\r\n000282E7 retn\r\n000282E7 check_connectivity_loop endp\r\nThis loop tries to resolve www.google.de every 5 seconds until it succeeds — indicating the host has internet\r\nconnectivity.\r\nEvery failed POST is retried a second time for the same domain (see offsets 2B0EE and 2B146). After the second\r\nfailed attempt, Banjori calls generate_and_save_next_domain . This routine generates a new domain based on the\r\nprevious:\r\n000281BD push ds:pDomain\r\n000281C3 call next_domain\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 3 of 10\n\nThe routine next_domain is:\r\n00028242 next_domain proc near\r\n00028242\r\n00028242\r\n00028242 prevdomain = dword ptr 8\r\n00028242\r\n00028242 push ebp\r\n00028243 mov ebp, esp\r\n00028245 push edi\r\n00028246 mov edi, [ebp+prevdomain]\r\n00028249 cmp dword ptr [edi], 'ptth'\r\n0002824F jnz short loc_28254\r\n00028251 add edi, 7\r\n00028254\r\n00028254 loc_28254:\r\n00028254 movzx eax, byte ptr [edi]\r\n00028257 movzx ecx, byte ptr [edi+3]\r\n0002825B add eax, ecx\r\n0002825D push eax\r\n0002825E call map_to_lowercase_letter\r\n00028263 mov [edi], al\r\n00028265 movzx eax, byte ptr [edi+1]\r\n00028269 movzx ecx, byte ptr [edi]\r\n0002826C movzx edx, byte ptr [edi+1]\r\n00028270 add eax, ecx\r\n00028272 add eax, edx\r\n00028274 push eax\r\n00028275 call map_to_lowercase_letter\r\n0002827A mov [edi+1], al\r\n0002827D movzx eax, byte ptr [edi+2]\r\n00028281 movzx ecx, byte ptr [edi]\r\n00028284 add eax, ecx\r\n00028286 dec eax\r\n00028287 push eax\r\n00028288 call map_to_lowercase_letter\r\n0002828D mov [edi+2], al\r\n00028290 movzx eax, byte ptr [edi+3]\r\n00028294 movzx ecx, byte ptr [edi+1]\r\n00028298 movzx edx, byte ptr [edi+2]\r\n0002829C add eax, ecx\r\n0002829E add eax, edx\r\n000282A0 push eax\r\n000282A1 call map_to_lowercase_letter\r\n000282A6 mov [edi+3], al\r\n000282A9 pop edi\r\n000282AA leave\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 4 of 10\n\n000282AB retn 4\r\n000282AB next_domain endp\r\nThe routine map_to_lowercase_letter is:\r\n000282AE map_to_lowercase_letter proc near\r\n000282AE\r\n000282AE\r\n000282AE charsum = dword ptr 8\r\n000282AE\r\n000282AE push ebp\r\n000282AF mov ebp, esp\r\n000282B1 mov eax, [ebp+charsum]\r\n000282B4 sub eax, 'a'\r\n000282B7 mov ecx, 26\r\n000282BC\r\n000282BC loc_282BC:\r\n000282BC cmp eax, ecx\r\n000282BE jb short loc_282C4\r\n000282C0 sub eax, ecx\r\n000282C2 jmp short loc_282BC\r\n000282C4 ; ---------------------------------------------------------\r\n000282C4\r\n000282C4 loc_282C4:\r\n000282C4 add eax, 'a'\r\n000282C7 leave\r\n000282C8 retn 4\r\n000282C8 map_to_lowercase_letter endp\r\nThe following Python script implements these two routines to calculate 10 domains of the DGA:\r\ndef map_to_lowercase_letter(s):\r\n return ord('a') + ((s - ord('a')) % 26)\r\ndef next_domain(domain):\r\n dl = [ord(x) for x in list(domain)]\r\n dl[0] = map_to_lowercase_letter(dl[0] + dl[3])\r\n dl[1] = map_to_lowercase_letter(dl[0] + 2*dl[1])\r\n dl[2] = map_to_lowercase_letter(dl[0] + dl[2] - 1)\r\n dl[3] = map_to_lowercase_letter(dl[1] + dl[2] + dl[3])\r\n return ''.join([chr(x) for x in dl])\r\nseed = 'earnestnessbiophysicalohax.com' # 15372 equal to 0 (seed = 0)\r\ndomain = seed\r\nfor i in range(10):\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 5 of 10\n\nprint(domain)\r\n domain = next_domain(domain)\r\nThe DGA only changes the first four letters of the seed domain, leaving the rest of the domain as is:\r\n$ python dga.py\r\nearnestnessbiophysicalohax.com\r\nkwtoestnessbiophysicalohax.com\r\nrvcxestnessbiophysicalohax.com\r\nhjbtestnessbiophysicalohax.com\r\ntxmoestnessbiophysicalohax.com\r\nagekestnessbiophysicalohax.com\r\ndbzwestnessbiophysicalohax.com\r\nsgjxestnessbiophysicalohax.com\r\nigjyestnessbiophysicalohax.com\r\nzxahestnessbiophysicalohax.com\r\nThe current domain is saved in registry. This allows Banjori to pick up where it left off after a reboot:\r\n000281C8 push ds:pDomain\r\n000281CE call ds:lstrlenA ; kernel32.lstrlenA\r\n000281D4 inc eax\r\n000281D5 push eax\r\n000281D6 push ds:pDomain\r\n000281DC push 1\r\n000281DE push ebx\r\n000281DF push offset subkey ; \"tst\"\r\n000281E4 push ds:hkey ; HKCU\r\n000281EA call ds:RegSetValueExA ; ADVAPI32.RegSetValueExA\r\nSome Properties of the Recurrence Relation\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 6 of 10\n\nLet d = (d0\r\n, d1, d2, d3) denote the first four letters of the domains subject to change, without the offset \"a\" = 97 .\r\nFor example, abcz is represented by (0,1,2,25). Also let d\r\n(i)\r\n denote the first four letters of the i th domain. Then\r\nnext_domain implements the following 4-indexed recurrence relation of order 1:\r\n𝑑0\r\n(𝑖 + 1)\r\n= 𝑑0\r\n(𝑖)\r\n+ 𝑑3\r\n(𝑖) mod26\r\n𝑑1\r\n(𝑖 + 1)\r\n= 𝑑0\r\n(𝑖)\r\n+ 2 ⋅ 𝑑1\r\n(𝑖)\r\n+ 𝑑3\r\n(𝑖) mod26\r\n𝑑2\r\n(𝑖 + 1)\r\n= 𝑑0\r\n(𝑖)\r\n+ 𝑑2\r\n(𝑖)\r\n+ 𝑑3\r\n(𝑖)\r\n− 1 mod26\r\n𝑑3\r\n(𝑖 + 1)\r\n= 2 ⋅ 𝑑1\r\n(𝑖)\r\n+ 2 ⋅ 𝑑1\r\n(𝑖)\r\n+ 𝑑2\r\n(𝑖)\r\n+ 3 ⋅ 𝑑3\r\n(𝑖)\r\n− 1 mod26\r\nSince d is in Z26 x Z26 x Z26 x Z26 the sequence will inevitably repeat itself. Once the sequence reaches a\r\npreviously visited word, all domains in between form a cycle. I call all sequences of four letter words that not part\r\nof a cycle tail. The recurrence relation used for this DGA has an interesting property: d is part of a tail if and only\r\nif d0 + d1 ≡ 1 mod 2. Furthermore, because\r\n𝑑0\r\n(𝑖 + 1)\r\n+ 𝑑1\r\n(𝑖 + 1)\r\n= 𝑑0\r\n(𝑖)\r\n+ 𝑑3\r\n(𝑖)\r\n+ 𝑑0\r\n(𝑖)\r\n+ 2 ⋅ 𝑑1\r\n(𝑖)\r\n+ 𝑑3\r\n(𝑖)\r\n= 2 ⋅ 𝑑0\r\n(𝑖)\r\n+ 2 ⋅ 𝑑1\r\n(𝑖)\r\n+ 2 ⋅ 𝑑3\r\n(𝑖)\r\n≡ 0 mod2\r\nall tails have length 1. It can also been shown that every non-tail word has one tail precursor. From these\r\nproperties, and an exhaustive enumeration of all words, one can deduce the following properties:\r\nIf a domain starts with a four letter tail word, it must be the seed domain.\r\nAll four letter tail words have no precursor.\r\nAll four letter words of a cycle have two precursor: one tail word and one word that is part of the cycle.\r\nHalf of the four letter words are tails, the other half part of a cycle\r\nThere are 32 different cycles with different lengths (see below).\r\nThe following graphic shows part of a 15372 word cycle with its tail words:\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 7 of 10\n\nThe length of the 32 cycles varies:\r\n13 cycles have length 15372, covering 87% of all four letter words.\r\n13 cycles have length 2196, covering and additional 12% of words. 99.95% of all four letter words end up\r\nin either 2196 or 15372 length cycles.\r\n2 cycles have length 42.\r\n1 cycles has length 7\r\n2 cycles have length 6\r\n1 four letter – the word “igih” – is a fixed point; the recurrence relation will not change this word.\r\nThe analysis of the recurrence relation show two interesting facts:\r\nThe DGA will not work for seed domains starting with “igih”. Of course the probability of such a choice is\r\nvirtually zero.\r\nHalf of the seed domains will be tails, meaning they are never revisited once the initial connection fails.\r\nRegistering one of the cycle domains (e.g., the domain right after the seed domain) might therefore be a\r\nbetter option as these domains will be revisited eventually - although most cycles lengths are awfully long.\r\nSeeds in the Wild\r\nAs mentioned above, Banjori is seeded with hardcoded domains. One can find Banjori samples by searching the\r\nvarious online sandboxes for the two mutexe UpdateJbr32 and JbrDelMutex . The following tables lists the\r\nsamples that I found, including the seed domain. The table also lists whether or not the seed domain is a tail\r\ndomain (meaning it will never be revisited), and the length of the cycle (how many different domains the seed will\r\nproduce).\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 8 of 10\n\nseed date md5 tail\r\ncycle\r\nlength\r\nantisemitismgavenuteq.com\r\n2013-\r\n04-24\r\n538da019729597b176e5495aa5412e83 yes 15372\r\nbandepictom.com\r\n2013-\r\n05-05\r\n5592456E82F60D2222C9F2BCE5444DE5 yes 15372\r\nbuckbyplaywobb.com\r\n2013-\r\n05-13\r\nf9d02df23531cff89b0d054b30f98421 yes 15372\r\ntelemachuslazaroqok.com\r\n2013-\r\n05-14\r\nbc69a956b147c99f6d316f8cea435915 yes 15372\r\ntexanfoulilp.com\r\n2013-\r\n07-01\r\n36a9c28031d07b82973f7c9eec3b995c yes 15372\r\nclearasildeafeninguvuc.com\r\n2013-\r\n07-05\r\n1e081e503668347c81bbba7642bef609 yes 15372\r\nmarisagabardinedazyx.com\r\n2013-\r\n07-12\r\nc2c980ea81547c4b8de34adf829ccc26 no 15372\r\npickfordlinnetavox.com\r\n2013-\r\n07-12\r\n4e76a7ba69d1b6891db95add7b29225e yes 15372\r\nsnapplefrostbitecycz.com\r\n2013-\r\n08-06\r\nabb80f23028c49d753e7c93a801444d8 yes 2196\r\nfiltererwyatanb.com\r\n2013-\r\n08-12\r\neff48dae5e91845c2414f0a4f91a1518 yes 15372\r\nantwancorml.com\r\n2013-\r\n08-26\r\n5dda3983ac7cebd3190942ee47a13e50 yes 15372\r\nstravinskycattederifg.com\r\n2013-\r\n08-30\r\neaeb5a9d8d955831c443d4a6f9e179fd yes 15372\r\nforepartbulkyf.com\r\n2013-\r\n09-01\r\n080b3f46356493aeb7ec38e30acbe4f5 yes 15372\r\nfundamentalistfanchonut.com\r\n2013-\r\n09-15\r\n40827866594cc26f12bda252939141f6 yes 15372\r\ncriterionirkutskagl.com\r\n2013-\r\n10-28\r\n8e1d326b687fc4aacc6914e16652c288 yes 2196\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 9 of 10\n\nseed date md5 tail\r\ncycle\r\nlength\r\ncriminalcentricem.com\r\n2013-\r\n11-04\r\na03971bff15ec6782ae25182f4533b92 yes 15372\r\nbabysatformalisticirekb.com\r\n2014-\r\n11-08\r\nb9fb8ae5e3985980175e74cf5deaa6fb yes 15372\r\nearnestnessbiophysicalohax.com\r\n2015-\r\n02-05\r\nf555132e0b7984318b965f984785d360 no 15372\r\nThe blog posts on kleissner.org [1] [2] list additional seeds.\r\nDGA Characteristics\r\nThe following table summarizes the properties of the DGA:\r\nproperty value\r\nseed domain, time independent\r\ndomains per seed\r\nat most 15373, at least 2196 (in some very rare cases less, see the analysis on the\r\nrecurrence relation)\r\ntested domains all\r\nsequence\r\none after another, potentially restarting with first or second domain (see discussion on\r\ntail vs body domains). Last checked domain state saved in registry.\r\nwait time between\r\ndomains\r\nnone as long as DNS query for www.google.de succeeds\r\ntop and second\r\nlevel domain\r\nsame as seed, .com for all observed seeds\r\nsecond level\r\ncharacters\r\nfirst four characters are letters, remaining character could be anything\r\nsecond level\r\ndomain length\r\nsame as seed domain\r\nNote: I removed the Disqus integration in an effort to cut down on bloat. The following comments were retrieved\r\nwith the export functionality of Disqus. If you have comments, please reach out to me by Twitter or email.\r\nSource: https://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nhttps://www.johannesbader.ch/2015/02/the-dga-of-banjori/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.johannesbader.ch/2015/02/the-dga-of-banjori/"
	],
	"report_names": [
		"the-dga-of-banjori"
	],
	"threat_actors": [],
	"ts_created_at": 1775791311,
	"ts_updated_at": 1775791339,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/cb45154affb6570952e6a40ea2c2d50aba19caff.pdf",
		"text": "https://archive.orkl.eu/cb45154affb6570952e6a40ea2c2d50aba19caff.txt",
		"img": "https://archive.orkl.eu/cb45154affb6570952e6a40ea2c2d50aba19caff.jpg"
	}
}