{
	"id": "d62645ac-8885-4fb7-abcf-e37468e40785",
	"created_at": "2026-04-06T00:14:16.524618Z",
	"updated_at": "2026-04-10T03:21:23.993691Z",
	"deleted_at": null,
	"sha1_hash": "4351b8b104799055aeb63ea5a7fc141feeea044e",
	"title": "Gozi Gozi Gozi - String Decryption",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 301170,
	"plain_text": "Gozi Gozi Gozi - String Decryption\r\nBy R3dy\r\nPublished: 2025-12-16 · Archived: 2026-04-05 20:49:18 UTC\r\nDescription of the Z2A challenge\r\nThis challenge involves the ISFB malware family!\r\nReverse engineer the string decryption routine \r\nDevelop a script to automate decryption\r\nSHA256 0a66e8376fc6d9283e500c6e774dc0a109656fd457a0ce7dbf40419bc8d50936\r\nMalware\r\nBazaar\r\nlink\r\nhttps://bazaar.abuse.ch/sample/0a66e8376fc6d9283e500c6e774dc0a109656fd457a0ce7dbf40419bc8d50936/\r\nFile\r\nType\r\nDLL\r\nBasic Static Analysis\r\nUsing Detect It Easy, two sections of the provided file show some irregularities. The .crt section reaches a high entropy\r\nvalue of 7.98 \u0026 .erloc also reaches 7.98 . Moreover, the diagram shows a constant flat area on both functions with no\r\nspikes. This indicates two encrypted sections and the presence of a packer.\r\nDetect It Easy - Two packed section\r\nUnpacking\r\nIn x32dbg, it is necessary to breakpoints on the VirtualAlloc \u0026 VirtualProtect WinAPI functions. After three hits, a\r\nvalid MZ* binary appears in the dump view.\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 1 of 11\n\nx32dbg in the dump\r\nview of the returned value after VirtualAlloc\r\nThis binary is mapped, as confirmed by the hex view of the dump. Even if the sample is successfully dumped, it cannot be\r\nanalyzed correctly at this stage.\r\nPE-Bear Warning message\r\nUnmapping PE file\r\nFor proper analysis, this dumped binary needs to be converted to an unmapped binary using a PE tool such as PE-Bear.\r\nFirst, open the dumped file in PE-Bear and click on Section Hdrs tab.\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 2 of 11\n\n_PE-Bear - Section\r\nview 1 _\r\nSecond, edit the Raw Addr. field to match the Virtual Addr.\r\n PE-Bear - Section\r\nview 2\r\nThird, use the this formula to edit the Raw size : Raw size n = VA of n+1 - VA of n . Finally, set the Virtual Size to\r\nmatch the Raw size .\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 3 of 11\n\nPE-Bear - Section\r\nview 3\r\nAfter editing the Section Hdrs tab, go to the Optional Hdr tab and verify the Image Base value. It must match the\r\npacked malware’s image base. You can then save the Gozi dump file to disk. To confirm that the modifications are applied\r\nsuccessfully, inspect the different libraries listed in the Imports tab and launch IDA !\r\n PE-Bear - Imports Tab\r\nHere is a comparaison between two screenshot on IDA (before \u0026 after unmapping the PE file)\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 4 of 11\n\nIDA Before Unmapping\r\n IDA After\r\nUnmapping\r\nStatic Analysis\r\nThis first function called sub_831EFE checks the MZ signature at the beginning of the file, after, a handle to the DLL is\r\nretrieved. This handle will be used in the next function which involves APC Injection. This method uses 2 interesting\r\nparameters :\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 5 of 11\n\nA function (analyzed in The APC function part)\r\nA handle to the dll\r\n1\r\n2\r\n3\r\npossible_dll = retrieve_dll_handle(lpReserved);\r\nhObject = APC_injection((PAPCFUNC)sub_831B7F, (ULONG_PTR)possible_dll, \u0026fdwReason);\r\nAPC Injection\r\nThis technique allows a program to execute code in a specific thread by attaching to an APC queue. The injected\r\ncode will be executed by the thread when it exists of alertable state like SleepEx , SignalObjectAndWait or\r\nWaitForSingleObjectsEx .\r\nThis APC_injection function creates a new thread with the starting routine SleepEx to trigger the APC queue. Then, the\r\nAPI QueueUserAPC is run with 3 parameters:\r\npfnAPC - The next function to be analyzed\r\nThread - The thread handle\r\ndwData - Unique value transmitted, here the DLL handle\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\nThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)SleepEx, lpParameter, 0, lpThreadId);\r\nv4 = Thread;\r\nif ( Thread \u0026\u0026 !QueueUserAPC(pfnAPC, Thread, dwData) )\r\n {\r\n LastError = GetLastError();\r\n TerminateThread(v4, LastError);\r\n CloseHandle(v4);\r\n v4 = 0;\r\n SetLastError(LastError);\r\n }\r\nreturn v4;\r\nThe APC function\r\nLet’s dive into the APC function, used as a parameter of QueueUserAPC above ! The thread is retrieved and pinned to the\r\nCPU 0 with SetThreadAffinityMask . After, the thread priority is set to -1 (THREAD_PRIORITY_BELOW_NORMAL).\r\n1\r\n2\r\n3\r\n4\r\n CurrentThread = GetCurrentThread();\r\n if ( SetThreadAffinityMask(CurrentThread, 1u) )\r\n SetThreadPriority(CurrentThread, THREAD_PRIORITY_BELOW_NORMAL);\r\n v2 = query_info(Parameter);\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 6 of 11\n\nThe method query_info is the “main” of our APC function. It begins with a check function that verifies the Windows OS\r\nversion. This program refuses to run on Windows \u003c= XP/2003 .\r\n1\r\n2\r\nif (MajorVersion == 5 \u0026\u0026 MinorVersion == 0)\r\n/* ... */\r\nThen, a handle to the current process is collected with several access rights described in the code below.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\ncurrent_pid = GetCurrentProcessId();\r\nprocess_handle = (int)OpenProcess(0x10047Au, 0, current_pid);\r\n/*\r\n0x10047A =\r\nPROCESS_QUERY_INFORMATION |\r\nPROCESS_VM_READ |\r\nPROCESS_VM_WRITE |\r\nPROCESS_VM_OPERATION |\r\nPROCESS_DUP_HANDLE |\r\nPROCESS_SET_INFORMATION\r\n*/\r\nThe following code creates a pseudo-random/magic value using the NtQuerySystemInformation API with\r\nSystemProcessorPerformanceInformation class. This parameter returns an array of\r\nSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION structures, one for each processor installed in the system. Furthermore, the\r\nIdleTime value (first field of the structure) is then used modulo Ox13 (19) to compute this value ( unk_value ). As a\r\nresult, unk_value is a number between 0 \u0026 20 (because NT_STATUS is equal to 0 if the API works fine).\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\nresult = w_open_process();\r\n handle_selfProcess = (DWORD)result;\r\n if ( !result )\r\n {\r\n do\r\n {\r\n unk_arg = 0;\r\n ReturnLength = 0;\r\n SystemInformationLength = 48;\r\n do\r\n {\r\n SystemInformation = (_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *)w_heap_alloc(SystemInformationLengt\r\n if ( SystemInformation )\r\n {\r\n ret_NtQuerySysInf = NtQuerySystemInformation(\r\n SystemProcessorPerformanceInformation,\r\n SystemInformation,\r\n SystemInformationLength,\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 7 of 11\n\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n \u0026ReturnLength);\r\n handle_selfProcess = (unsigned __int16)ret_NtQuerySysInf;\r\n if ( (unsigned __int16)ret_NtQuerySysInf == 4 )\r\n SystemInformationLength += 48;\r\n unk_value = SystemInformation-\u003eIdleTime.LowPart % 0x13 + ret_NtQuerySysInf + 1;\r\n w_heap_free(SystemInformation);\r\n /* ... */*\r\nThis value is then used later in sub_83197C . The function sub_831922 checks the presence of the .bss section. Iterating\r\nthrough all the sections in the binary using section-\u003eName == 'ssb.' .\r\nIDA - Iteration to find .bss section\r\nFinally, this function stores the Virtual Address and Size Raw Data of the section.\r\nOnce the malware get these values, it converts the size of the .bss section to a number of memory pages using the\r\nfollowing formula :\r\n1 pages = (bss_sizeRawData \u003e\u003e 12) + ((bss_sizeRawData \u0026 0xFFF) != 0);\r\nX \u003e\u003e 12 = X / 4096 = np X \u0026 0xFFF = X % 4096 = nb != 0 -\u003e pn++ np (number pages) - nb (number\r\nbytes) So if X = 6500 -\u003e np = 2 —-\u003e 2 memory pages needed to hold 6500 bytes\r\nUsing the number of pages, it allocates a memory area using VirtualAlloc :\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 8 of 11\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\nv5 = VirtualAlloc(\r\n0,\r\npages \u003c\u003c 12, // pn * 4096 = bytes\r\nMEM_COMMIT | MEM_RESERVE, //0x3000\r\nPAGE_READWRITE // 0x4\r\n);\r\nThen, the decrypt function is called, using the pseudo-code shows a lot of information that can mislead the analyst. The\r\ncode below displays the string used by the malware : “ Apr 26 2022 ”, surely the campaign date of gozi.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\nstrcpy((char *)weird_str, \"26 2022\");\r\ndecrypt(\r\n(_DWORD *)((char *)cpy_bss_offset + delta),\r\n(int)cpy_bss_offset,\r\nbss_VA + first_dword_date[0] + *(_DWORD *)second_dword_date - counter + unk_value - 1,\r\n1024);\r\nTo put it in a nutshell, the decrypt function iterates block by block over the .bss section. Each block is decrypted by\r\nsubtracting the key from the previous cyphertext. This key is a value that depends on the value derived from the\r\nNtQuerySystemInformation API ( unk_arg ).\r\nThe most important line is 16 , where current_block += prev_block - key; is equivalent to plaintext = ciphertext -\r\nkey + prev_ciphertext;\r\nUsing all the information we gather while analyzing this sample, a string decryption script can be code.\r\nStrings Decryption Code\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 9 of 11\n\nThe behavior observed above is translated into the following Python script:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n48\r\nimport pefile\r\nimport struct\r\npath = \"////////////\"\r\ndate = b\"Apr 26 2022\" # Date of campaign\r\ndef get_bss_section(pe):\r\n for section in pe.sections:\r\n if b\".bss\" in section.Name:\r\n data = file[section.PointerToRawData:section.PointerToRawData+section.SizeOfRawData]\r\n return section.VirtualAddress, data\r\ndef retrieve_key(bss_va, date, index):\r\n date_first_part = struct.unpack(\"\u003cI\", date[0:4])[0]\r\n date_second_part = struct.unpack(\"\u003cI\", date[4:8])[0]\r\n return date_first_part + date_second_part + bss_va + index\r\ndef decrypt(data, key):\r\n ct = 0\r\n final = b\"\"\r\n for i in range (0, len(data), 4):\r\n encoded = struct.unpack(\"I\", data[i:i+4])[0]\r\n if encoded:\r\n \r\n final += struct.pack(\"I\", (ct - key + encoded) \u0026 0xffffffff)\r\n ct = encoded\r\n \r\n else:\r\n break\r\n return final\r\nfound = False\r\npe = pefile.PE(path)\r\nfile = open(path, \"rb\").read()\r\nbss_va, data = get_bss_section(pe)\r\nfor i in range (0,20):\r\n key = retrieve_key(bss_va, date, i)\r\n decrypted = decrypt(data, key)\r\n if b\"NTDLL.DLL\" in decrypted:\r\n found = True\r\n break\r\nif found:\r\n print(decrypted)\r\n print(\"-\u003e Decrypted strings above !\")\r\n print(\"-\u003e Magic value : \" + hex(i))\r\n print(\"-\u003e Key : \" + hex(key))\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 10 of 11\n\nIn the next article on the Gozi sample, I will continue the analysis and explore the next steps. See you next time !\r\nR3dy —————-\r\n“Gouzi-gouzi is a French onomatopoeic expression used in infant-directed speech to amuse babies”\r\nSource: https://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nhttps://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://r3dy.fr/posts/gozi-gozi-gozi-string-decryption/"
	],
	"report_names": [
		"gozi-gozi-gozi-string-decryption"
	],
	"threat_actors": [],
	"ts_created_at": 1775434456,
	"ts_updated_at": 1775791283,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/4351b8b104799055aeb63ea5a7fc141feeea044e.pdf",
		"text": "https://archive.orkl.eu/4351b8b104799055aeb63ea5a7fc141feeea044e.txt",
		"img": "https://archive.orkl.eu/4351b8b104799055aeb63ea5a7fc141feeea044e.jpg"
	}
}