{
	"id": "5ebc5082-167f-4adc-973c-3d1bf47d5af6",
	"created_at": "2026-04-06T00:14:19.07305Z",
	"updated_at": "2026-04-10T03:21:31.544906Z",
	"deleted_at": null,
	"sha1_hash": "beae29f87e7143b806ffaef2cd99b68215a7730a",
	"title": "Revisiting Hancitor in Depth | 0ffset Training Solutions",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 3712472,
	"plain_text": "Revisiting Hancitor in Depth | 0ffset Training Solutions\r\nBy 0verfl0w_\r\nPublished: 2019-02-05 · Archived: 2026-04-05 21:55:04 UTC\r\nAs you probably guessed from the title, we are going to be taking a look at Hancitor once again, except this time,\r\nI’ll be focusing on the second stage of Hancitor that is dropped as a result of a Microsoft Word or Excel document.\r\nI was planning to include an analysis of one of the third stage payloads – ISFB – in this post, however it would\r\nhave been extremely long, so I decided to give it it’s own post. This post will replace my original post about\r\nHancitor (Part 2, not Part 1), as this time I’ve fully analyzed the sample, and therefore do not need to rely on\r\noutside information. Both the packed and unpacked samples are available on VirusBay. Let’s get into it!\r\nMD5 (Hancitor – Packed): c07661bd4f875b6c6908f2d526958532\r\nMD5 (Hancitor – Unpacked, Unmapped): 5fe47865512eb9fa5ef2cccd9c23bcbf\r\nUnpacking Hancitor\r\nAs per usual with most malware nowadays, Hancitor’s Second Stage payload is packed, so before we get to the\r\ninteresting part, we need to unpack it, which isn’t particularly difficult to do so. I will be using Immunity\r\nDebugger to step through the unpacking, as x32dbg failed to analyze sections correctly. This will be a quick\r\nunpacking, and there won’t be much detail on the unpacking routine as this isn’t the purpose of the post. Upon\r\nopening the file in a debugger, scroll down until you see a call to EBX and put a breakpoint on that call:\r\nExecute the program and once it has hit the breakpoint, step into EBX. From there, you will see several jumps –\r\nfollow these jumps and you will notice values being pushed to the stack, until you see a call to EAX\r\n(VirtualProtect).\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 1 of 22\n\nYou can step over this and follow the jumps again. You’ll notice registers being incremented and compared, until\r\nyou hit an XOR BYTE PTR. As you probably guessed, this is a loop that XOR’s values in the main binary. If you\r\nkeep stepping over, you’ll reach a JB instruction, and just underneath is a JNO instruction. Put a breakpoint on\r\nthe JNO instruction, as shown below, and execute the program.\r\nOnce you’ve hit the breakpoint, simply step over the next few instructions, until you see a jump to EAX. Upon\r\nfollowing this jump, you’ll find a section of un-analyzed code. Right click and select Analysis-\u003eDuring next\r\nanalysis, treat selection as-\u003eCommand, and then CTRL-A. The section will be re-analyzed and should resemble\r\nsomething similar to the image below.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 2 of 22\n\nFrom there, scroll down. You’ll notice strings such as “VirtualAlloc” and “_stricmp” – this section loads different\r\nDLLs and imports functions. You can step through this and analyze it, or you can scroll down until you see the last\r\nAPI being imported, which in this case is “memcpy“. After a call to GetProcAddress ([EBP-28] here), EAX\r\n(memcpy) is moved into [EBP-30]. Put a breakpoint on this and execute the program.\r\nScroll down further and you’ll see another jump to EAX – put a breakpoint on that and run the program once\r\nagain.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 3 of 22\n\nFollowing this jump should take you to another region of memory – in this case it is at 0x000203E4. You’ll see a\r\njump near the bottom of the window, so put a breakpoint on that and execute the program again.\r\nJumping to the address will show the Substitution Box creation and scramble instructions, which creates the\r\nlookup table for RC4 decryption. We can skip over this so keep scrolling down until you see\r\nseveral IMUL instructions and another function call. Put a breakpoint on this call and run the program.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 4 of 22\n\nSeveral libraries will be loading upon running the program, but just ignore that. Once the breakpoint has been\r\ntripped, step into the function and follow the jump. From there, there will be a call to an API, and a call to a\r\nfunction. Make sure you step into this function, and follow the jump.\r\nYou will see another API call and function call. Step into the function, and there will be a jump to EAX – take this\r\njump, and it will lead you back to the original memory region of the binary.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 5 of 22\n\nOnce you get there, you will need to re-analyze the section, just like we did before. Ignore the first function call,\r\nand step into the second call.\r\nThis function has a few calls, but the important ones are near the bottom – GetMessageA, TranslateMessage,\r\nand DispatchMessageA. They will be in a loop, so simply put a breakpoint after the loop, and run the program.\r\nThis loop will result in the main Hancitor payload being written to a different region of memory, and run it, so\r\nmake sure you disconnect your machine from the network for this. You will have to pause the execution of the\r\nprogram yourself, as the loop will not exit until the payload thread has.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 6 of 22\n\nMake sure you have Process Hacker open as well, as this will allow you to dump the unpacked payload. Wait for\r\naround 30-45 seconds (although it depends), and search for RWX protected regions of memory in Process Hacker.\r\nIn this case, there is a 36 Kb section, which upon viewing, has a valid MZ header, so let’s dump it.\r\nAs we dumped the payload from memory, it is mapped, so we need to unmap it. Open the dumped file in PE-Bear\r\nand go to the Section Headers option, as shown in the image. You need to change the value of the Raw Addr. so\r\nthat it matches the value of the Virtual Addr. You then need to change the Raw size of each of the sections, except\r\nfor the last section, which is .reloc here.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 7 of 22\n\nUpon doing this, you will notice the imports section starts fixing itself. If you see a similar import table to the one\r\nshown below, congratulations, you have successfully unpacked Hancitor! We can now start analyzing the\r\nunpacked payload!\r\nAnalyzing Hancitor: Unpacked\r\nI will be statically analyzing the unpacked version of Hancitor, using IDA Pro, although you can use any\r\ndissassembler, or even dynamically analyze it.\r\nUpon opening the file, there are three functions, and then a call to ExitProcess. The first two functions are not\r\nimportant, and simply seem to be used for importing API calls and loading libraries. The third function contains\r\nall of the interesting stuff, so let’s jump into that.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 8 of 22\n\nInside the main section, there are several functions that are called. The first three are calls to a function that simply\r\nallocates a heap, with the sizing based on the argument, so we can ignore that.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 9 of 22\n\nTaking a look at the next called function, we can see a lot of stuff happening.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 10 of 22\n\nFirst, Hancitor calls GetVersion, and then calls 4 additional functions to gather more system information. The first\r\nfunction of the 4 returns a GUID for the user, based on gathered volume information and adapter addresses.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 11 of 22\n\nThe second function locates both the computer name and the username. To get the computer name, it simply\r\ncalls GetComputerName and appends an @ sign on the end. In order to get the username, rather than\r\ncalling GetUserName, it enumerates through running processes searching for explorer.exe, and when found, it\r\nopens the process, opens the process token, and then gathers the token information. This is then used in a call\r\nto LookupAccountSidA, which will return the username and the domain which the username was found on. This\r\nis then formatted together, so it will read Domain\\Username. Then, this is appended to the original computer\r\nname.\r\nThe third function is responsible for gathering the external IP address. To do so, it uses the WININET library to\r\nsend a GET request to api[.]ipify[.]com, the go-to for Hancitor. If it fails to connect to the site, it simply sets the\r\nIP as 0.0.0.0, and continues on.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 12 of 22\n\nFinally, the fourth function is used to determine the architecture of the system, whether it is x64 or x32 bit. This\r\nwill determine which string to wsprintf the data to. It attempts to import GetNativeSystemInfo, and if it fails, it\r\nwill just call GetSystemInfo. If the function returns 1, the system is a 64 bit system. Otherwise, it will be set as 32\r\nbit.\r\nOnce the architecture has been determined, the BUILD value and C2 URLs are RC4 decrypted, using native\r\nWinCrypt functions rather than a custom implementation of RC4. The BUILD represents the campaign date of the\r\nspecific Hancitor sample. In this case, the build is 17bdp12, which indicates the campaign began on the 17th of\r\nDecember.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 13 of 22\n\nOnce the decryption has finished, the values retrieved by the five functions are stored in a string using wsprintf.\r\nThe string depends on the architecture, but only the last 5 characters:\r\nGUID=%I64u\u0026BUILD=%s\u0026INFO=%s\u0026IP=%s\u0026TYPE=1\u0026WIN=%d.%d(x32)\r\nThis is stored in a buffer, which will be used in a POST request to the recently decrypted C2s. After\r\nthe wsprintf call, Hancitor begins to focus on the C2s. First, it checks to see if the C2s have been decrypted, and\r\nif not, it will decrypt them again. Once decrypted, each C2 URL is split with ‘|‘, for easy splitting. Hancitor copies\r\nthe first URL to a different region of memory and attempts to connect to it. If it fails to contact the C2, it will try\r\nwith the next URL, until it realizes all C2s are down, and then it sleeps for 60000 milliseconds, and retries. If there\r\nis still no response, it will exit. The C2s are contacted using WININET API’s, with a POST request containing the\r\nformatted data. If a C2 server is online, it will typically return a large string of encrypted data that indicates what\r\nthe malware should do next.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 14 of 22\n\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 15 of 22\n\nIf the C2 server is online and does return data, a verification function is called, which takes the returned data as an\r\nargument. The first 4 bytes in the response are checked to see if they are more than or equal to 65, and less than or\r\nequal to 90 (basically checking if they are in the alphabet and are uppercase letters). Then, it checks if the\r\ncharacter code of the second letter (response[1]) minus 90, plus 65 is equal to the character code of the third letter\r\n(response[2]). If it is equal, the function will return 0, otherwise it will run another check to see if the character\r\ncode of the fourth letter (response[3]) is equal to 90 minus the character code of the first letter (response[0]) + 65.\r\nThe result of this will be returned. If 0 is returned, the malware will return 0, otherwise it will return 1.\r\nAnd that brings a close to the first function – this will be a long one. Back to the main payload, if the last function\r\nreturned 1, Hancitor will begin to decrypt the data, otherwise it will sleep and try again. The next function call\r\naccepts the C2_Response + 4, so it discards the first 4 bytes, as they are simply for verification. Taking a look at\r\nthe function, we can see a call to a function that takes the encrypted data and the address of an empty heap that\r\nwas previously allocated. It returns a value which is stored in a variable. This particular variable is used in\r\na for loop, so we can assume that this is the size of the data. We can also assume that the empty heap will contain\r\ndata, as each character is XOR’ed using the hexadecimal value 0x7A. Once the loop has ended, it returns. So let’s\r\ntake a look at sub_3B1000.\r\nIf you have ever written a Base64 encoder/decoder in languages such as C/C++ or even Python, you may\r\nrecognize this pseudo-code as a Base64 decryption algorithm. Hancitor simply Base64 decodes the C2 response\r\nand XOR’s it using 0x7A, making it quite effortless to decrypt C2 data.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 16 of 22\n\nBefore we move onto the next functions, it is important to know what the C2 data actually looks like.\r\nAs you can see, there are 3 “sections” in this decrypted response, with each section starting with { and ending with\r\n}, and each URL being split with a |. You can also see at the start of each section there is a letter and then : – this\r\nletter indicates what Hancitor should do with the specific URL. The next function splits the sections up, by\r\nchecking for { and then copying each character to an allocated heap, until the character equals }. This data is then\r\nused in the next function.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 17 of 22\n\nThe next function takes the section of URLs and checks to see if the second character is :, and then to see if the\r\nfirst character equals; r, l, e, b, d, c,or n. If it doesn’t equal any of the characters, it loops. Otherwise, it will\r\ncontinue to the next function, which will carry out the command.\r\nWhilst Hancitor checks for 8 characters, it only uses 5 of them;  r, l, e, b and n. If the response is n, the malware\r\ndoes nothing. If it is b, it will download a file from the URL, decompress it, and inject it into SVCHOST. If it is e,\r\nit will download a file, decompress it, and execute it as a new thread. If it is l, it will download a file, decompress\r\nit, and execute it as a new thread with an argument. Finally, if the command is r, it will download a file,\r\ndecompress it, and execute it as it’s own process.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 18 of 22\n\nOne particularly interesting thing about the download and decompress routine is how it checks the first two\r\ncharacters of the decompressed, downloaded file for MZ, to make sure it is in fact an EXE or DLL. When\r\nexecuting as an own process, it checks whether or not the file is a DLL or an EXE by looking it up in the file\r\nheader, and if it is a DLL it uses RUNDLL32.exe to execute it.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 19 of 22\n\nSVCHOST.exe Injection:\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 20 of 22\n\nExecute in New Thread:\r\nExecute as own Process:\r\nOnce the process has been executed, the function returns back to the main payload, and now fully annotated, you\r\ncan view the flow of the program.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 21 of 22\n\nNow that brings an end to this full analysis of Hancitor’s second stage. I am currently working on writing a\r\nPython script that extracts Hancitor communications from PCAP files, decrypts them, and then attempts to interact\r\nwith the C2 servers to download the third stage payload as a file, which will be up on GitHub once it is complete.\r\nMy ISFB analysis should be posted soon – I am currently quite busy, but expect it soon!\r\nIOCs:\r\n Build: 17bdp12\r\n Hancitor (Packed: MD5): c07661bd4f875b6c6908f2d526958532\r\n Hancitor (Unpacked: MD5): 5fe47865512eb9fa5ef2cccd9c23bcbf\r\n Second Stage C2s:\r\n http://woodlandsprimaryacademy.org/wp-includes/(1|2|3)\r\n http://precisionpartners.org/wp-admin/includes/(1|2|3)\r\n http://precisionpartners.org/wp-admin/includes/(1|2|3)\r\n http://mail.porterranchpetnanny.com/wp-includes/(1|2|3)\r\n http://synergify.com/wp-content/themes/ward/(1|2|3)\r\n \r\nSource: https://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/\r\nPage 22 of 22",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://0ffset.net/reverse-engineering/malware-analysis/reversing-hancitor-again/"
	],
	"report_names": [
		"reversing-hancitor-again"
	],
	"threat_actors": [],
	"ts_created_at": 1775434459,
	"ts_updated_at": 1775791291,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/beae29f87e7143b806ffaef2cd99b68215a7730a.pdf",
		"text": "https://archive.orkl.eu/beae29f87e7143b806ffaef2cd99b68215a7730a.txt",
		"img": "https://archive.orkl.eu/beae29f87e7143b806ffaef2cd99b68215a7730a.jpg"
	}
}