{
	"id": "1430216a-646a-439d-aaf8-fad86b839152",
	"created_at": "2026-04-06T00:15:05.114995Z",
	"updated_at": "2026-04-10T03:20:51.031404Z",
	"deleted_at": null,
	"sha1_hash": "b56d78b4688175b1cc73b85765eeac3f13bfab07",
	"title": "A Walk-Through Tutorial, with Code, on Statically Unpacking the FinSpy VM: Part One, x86 Deobfuscation — Möbius Strip Reverse Engineering",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 89855,
	"plain_text": "A Walk-Through Tutorial, with Code, on Statically Unpacking the\r\nFinSpy VM: Part One, x86 Deobfuscation — Möbius Strip Reverse\r\nEngineering\r\nBy Rolf Rolles\r\nPublished: 2018-01-23 · Archived: 2026-04-05 17:29:52 UTC\r\n1. Introduction\r\nNormally when I publish about breaking virtual machine software protections, I do so to present new techniques.\r\nPast examples have included:\r\nWriting an IDA processor module to unpack a VM\r\nLogging VM execution with DLL injection\r\nCompiler-based techniques to unpack commercial-grade VMs\r\nAbstract interpretation-based techniques to deobfuscate control flow\r\nAutomated generation of peephole superdeobfuscators\r\nProgram synthesis-based deobfuscation of metamorphic behavior in VM handlers\r\nToday's document has a different focus. I am not going to be showcasing any particularly new techniques. I will,\r\ninstead, be providing a step-by-step walk-through of the process I used to analyze the FinSpy VM, including my\r\nthoughts along the way, the procedures and source code I used, and summaries of the notes I took. The interested\r\nreader is encouraged to obtain the sample and walk through the analysis process for themselves.\r\nI have three motives in publishing this document:\r\n1. I think it's in the best interest of the security defense community if every malware analyst is able to unpack\r\nthe FinSpy malware VM whenever they encounter it (for obvious reasons).\r\n2. Reverse engineering is suffering from a drought of hands-on tutorial material in modern times. I was\r\nfortunate to begin reverse engineering when such tutorials were common, and they were invaluable in\r\nhelping me learn the craft. Slides are fine for large analyses, but for smaller ones, let's bring back tutorials\r\nfor the sake of those that have followed us.\r\n3. Publications on obfuscation, especially virtualization obfuscation, have become extremely abstruse\r\nparticularly in the past five years. Many of these publications are largely inaccessible to those not well-versed in master's degree-level program analysis (or above). I want to demonstrate that easier techniques\r\ncan still produce surprisingly fast and useful results for some contemporary obfuscation techniques. (If you\r\nwant to learn more about program analysis-based approaches to deobfuscation, there is currently a public\r\noffering of my SMT-based program analysis training class, which has over 200 slides on modern\r\ndeobfuscation with working, well-documented code.)\r\nUpdate: the rest of this document, the second and third parts, are now available online at the links just given.\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 1 of 9\n\n2. Initial Steps\r\nThe first thing I did upon learning that a new FinSpy sample with VM was publicly available was, of course, to\r\nobtain the sample. VirusTotal gave the SHA256 hash; and I obtained the corresponding sample from Hybrid-Analysis.\r\nThe next step was to load the sample into IDA. The navigation bar immediately tipped me off that the binary was\r\nobfuscated:\r\nThe first half of the .text section is mostly colored grey and red, indicating data and non-function code\r\nrespectively.\r\nThe second half of the .text section is grey in the navigation bar, indicating data turned into arrays.\r\nA normal binary would have a .text section that was mostly blue, indicating code within functions.\r\n3. Analysis of WinMain: Suspicions of VM-Based Obfuscation\r\nIDA's auto-analysis feature identified that the binary was compiled by the Microsoft Visual C compiler. I began by\r\nidentifying the WinMain function. Normally IDA would do this on my behalf, but the code at that location is\r\nobfuscated, so IDA did not name it or turn it into a function. I located WinMain by examining the\r\n___tmainCRTStartup function from the Visual C Run-Time and finding where it called into user-written code. The\r\nfirst few instructions resembled a normal function prologue; from there, the obfuscation immediately began.\r\n.text:00406154  mov  edi, edi  ; Normal prologue\r\n.text:00406156  push  ebp  ; Normal prologue\r\n.text:00406157  mov  ebp, esp  ; Normal prologue\r\n.text:00406159  sub  esp, 0C94h  ; Normal prologue\r\n.text:0040615F  push  ebx  ; Save registers #1\r\n.text:00406160  push  esi  ; Save registers #1\r\n.text:00406161  push  edi  ; Save registers #1\r\n.text:00406162  push  edi  ; Save registers #2\r\n.text:00406163  push  edx  ; Save registers #2\r\n.text:00406164  mov  edx, offset byte_415E41 ; Obfuscation - #1\r\n.text:00406169  and  edi, 0C946B9C3h  ; Obfuscation - #2\r\n.text:0040616F  sub  edi, [edx+184h] ; Obfuscation - #3\r\n.text:00406175  imul  edi, esp, 721D31h  ; Obfuscation - #4\r\n.text:0040617B  stc  ; Obfuscation\r\n.text:0040617C  sub  edi, [edx+0EEh] ; Obfuscation - #5\r\n.text:00406182  shl  edi, cl  ; Obfuscation\r\n.text:00406184  sub  edi, [edx+39h] ; Obfuscation - #6\r\n.text:0040618A  shl  edi, cl  ; Obfuscation\r\n.text:0040618C  imul  edi, ebp  ; Obfuscation\r\n.text:0040618F  mov  edi, edi  ; Obfuscation\r\n.text:00406191  stc  ; Obfuscation\r\n.text:00406192  sub  edi, 0A14686D0h  ; Obfuscation\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 2 of 9\n\n; ... obfuscation continues ...\r\n.text:004065A2 pop edx ; Restore registers\r\n.text:004065A3  pop  edi  ; Restore registers\r\nThe obfuscation in the sequence above continues for several hundred instructions, nearly all of them consisting of\r\nrandom-looking modifications to the EDI register. I wanted to know A) whether the computations upon EDI were\r\nentirely immaterial junk instructions, or whether a real value was being produced by this sequence, and B)\r\nwhether the memory references in the lines labeled #1, #3, #5, and #6 were meaningful.\r\nAs for the first question, note that the values of the registers upon entering this sequence are unknown. We are,\r\nafter all, in WinMain(), which uses the __cdecl calling convention, meaning that the caller did not pass arguments\r\nin registers. Therefore, the value computed on line #2 is unpredictable and can potentially change across different\r\nexecutions. Also, the value computed on line #4 is pure gibberish -- the value of the stack pointer will change\r\nacross runs (and the modification to EDI overwrites the values computed on lines #1-#3).\r\nAs for the second question, I skimmed the obfuscated listing and noticed that there were no writes to memory,\r\nonly reads, all intertwined with gibberish instructions like the ones just described. Finally, the original value of edi\r\nis popped off the stack at the location near the end labeled \"restore registers\". So I was fairly confident that I was\r\nlooking at a sequence of instructions meant to do nothing, producing no meaningful change to the state of the\r\nprogram.\r\nFollowing that was a short sequence:\r\n.text:004065A4  push  5A403Dh  ; Obfuscation\r\n.text:004065A9  push  ecx  ; Obfuscation\r\n.text:004065AA  sub  ecx, ecx  ; Obfuscation\r\n.text:004065AC  pop  ecx  ; Obfuscation\r\n.text:004065AD  jz  loc_401950  ; Transfer control elsewhere\r\n.text:004065AD ; ---------------------------------------------------------------------------\r\n.text:004065B3  db 5 dup(0CCh)\r\n.text:004065B8 ; ---------------------------------------------------------------------------\r\n.text:004065B8  mov  edi, edi\r\n.text:004065BA  push  ebp\r\n.text:004065BB  mov  ebp, esp\r\n.text:004065BD  sub  esp, 18h\r\n; ... followed by similar obfuscation to what we saw above ...\r\nBy inspection, this sequence just pushes the value 5A403Dh onto the stack, and transfers control to loc_401950.\r\n(The \"sub ecx, ecx\" instruction above sets the zero flag to 1, therefore the JZ instruction will always branch.) \r\nNext we see the directive \"db 5 dup(0CCh)\" followed by \"mov edi, edi\". Reverse engineers will recognize these\r\nsequences as the Microsoft Visual C compiler's implementation of hot-patching support. The details of hot-patching are less important than the observation that I expected that the original pre-obfuscated binary contained a\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 3 of 9\n\nfunction that began at the address of the first sequence, and ended before the \"db 5 dup(0CCh)\" sequence. I.e. I\r\nexpect that the obfuscator disassembled all of the code within this function, replaced it with gibberish instructions,\r\nplaced a branch at the end to some other location, and then did the same thing with the next function.\r\nThis is a good sign that we're dealing with a virtualization-based obfuscator: namely, it looks like the binary was\r\ncompiled with an ordinary compiler, then passed to a component that overwrote the original instructions (rather\r\nthan merely encrypting them in-place, as would normal packers). \r\n4. Learning More About the VM Entrypoint and VM Pre-Entry\r\nRecall again the second sequence of assembly code from the previous sequence:\r\n.text:004065A4  push  5A403Dh  ; Obfuscation - #1\r\n.text:004065A9  push  ecx  ; Obfuscation\r\n.text:004065AA  sub  ecx, ecx  ; Obfuscation\r\n.text:004065AC  pop  ecx  ; Obfuscation\r\n.text:004065AD  jz  loc_401950  ; Transfer control elsewhere\r\nSince -- by supposition -- all of the code from this function was replaced with gibberish, there wasn't much to\r\nmeaningfully analyze. My only real option was to examine the code at the location loc_401950, the target of the\r\nJZ instruction on the last line. The first thing I noticed at this location, loc_401950, was that there were 125\r\nincoming references, nearly all of them of the form \"jz loc_401950\", with some of the form \"jmp loc_401950\".\r\nHaving analyzed a number of VM-based obfuscators in my day, this location fits the pattern of being the part of\r\nthe VM known as the \"entrypoint\" -- the part where the virtual CPU begins to execute. Usually this location will\r\nsave the registers and flags onto the stack, before performing any necessary setup, and finally beginning to\r\nexecute VM instructions. VM entrypoints usually require a pointer or other identifier to the bytecode that will be\r\nexecuted by the VM; maybe that's the value from the instruction labeled #1 in the sequence above? Let's check\r\nanother incoming reference to that location to verify:\r\n.text:00408AB8  push  5A7440h ; #2\r\n.text:00408ABD  push  eax\r\n.text:00408ABE  sub  eax, eax\r\n.text:00408AC0  pop  eax\r\n.text:00408AC1  jz  loc_401950\r\nThe other location leading to the entrypoint is functionally identical, apart from pushing a different value onto the\r\nstack. This value is not a pointer; it does not correspond to an address within the executable's memory image.\r\nNevertheless, we expect that this value is somehow responsible for telling the VM entrypoint where the bytecode\r\nis located.\r\n5. Analyzing the VM Entrypoint Code\r\nSo far we have determined that loc_401950 is the VM entrypoint, targeted by 125 branching locations within the\r\nbinary, which each push a different non-pointer DWORD before branching. Let's start analyzing that code:\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 4 of 9\n\n.text:00401950 loc_401950:\r\n.text:00401950 0F 82 D1 02 00 00 jb loc_401C27\r\n.text:00401956 0F 83 CB 02 00 00  jnb  loc_401C27\r\nImmediately we see an obvious and well-known form of obfuscation. The first line jumps to loc_401C27 if the\r\n\"below\" conditional is true, and the second line jumps to loc_401C27 if the \"not below\" conditional is true. I.e.,\r\nexecution will reach loc_401C27 if either \"below\" or \"not below\" is true in the current EFLAGS context. I.e.,\r\nthese two instructions will transfer control to loc_401C27 no matter what is in EFLAGS -- and in particular, we\r\nmight as well replace these two instructions with \"jmp loc_401C27\", as the effect would be identical.\r\nContinuing to analyze at loc_401C27, we see another instance of the same basic idea:\r\n.text:00401C27  loc_401C27:\r\n.text:00401C27 77 CD  ja  short loc_401BF6\r\n.text:00401C29 76 CB  jbe  short loc_401BF6\r\nHere we have an unconditional branch to loc_401BF6, split across two instructions -- a \"jump if above\", and\r\n\"jump if below or equals\", where \"above\" and \"below or equals\" are logically opposite and mutually exclusive\r\nconditions.\r\nAfter this, at location loc_401BF6, there is a legitimate-looking instruction (push eax), followed by another\r\nconditional jump pair to loc_401D5C. At that location, there is another legitimate-looking instruction (push ecx),\r\nfollowed by a conditional jump pair to loc_4019D2. At that location, there is another legitimate-looking\r\ninstruction (push edx), followed by another conditional jump pair. It quickly became obvious that every legitimate\r\ninstruction was interspersed between one or two conditional jump pairs -- there are hundreds or thousands of these\r\npairs throughout the binary.\r\nThough an extremely old and not particularly sophisticated form of obfuscation, it is nevertheless annoying and\r\ndegrades the utility of one's disassembler. As I discussed in a previous entry on IDA processor module extensions,\r\nIDA does not automatically recognize that two opposite conditional branches to the same location are an\r\nunconditional branch to that location. As a result, IDA thinks that the address following the second conditional\r\nbranch must necessarily contain code. Obfuscation authors exploit this by putting junk bytes after the second\r\nconditional branch, which then causes the disassembler to generate garbage instructions, which may overlap and\r\nocclude legitimate instructions following the branch due to the variable-length encoding scheme for X86. (Note\r\nthat IDA is not to blame for this conundrum -- ultimately these problems are undecidable under ordinary Von\r\nNeumann-based models of program execution.) The result is that many of the legitimate instructions get lost in the\r\ndreck generated by this process, and that, in order to follow the code as usual in manual static analysis, one would\r\nspend a lot of time manually undefining the gibberish instructions and re-defining the legitimate ones.\r\n6. Deobfuscating the Conditional Branch Obfuscation: Theory and Practice\r\nManually undefining and redefining instructions as just described, however, would be a waste of time, so let's not\r\ndo that. Speaking of IDA processor modules, once it became clear that this pattern repeated between every\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 5 of 9\n\nlegitimate non-control-flow instruction, I got the idea to write an IDA processor module extension to remove the\r\nobfuscation automatically. IDA processor module extensions give us the ability to have a function of ours called\r\nevery time the disassembler encounters an instruction. If we could recognize that the instruction we were\r\ndisassembling was a conditional branch, and determine that the following instruction contains its opposite\r\nconditional branch to the same target as the first, we could replace the first one with an unconditional branch and\r\nNOP out the second branch instruction.\r\nThus, the first task is to come up with a way to recognize instances of this obfuscation. It seemed like the easiest\r\nway would be to do this with byte pattern-recognition. In my callback function that executes before an instruction\r\nis disassembled, I can inspect the raw bytes to determine whether I'm dealing with a conditional branch, and if so,\r\nwhat the condition is and the branch target. Then I can apply the same logic to determine whether the following\r\ninstruction is a conditional branch and determine its condition and target. If the conditions are opposite and the\r\nbranch targets are the same, we've found an instance of the obfuscation and can neutralize it.\r\nIn practice, this is even easier than it sounds! Recall the first example from above, reproduced here for ease of\r\nreading:\r\n.text:00401950 0F 82 D1 02 00 00  jb  loc_401C27\r\n.text:00401956 0F 83 CB 02 00 00  jnb  loc_401C27\r\nEach of these two instructions is six bytes long. They both begin with the byte 0F (the x86 two-byte escape\r\nopcode stem), are then followed by a byte in the range of 80 to 8F, and are then followed by a DWORD encoding\r\nthe displacement from the end of the instructions to the branch targets. As a fortuitous quirk of x86 instruction\r\nencodings, opposite conditional branches are encoded with adjacent bytes. I.e. 82 represents the long form of JB,\r\nand 83 represents the long form of JNB. Two long branches have opposite condition codes if and only if their\r\nsecond opcode byte differs from one another in the lowest bit (i.e. 0x82 ^ 0x83 == 0x01). And note also that the\r\nDWORDs following the second opcode byte differ by exactly 6 -- the length of a long conditional branch\r\ninstruction.\r\nThat's all we need to know for the long conditional branches. There is also a short form for conditionals, shown in\r\nthe second example above and reproduced here for ease of reading:\r\n.text:00401C27 77 CD  ja  short loc_401BF6\r\n.text:00401C29 76 CB  jbe  short loc_401BF6\r\nVirtually identical comments apply to these sequences. The first bytes of both instructions are in the range of 0x70\r\nto 0x7F, opposite conditions have differing lowest bits, and the second bytes differ from one another by exactly 2 -\r\n- the length of a short conditional branch instruction.\r\n7. Deobfuscating the Conditional Branch Obfuscation: Implementation\r\nI started by copying and pasting my code from the last time I did something like this. I first deleted all the code\r\nthat was specific to the last protection I broke with an IDA processor module extension. Since I've switched to\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 6 of 9\n\nIDA 7.0 in the meantime, and since IDA 7.0 made breaking changes vis-a-vis prior APIs, I had to make a few\r\nmodifications -- namely, renaming the custom analysis function from deobX86Hook::custom_ana(self) to\r\ndeobX86Hook::ev_ana_insn(self, insn), and replacing every reference to idaapi.cmd.ea with insn.ea. Also, my\r\nprevious example would only run if the binary's MD5 matched a particular sum, so I copied and pasted the sum of\r\nmy sample out of IDA's database preamble over the previous MD5.\r\nFrom there I had to change the logic in custom_ana. The result was even simpler than my last processor module\r\nextension. Here is the logic for recognizing and deobfuscating the short form of the conditional branch\r\nobfuscation:\r\nb1 = idaapi.get_byte(insn.ea)\r\nif b1 \u003e= 0x70 and b1 \u003c= 0x7F:\r\n d1 = idaapi.get_byte(insn.ea+1)\r\n b2 = idaapi.get_byte(insn.ea+2)\r\n d2 = idaapi.get_byte(insn.ea+3)\r\n if b2 == b1 ^ 0x01 and d1-2 == d2:\r\n # Replace first byte of first conditional with 0xEB, the opcode for \"JMP rel8\"\r\n idaapi.put_byte(insn.ea, 0xEB)\r\n # Replace the following instruction with two 0x90 NOP instructions\r\n idaapi.put_word(insn.ea+2, 0x9090)\r\nDeobfuscating the long form is nearly identical; see the code for details.\r\n8. Admiring My Handiwork, Cleaning up the Database a Bit\r\nNow I copied the processor module extension to %IDA%\\plugins and re-loaded the sample. It had worked! The\r\nVM entrypoint had been replaced with:\r\n.text:00401950 loc_401950:\r\n.text:00401950  jmp  loc_401C27\r\nThough the navigation bar was still largely red and ugly, I immediately noticed a large function in the middle of\r\nthe text section:\r\nLooking at it in graph mode, we can see that it's kind of ugly and not entirely as nice as analyzing unobfuscated\r\nX86, but considering how trivial it was to get here, I'll take it over the obfuscated version any day. The red nodes\r\ndenote errant instructions physically located above the valid ones in the white nodes. IDA's graphing algorithm\r\nincludes any code within the physically contiguous region of a function's chunks in the graph display, regardless\r\nof whether they have incoming code cross-references, likely to make displays of exception handlers nicer. It\r\nwould be easy enough to remove these and strip the JMP instructions if you wanted to write a plugin to do so.\r\nNext I was curious about the grey areas in the .text section navigation bar held. (Those areas denote defined data\r\nitems, mixed in with the obfuscated code in the .text section.) I figured that the data held there was most likely\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 7 of 9\n\nrelated to the obfuscator. I spent a minute looking at the grey regions and found this immediately after the defined\r\nfunction:\r\n.text:00402AE0  dd offset loc_402CF2\r\n.text:00402AE4  dd offset loc_402FBE\r\n; ... 30 similar lines deleted ...\r\n.text:00402B60  dd offset loc_4042DC\r\n.text:00402B64  dd offset loc_40434D\r\n34 offsets, each of which contains code. Those are probably the VM instruction handlers. For good measure, let's\r\nturn those into functions with an IDAPython one-liner:\r\nfor pFuncEa in xrange(0x00402AE0, 0x00402B68, 4):\r\n idaapi.add_func(idaapi.get_long(pFuncEa))\r\nNow a large, contiguous chunk of the navigation bar for the .text section is blue. And at this point I realized I had\r\nforgotten to create a function at the original dispatcher location, so I did that manually and here was the resulting\r\nnavigation bar:\r\nHex-Rays doesn't do a very good job with any of the functions we just defined, since they were originally written\r\nin assembly language and use instructions and constructs not ordinarily produced by compilers. I don't blame Hex-Rays for that and I hope they continue to optimize for standard compiler-based use cases and not weird ones like\r\nthis.\r\nLastly, I held PageDown scrolling through the text section to see what was left. The majority of it was VM\r\nentrypoints like those we saw in section 3. There were a few functions that appeared like they had been produced\r\nby a compiler.\r\nSo now we have assessed what's in the text section -- a VM with 34 handlers, 125+ virtualized functions, and a\r\nhandful of unvirtualized ones. Next time we'll take a look at the VM.\r\n9. Preview of Parts 2 and 3, and Beyond\r\nAfter this I spent a few hours analyzing the VM entrypoint and VM instruction handlers. Next, through static\r\nanalysis I obtained the bytecode for the VM program contained within this sample. I then wrote a disassembler for\r\nthe VM. That's part two.\r\nFrom there, by staring at the disassembled VM bytecode I was able to write a simple pattern-based deobfuscator.\r\nAfter that I re-generated the X86 machine code, which was not extremely difficult, but it was more laborious than\r\nI had originally anticipated. That's part three.\r\nAfter that, I re-inserted the X86 machine code into the original binary and analyzed it. It turned out to be a fairly\r\nsophisticated dropper for one of two second-stage binaries. It was fairly heavy on system internals and had a few\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 8 of 9\n\ntricks that aren't widely documented, so I may publish one or more of those as separate entries, and/or I may\r\npublish an analysis of the entire dropper.\r\nFinally, I analyzed -- or rather, still am analyzing -- the second-stage binaries. They may or may not prove worthy\r\nof publication.\r\n \r\nSource: http://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nhttp://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA",
		"Malpedia"
	],
	"references": [
		"http://www.msreverseengineering.com/blog/2018/1/23/a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation"
	],
	"report_names": [
		"a-walk-through-tutorial-with-code-on-statically-unpacking-the-finspy-vm-part-one-x86-deobfuscation"
	],
	"threat_actors": [],
	"ts_created_at": 1775434505,
	"ts_updated_at": 1775791251,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/b56d78b4688175b1cc73b85765eeac3f13bfab07.pdf",
		"text": "https://archive.orkl.eu/b56d78b4688175b1cc73b85765eeac3f13bfab07.txt",
		"img": "https://archive.orkl.eu/b56d78b4688175b1cc73b85765eeac3f13bfab07.jpg"
	}
}