{
	"id": "5df32b47-de52-4480-b896-0374c1fed667",
	"created_at": "2026-04-06T00:16:52.842247Z",
	"updated_at": "2026-04-10T03:22:50.064262Z",
	"deleted_at": null,
	"sha1_hash": "db4237de5b592f87d4c8775722bdc5589dce6950",
	"title": "On the Royal Road",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 177730,
	"plain_text": "On the Royal Road\r\nPublished: 2020-03-21 · Archived: 2026-04-05 16:07:36 UTC\r\nIntro\r\nRoyal Road or 8.t is one of the most known RTF weaponizer, its used and shared mostly amongst Chinese\r\nspeaking actors - there are also couple very good publications including one form nao_sec, Sebdraven and\r\nAnomali. It was on my todo list for some time, and thanks to recent twitter discussion as well as quarantine time i\r\nfinally took a deeper look at it. We’ll go into how to quickly analyze RTF maldocs, quickly tear-down shellcode\r\nused and finally how to extract embedded payload.\r\nGetting shellcode\r\nDocument that will be used as an example caused a discussion on twitter regarding actor behind this attack due to\r\nsome code overlap of a dropped payload. We wont talk about this part tho, aforementioned document has a sha256\r\n1527f7b9bdea7752f72ffcd8b0a97e9f05092fed2cb9909a463e5775e12bd2d6 and in order to lures victim it\r\nexploits current pandemic situation having a title President discusses budget savings due to coronavirus with\r\nFinance Minister.rtf\r\nLike with most documents, you can relay on oletools to help you with dissecting rtfs, rtfobj does a great job\r\nextracting both shellcode and encoded payload.\r\nFile: '/tmp/5e31d16d6bf35ea117d6d2c4d42ea879.bin' - size: 574379 bytes\r\n---+----------+---------------------------------------------------------------\r\nid |index |OLE Object\r\n---+----------+---------------------------------------------------------------\r\n0 |0000CAE6h |format_id: 2 (Embedded)\r\n | |class name: 'Package'\r\n | |data size: 254164\r\n | |OLE Package object:\r\n | |Filename: u'wd32PrvSE.wmf'\r\n | |Source path: u'C:\\\\Windows\\\\wd32PrvSE.wmf'\r\n | |Temp path = u'C:\\\\Windows\\\\wd32PrvSE.wmf'\r\n | |MD5 = 'b33dabc3e9e653ce14fdc5323cec12f8'\r\n---+----------+---------------------------------------------------------------\r\n1 |00088D1Eh |format_id: 2 (Embedded)\r\n | |class name: 'Equation.2\\x00\\x124Vx\\x90\\x124VxvT2'\r\n | |data size: 6436\r\n | |MD5 = '865ea38d8074829351a66826ebab2fe9'\r\n---+----------+---------------------------------------------------------------\r\n2 |00088D04h |Not a well-formed OLE object\r\n---+----------+---------------------------------------------------------------\r\nSaving file embedded in OLE object #0:\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 1 of 13\n\nformat_id = 2\r\n class name = 'Equation.2\\x00\\x124Vx\\x90\\x124VxvT2'\r\n data size = 6436\r\n saving to file /tmp/5e31d16d6bf35ea117d6d2c4d42ea879.bin_object_00088D1E.bin\r\n md5 865ea38d8074829351a66826ebab2fe9\r\nShellcode stub\r\nFrom output above we can assume that document attempts to exploit a bug in Equation Editor so object number 1\r\nwill most likely contain a shellcode and will be a target of our analysis. Let’s throw it in our favorite disassembler.\r\nUnfortunately IDA doesn’t reveal any code which hints us that somewhere there there is a decoding stub,\r\nresponsible for decoding actual shellcode. Indeed we can find one at offset 6Ah\r\nseg000:0000006A E8 FF FF FF FF call near ptr loc_6A+4\r\nseg000:0000006F C3 retn\r\nseg000:00000070 ; ----------------------------------------------------------------------\r\nseg000:00000070 5F pop edi\r\nseg000:00000071 83 C7 1A add edi, 1Ah\r\nseg000:00000074 33 C9 xor ecx, ecx\r\nseg000:00000076 66 B9 A5 0B mov cx, 0BA5h\r\nseg000:0000007A\r\nseg000:0000007A loc_7A: ; CODE XREF: seg000:00000087↓j\r\nseg000:0000007A 66 83 3F 00 cmp word ptr [edi], 0\r\nseg000:0000007E 74 05 jz short loc_85\r\nseg000:00000080 66 81 37 C3 90 xor word ptr [edi], 90C3h\r\nseg000:00000085\r\nseg000:00000085 loc_85: ; CODE XREF: seg000:0000007E↑j\r\nseg000:00000085 47 inc edi\r\nseg000:00000086 47 inc edi\r\nseg000:00000087 E2 F1 loop loc_7A\r\nWhen analyzing shellcodes used in Equation Editor exploits i prefer to look for a bytes indicating jumps or calls\r\ninstead of parsing structure of a file, this yields quicker results and allows to throw out garbage bytes like here at\r\nthe begin of a file. Anyhow here we have a typical jump-into-instruction and short xoring loop. Nothing that can’t\r\nbe solved with a bit of scripting. To make it more fun, will make it a generic\r\ndef decode_first_stage():\r\n pat = \"C3 5F 83 C7 ? 33 C9 66 B9 ? ? 66 83 3F ? 74 ?? 66 81 37 ? ? 47 47 E2 ?\"\r\n off = FindBinary(idaapi.get_imagebase(), idaapi.SEARCH_DOWN,pat)\r\n size = ida_bytes.get_word(off+9)\r\n key = ida_bytes.get_word(off + 20)\r\n ea = off + ida_bytes.get_byte(off + 4)\r\n for i in range(size):\r\n if ida_bytes.get_word(ea):\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 2 of 13\n\npatch_word(ea,ida_bytes.get_word(ea) ^ key)\r\n ea += 2\r\nThis will take care of our problem. It is also worth noticing that this stub is pretty uncommon due to comparison\r\nto zero at 7Ah which makes it pretty good candidate for YARA signature! something like that should do the trick.\r\nrule royal_road_dec_loop : RoyalRoad\r\n{\r\n meta:\r\n author = \"mak\"\r\n hash = \"1527f7b9bdea7752f72ffcd8b0a97e9f05092fed2cb9909a463e5775e12bd2d6\"\r\n hash = \"3e216e2b0320201082b81ebc3a35b65a242ff260f2d7f8e441970ac5262b3a71\"\r\n description = \"matches a shellcode stub in binary and rtf document\"\r\n \r\n /*\r\n 0x70L 5F pop edi\r\n 0x71L 83C71A add edi, 0x1a\r\n 0x74L 33C9 xor ecx, ecx\r\n 0x76L 66B9A50B mov cx, 0xba5\r\n 0x7aL 66833F00 cmp word ptr [edi], 0\r\n 0x7eL 7405 je 0x85\r\n 0x80L 668137C390 xor word ptr [edi], 0x90c3\r\n 0x85L 47 inc edi\r\n 0x86L 47 inc edi\r\n 0x87L E2F1 loop 0x7a\r\n */\r\n strings:\r\n $chunk_1 = { 5F 83 C7 ?? 33 C9 66 B9 ?? ?? 66 83 3F ?? 74 ?? 66 81 37 ?? ?? 47 47 E2 }\r\n $enc_chunk = { 35 46 38 33 43 37 ?? ?? 33 33 43 39 36 36 42 39 ?? ?? ?? ?? 36 36 38 33 33 46 ?? ?? 37 34 ??\r\n $enc_chunk1 = { 35 66 38 33 63 37 ?? ?? 33 33 63 39 36 36 62 39 ?? ?? ?? ?? 36 36 38 33 33 66 ?? ?? 37 34 ??\r\n $rtf = \"{\\\\rt\"\r\n condition:\r\n ($rtf and 1 of ($enc*)) or $chunk_1\r\n \r\n}\r\nNote that we have hex encoded version for upper and lower cases. But enough of this digression, lets get back to\r\nour shellcode.\r\nShellcode\r\nAt first glance, decompilation of main shellcode function doesn’t look very nice, but we can make it work. If one\r\nscroll down a little it’s actually pretty easy to find a decoding loop and make an educated guess that this will\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 3 of 13\n\ndecode a payload, but this time will do it step by step.\r\nFirst when dealing with shellcodes (or generally with most malwares) its important to find how it utilize API calls.\r\nIn most cases there will be a search for library addresses by walking a PEB and later parsing PE files and applying\r\nsome sort of hashing in order to resolve imports. Same here. At offset b67h there is a function that will resolve\r\nall necessary imports.\r\n v0 = (int *)(sub_79E() + 13247445);\r\n v0[2] = sub_6BF();\r\n v1 = sub_669();\r\n v2 = v0[27];\r\n v3 = v0[2];\r\n *v0 = v1;\r\n v4 = ((int (__fastcall *)(int, int))sub_70A)(v3, v2);\r\n v5 = v0[98];\r\n v6 = v0[2];\r\n v0[27] = v4;\r\n v0[98] = ((int (__fastcall *)(int, int))sub_70A)(v6, v5);\r\nAt top of this function we can see a call to a function and some strange number being added to a returned value,\r\nthis is typical way of obfuscating address of a global structure that will hold pointers to resolved APIs - sometimes\r\nit also hold hashes of APIs that need to be resolved, exactly like in this case - which we can see immediately when\r\nfollowing memory operations. After looking at sub_70A we found out that hashing algorithm is ror7\r\ndef ror7_hash(name):\r\n x = 0\r\n for c in name:\r\n x = ror(x, 7) \u0026 0xffffffff\r\n x += ord(c)\r\n x \u0026= 0xffffffff\r\n return x\r\nAfter this quick analysis we know that:\r\nhashing function is ror7\r\nlist of hash values is located at -13242239 + 13247445 = 0x1456\r\nLets create a script that will create CTX objects for us. First we need a list of API names, here mlib has you\r\ncovered as it exposes couple thousands of standard APIs one can found in windows dll’s. Rest is just a matter of\r\ncreating and naming a structure members. Following script accomplishes that.\r\nfrom mlib.winapi import make_hash_dict\r\nfrom mlib.hash import ror7_hash\r\ndef create_ctx(addr):\r\n api_dict = make_hash_dict(ror7_hash)\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 4 of 13\n\nst_id = ida_struct.add_struc(idaapi.BADADDR,'ctx')\r\n st = ida_struct.get_struc(st_id)\r\n h = 0\r\n while h != idaapi.BADADDR:\r\n h = ida_bytes.get_dword(addr)\r\n name = api_dict.get(h,None)\r\n if name:\r\n name = 'api_' + name\r\n addr +=4\r\n ida_struct.add_struc_member(st, name, idaapi.BADADDR, idaapi.FF_DWORD, None, 4)\r\nAnd soon after applying this and some more types we can get a nice clear view of a main function\r\nctx *main_function()\r\n{\r\n int v0; // edi\r\n ctx *v1; // esi\r\n char *v2; // edx\r\n char *i; // ecx\r\n int v4; // ecx\r\n char v5; // al\r\n unsigned int v6; // ebx\r\n unsigned int v7; // ebx\r\n int v8; // edx\r\n int v9; // edi\r\n unsigned int v10; // ebx\r\n unsigned int v11; // eax\r\n int v13; // [esp-44h] [ebp-68h]\r\n int v14; // [esp+10h] [ebp-14h]\r\n _BYTE *pefile; // [esp+14h] [ebp-10h]\r\n char v16; // [esp+18h] [ebp-Ch]\r\n int v17; // [esp+1Ch] [ebp-8h]\r\n int file_size; // [esp+20h] [ebp-4h]\r\n v0 = 4;\r\n v17 = 0;\r\n v1 = (ctx *)(get_ctx_offset() + 13247445);\r\n resolve_api();\r\n v1-\u003ecall_func(v1-\u003eapi_GetTempPathA, 2, 260, (unsigned int)v1-\u003etemp_path, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\r\n v2 = v1-\u003efile_name;\r\n for ( i = v1-\u003etemp_path; *i; ++i )\r\n ;\r\n v4 = i - v2;\r\n do\r\n {\r\n v5 = *v2;\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 5 of 13\n\nv2[v4] = *v2;\r\n ++v2;\r\n }\r\n while ( v5 );\r\n v14 = v1-\u003ecall_func(v1-\u003eapi_CreateFileA, 7, (int)v1-\u003etemp_path, 0x80000000, 0, 0, 3, 128, 0, 0, 0, 0, 0, 0, 0,\r\n v6 = v1-\u003ecall_func(v1-\u003eapi_GetFileSize, 2, v14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n v13 = v1-\u003eapi_VirtualAlloc;\r\n file_size = v6;\r\n pefile = (_BYTE *)v1-\u003ecall_func(v13, 4, 0, v6, 12288, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n v1-\u003ecall_func(v1-\u003eapi_ReadFile, 5, v14, (unsigned int)pefile, v6, \u0026v16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n v7 = 2029427297;\r\n v8 = 0;\r\n if ( file_size \u003e 0 )\r\n {\r\n do\r\n {\r\n v9 = 7;\r\n do\r\n {\r\n v7 = v7 \u0026 4 ^ ((v7 ^ (v7 \u003e\u003e 27)) \u003e\u003e 3) \u0026 1 | (2 * v7);\r\n --v9;\r\n }\r\n while ( v9 );\r\n pefile[v8++] ^= v7;\r\n }\r\n while ( v8 \u003c file_size );\r\n v0 = 4;\r\n }\r\n while ( 1 )\r\n {\r\n v10 = v1-\u003ecall_func(v1-\u003eapi_GetFileSize, 2, v0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n if ( v10 \u003e= 0x1000 )\r\n {\r\n v1-\u003ecall_func(v1-\u003eapi_ReadFile, 5, v0, (unsigned int)\u0026v17, 4, \u0026v16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n if ( v17 == -535703600 )\r\n break;\r\n }\r\n v0 += 4;\r\n }\r\n v11 = v1-\u003ecall_func(v1-\u003eapi_VirtualAlloc, 4, 0, v10, 12288, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n v1-\u003ecall_func(v1-\u003eapi_WriteFile, 5, v0, v11, v10, \u0026v16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n v1-\u003ecall_func(v1-\u003eapi_CloseHandle, 1, v0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n load_pe(pefile, file_size);\r\n v1-\u003ecall_func(v1-\u003eapi_CloseHandle, 1, v14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\n v1-\u003ecall_func(v1-\u003eapi_TerminateProcess, 2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 6 of 13\n\nreturn v1;\r\nThis will be enough to continue analysis of a campaign, as we can now switch to decoding payload and analyze\r\nwhats there, however its sometimes worth to dig a little deeper into and uncover some interesting flavors that are\r\ninside. We will do exactly that.\r\ncall_func - down to rabbit hole\r\nWe skipped over analysis of how exactly api is resolved, lets apply some types first and take another look. First\r\nthing that will strike us is that it walks IMAGE_DATA_DIRECTORY instead of EXPORTS , indeed get_api function\r\n(located at 70Ah ) will recive as a frist argument pointer to begin of msvcrt.dll and will relay on addresses\r\nresolved when this librarly was loaded. This is a very nice anti-analysis trick, as this library is not automatically\r\nloaded, and can cause some problems when analyzing shellcode outside of it designed host. After address is\r\nresolved 5 is being added to it, this is strange! Keep it in mind and lets move to function located at 7B7h . This\r\nfunction will make sure that we have GetProcAddr and VirtualProtect as well will try to resolve address of\r\nclearerr from msvcrt.dll (via GetProcAddr ). In order to call any of those APIs we have to go through a\r\nwrapper at 8AEh and eventually we will reach function at 8D5h that will call our pointer, but before that it will\r\ndo some checks.\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 7 of 13\n\nHere lays the mystery of those +5 from get_api , before API is called first few bytes of its prologue are\r\nchecked. Any experienced eye can immediately recognize right hand values as an encoding of call or jmp asm\r\nopcodes. This is anti-hooking mechanism! If it detects hooking it will skip first 5 bytes emulating typical function\r\nprologue, thus rendering most of user-mode api loggers useless.\r\nAfter address clearerr is resolved, it content is overwritten with bytes from 8AEh to 8FDh which contains\r\nfunctions responsible for anti-hooking call proxy. There is another nice trick here, as the address of interesting\r\narea is dynamically calculated\r\nseg000:000008A4 call override_clearerr\r\nseg000:000008A9 call $+5\r\n...\r\nseg000:000008F4 pop ebx\r\nseg000:000008F5 mov eax, [ebx+1]\r\nseg000:000008F8 add ebx, eax\r\nseg000:000008FA add ebx, 5\r\nseg000:000008FD mov [ebp-18h], ebx\r\nseg000:00000900 push 50h ; 'P' ; a3\r\nseg000:00000902 mov edx, [ebp-18h] ; a2\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 8 of 13\n\nseg000:00000905 mov ecx, [ebp-8] ; a1\r\nseg000:00000908 call copy\r\nHere, beside call-pop technique, we can see that address that will be called from 8A9h is extracted allowing to\r\nput interesting region anywhere in binary. While that deep analysis is not necessary to obtain final payload, it adds\r\nsome information about anyone who coded this shellcode as those tricks are quite sophisticated and uncommon,\r\nOne can try to go through past and current samples of Royal Road and check how this shellcode evolves and if its\r\nmaybe typical to specific threat actor or not.\r\nOptimizing decoding\r\nAlong side their publication researchers at nao_sec released a tool that can help decode embedded payload into\r\nproper PE file. They realized that keys used to bootstrap decoding algorithm in shellcode are reused and first 4\r\nbytes of encoded binary will always be the same. They found 4 different algorithms and 4 diffrent keys, all of the\r\nalgorithms look more or less the same and have a shape of the one discussed earlier. It is important to notice that\r\nin their code two decoding functions ( decode_b25a6f00 and decode_b2a66dff ) are totally different than what\r\none can see in binaries, those are simple xors with one byte. Lets take a look at decode_b2a66dff :\r\n int v0, v9;\r\n int v7 = 2029427297;\r\n int v8 = 0;\r\n do\r\n {\r\n v9 = 7;\r\n do\r\n {\r\n v7 = v7 \u0026 4 ^ ((v7 ^ (v7 \u003e\u003e 27)) \u003e\u003e 3) \u0026 1 | (2 * v7);\r\n --v9;\r\n }\r\n while ( v9 );\r\n v8++;\r\n printf(\"%x\\n\", v7);\r\n }\r\n while ( v8 \u003c v18 );\r\n v0 = 4;\r\nThis function will very quickly reach 0xfffffffc and is degenerating whole encoding scheme to just a xor with\r\nconstant byte 0xfc. Lets split the line into simpler components\r\nint x = ((v7 ^ (v7 \u003e\u003e 27)) \u003e\u003e 3) \u0026 1;\r\nint z = v7\u00264;\r\nv7 = z ^ x | (2*v7);\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 9 of 13\n\nProblem lies in v7\u00264 which will keep its bit light up forever after the first time it will be set to 1. This is because\r\nit will be XORed with 1 bit variable and ORed with a value multiplied by 2. Next the bit will spread around the\r\nrest of the bits with every step until all the bits on the left will be set to 1 (thanks to @dsredford for clarification)\r\nThis is clearly a mistake caused probably by a typo or lack understanding of operators priority, what author\r\nwanted to accomplish was something like this (based on later modifications):\r\nint x = ((v7 ^ (v7 \u003e\u003e 27)) \u003e\u003e 3)\r\nint z = v7\u00264;\r\nv7 = (z ^ x)\u00261 | (2*v7);\r\nUnfortunately these mistakes are not present in every version, hence more elaborate codes in rr_decode.py . This\r\nscript can of course take care of other schemas but it take some time when dealing with bigger binaries:\r\n$ python3 rr_decode.py ./df684cc86f19d5843f07dfd3603603723bb6491a29a88de7c3d70686df8635cc.bin_8.t xx.bin\r\n[!] Type [b0747746] is Detected!\r\n[+] Decoding...\r\n[!] Complete!\r\nreal 31m49.903s\r\nuser 30m30.094s\r\nsys 0m40.822s\r\n$ du -hs /tmp/xx.bin\r\n536K /tmp/xx.bin\r\nThis is a problem for our automatic processing. Maybe we can do something about it? Lets take a look at this one,\r\n v9 = 0x48B53A6C;\r\n v10 = 0;\r\n if ( v8 \u003e 0 )\r\n {\r\n v11 = v8;\r\n do\r\n {\r\n v12 = 7;\r\n do\r\n {\r\n v9 = ((v9 ^ ((v9 ^ (v9 \u003e\u003e 26)) \u003e\u003e 3)) \u0026 1 | (2 * v9)) + 1;\r\n --v12;\r\n }\r\n while ( v12 );\r\n *(_BYTE *)(v10++ + v22) ^= v9;\r\n }\r\n while ( v10 \u003c v11 );\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 10 of 13\n\ncan we somehow optimize expression assigned to v9 ? It turns out we can! ;]\r\nLets expand it a little bit this part ((9 ^ ((v9 ^ (v9 \u003e\u003e 26)) \u003e\u003e 3))\u00261 using the fact that (x ^ y ) \u003e\u003e z is the\r\nsame as (x \u003e\u003e z) ^ (x \u003e\u003e z) . We’ll end up with (v9 ^ ((v9\u003e\u003e27)\u003e\u003e3) ^ (v9\u003e\u003e3))\u00261 . We can see here that\r\nthe whole expression is being anded with 1, meaning we only care about one bit of this operation. This bit will be\r\na result of xoring 3 bits from a number, more precisely we will xor following bits\r\n0th\r\n3th\r\n29th\r\nextracting those bits is easy, for example to get a 29th bit one can do:\r\nWe tested many variants of how to efficiently write it down, but it turns out that doing just that yields best results!.\r\nWe ended up with a following code:\r\ndef decode_b0747746(data):\r\n xor_key = 1219836524\r\n for i in xrange(len(data)):\r\n for _ in range(7):\r\n x0 = (xor_key \u0026 0x20000000) == 0x20000000\r\n x1 = (xor_key \u0026 8) == 8\r\n x2 = xor_key \u0026 1\r\n x = 1 + (x0 ^ x1 ^ x2)\r\n xor_key = (xor_key + xor_key) + x\r\n data[i] ^= xor_key \u0026 0xff\r\n return data\r\nand its way faster\r\n$ time ripper df684cc86f19d5843f07dfd3603603723bb6491a29a88de7c3d70686df8635cc.bin\r\nPotential malware family dected: ['royal_road']\r\nmalware data:\r\n{'object_name': None,\r\n 'payload': '23d263b6f55ac81f64c3c3cf628dd169d745e0f2b264581305f2f46efc879587',\r\n 'payload_enc_type': 'b0747746',\r\n 'payload_key': 1219836524,\r\n 'shellcode_key': 50064,\r\n 'target_path': '%TEMP%\\\\..\\\\..\\\\Roaming\\\\Microsoft\\\\Word\\\\STARTUP\\\\intel.wll',\r\n 'type': 'royal_road_rtf'}\r\nreal 6m11.555s\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 11 of 13\n\nuser 6m3.671s\r\nsys 0m0.162s\r\nSimilar operation can be done for 4th encoding scheme, but we will leave this as an exercise for a reader.\r\nConclusion\r\nEven with a plethora of tools and analysis made available by other researchers its beneficial to sometimes take a\r\ndeeper look into a subject. This can potentially uncover new ways of dealing with problems, revels new clues or\r\npatterns typical to threat actor, or simply find a nice new trick. This time we saw most of those came to life. In\r\naddition to deep analysis of Royal Road shellcode we showed some generic methods of how to approach and/or\r\nspeed up future analysis of any type of shellcode by utilizing power of scripting your disassembler and\r\nconcentrating on most important parts. More of those tips an tricks can be found in our ExtREme Malware\r\nAnalysis training.\r\nIt seems that Royal Road is going to stay with us for some time, despite all of the public research and that it\r\nexploits more than year old vulnerabilities. That being said we are looking forward to see if developers will\r\nimplement any new trick or will change their approach in anyway. Research published at VB2019 shows that\r\nRoyal Road is no longer in use only by APT groups but also by typical cyber-crime actors and we think its safe to\r\nassume that this weapnonizer was somehow leaked as we are seeing very strange testing payloads that almost\r\nreassemble red team exercises1 which suggests that its somehow available to any parties2.\r\nAnalysis Artifacts - Hashes, domains, urls, etc\r\nHASHES: 2aff4cbb4b1ba8a62e45b74944362d757ebfdb960867db5e7dbca7a6beab69e1\r\n723bdb101d5d046a470618ff3c90dcad9018530cf02248f2c30f3a95e8eb9f8a\r\naaa9ad2f93c15204053516500c73b86bdbcc14956f5f63cb208528c68fad8755\r\n657c45152a924cbf8542faff7fc10aa264bb9d4b55f79bf992569704b392610b\r\n52aa0924797e3600d9a2d2f9f55526358aba19bcc25b5d22c98ce05d2b6cfc25\r\n44657dcf6286836be5898a464165331ebaa9d7d57762f88f8255ab9499751338\r\n5bbf2643a601e632a49406483c8fc5262a76e206bd969f2ba3f4f2e238768ab9\r\nda23586cf0efffab039358e2b4410ea0a6b6eb4a9d7430a0d46ca1235a402027\r\n3f12dbce11f6faf0b27a4046e3c341b672228764eb5c3f98cee709980f2ec955\r\n4ac1e100cf5d46dca4cca9e051d744ff1406630904f836d95ee3c172a9d2aca5\r\n440ab62ec3520c378c61ee2def3da3ec32f553ab3ddd6eccda0cb0a70d9e8523\r\nd7f15f750cceeb9e28e412f278949f183f98aeb65fe99731b2340c8f1c008465\r\n100ded4eeffbd9c927cccd7850a3e83a2fb7b127e40e03f1570bbf6939cbb5fe\r\n98f06ddae144a0f22aac6898caa3469b965b1b02b90c1d54600e7e461a1cbdf7\r\n72cdfc4b25c6c0253a4cf1449d2a67343ee87c32176425bac5a7cbdd30007ec3\r\n1527f7b9bdea7752f72ffcd8b0a97e9f05092fed2cb9909a463e5775e12bd2d6\r\nc83c28add56ec8cad23a14155d5d3d082a1166c64ea5b7432e0acaa728231165\r\n500b6037ddb5efff0dd91f75b22ccce5b04d996c459d83d1f07fae8780b24e09\r\nb7bebe92a5802aa922e5719c948e35716f908e67701cfffaeebfcadc7a6e650a\r\n0eb7ba6457367f8f5f917f37ebbf1e7ccf0e971557dbe5d7547e49d129ac0e98\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 12 of 13\n\n855a060c43a83aa42faa63bfe4b08f31b4ba11cd64ea4cad69ad50910730f02f\r\n9d99badebbfc6616d9a74dbfced6b7db9097d274366a232025469980f9a229a0\r\ndf684cc86f19d5843f07dfd3603603723bb6491a29a88de7c3d70686df8635cc\r\n1. fb38bea02499d8cec47c88333234b033849307d6ad4d4442e6b6fd6837664d3b and\r\nac61b5fa62ea33717bdc80178f1083c49cfd34204b56556c805b8edb2265e534 ↩︎\r\n2. It’s also possible that someone went an extra mile and create a weaponizer that will appear as Royal Road\r\nbased on publicly available YARA signatures ↩︎\r\nSource: https://blog.malwarelab.pl/posts/on_the_royal_road/\r\nhttps://blog.malwarelab.pl/posts/on_the_royal_road/\r\nPage 13 of 13\n\nseg000:000008FA seg000:000008FD add ebx, mov [ebp-18h], 5 ebx\nseg000:00000900 push 50h ; 'P' ; a3\nseg000:00000902 mov edx, [ebp-18h] ; a2\n  Page 8 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blog.malwarelab.pl/posts/on_the_royal_road/"
	],
	"report_names": [
		"on_the_royal_road"
	],
	"threat_actors": [
		{
			"id": "b740943a-da51-4133-855b-df29822531ea",
			"created_at": "2022-10-25T15:50:23.604126Z",
			"updated_at": "2026-04-10T02:00:05.259593Z",
			"deleted_at": null,
			"main_name": "Equation",
			"aliases": [
				"Equation"
			],
			"source_name": "MITRE:Equation",
			"tools": null,
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434612,
	"ts_updated_at": 1775791370,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/db4237de5b592f87d4c8775722bdc5589dce6950.pdf",
		"text": "https://archive.orkl.eu/db4237de5b592f87d4c8775722bdc5589dce6950.txt",
		"img": "https://archive.orkl.eu/db4237de5b592f87d4c8775722bdc5589dce6950.jpg"
	}
}