{
	"id": "8d47d102-a680-47fe-bf81-9171e0a8f39b",
	"created_at": "2026-04-06T00:17:27.440859Z",
	"updated_at": "2026-04-10T13:11:32.030053Z",
	"deleted_at": null,
	"sha1_hash": "46ec2b6d605ae457ee032a9f9f3d903e94a6cdde",
	"title": "Ghidra script to decrypt strings in Amadey 1.09 – Max Kersten",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 153347,
	"plain_text": "Ghidra script to decrypt strings in Amadey 1.09 – Max Kersten\r\nArchived: 2026-04-05 15:33:06 UTC\r\nThis article was published on the 9th of February 2021. This article was updated on the 8th of December 2021.\r\nOn the 21st of March 2019, the American National Security Agency (NSA) released Ghidra: a free and open-source reverse engineering tool. The tool can disassemble and decompile code for a variety of architectures.\r\nAdditionally, users can create scripts in Python and Java using the exposed API. This article will cover the the\r\nstring encryption in Amadey 1.09, and will provide a step-by-step guide to create an automatic string decryption\r\nscript in Java.\r\nTable of contents\r\nThe sample\r\nScripting basics\r\nOutline\r\nFinding the decryption routine\r\nRemaking the decryption routine\r\nGetting user-input\r\nGetting all cross references for the decryption function\r\nIterating all decryption calls\r\nCaching the decrypted results\r\nAdding comments and bookmarks\r\nPutting it all together\r\nConclusion\r\nThe complete script\r\nThe sample\r\nThis sample is taken from KrabsOnSecurity‘s blog from Feburary 2019. The specific sample is the unpacked\r\nstage, as is described in the blog. The sample can be downloaded from VirusBay, Malware Bazaar, or MalShare.\r\nThe hashes are given below.\r\nMD5: dbaaa2699c639f652117e9176fd27fdf\r\nSHA-1: 3e4cd703deef2cfd1726095987766e2f062e9c57\r\nSHA-256: 654b53b4ef5b98b574f7478ad11192275178ca651d9e8496070651cd6f72656a\r\nSize: 51396 bytes\r\nAdditionally, snippets from this blog by Lars A. Wallenborn and Jesko H. Hüttenhain about a Ghidra script to\r\nautomatically decrypt strings in REvil samples are used. Within the code, credit to Lars’ and Jesko’s work and\r\nwebsites is given whenever it is used.\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 1 of 22\n\nScripting basics\r\nScripting in Ghidra can be done using Python and Java. Python scripts are executed with the help of Jython. As\r\nsuch, the native way of scripting in Ghidra, is with Java. Therefore, this is also what will be used in this article.\r\nIn the official repository’s DevGuide, guidance is given with regards to configuring Ghidra with Eclipse. This is\r\nuseful to develop Java based scripts in a proper IDE, with the option to debug the script.\r\nOnce the environment is set-up, one will see that every script extends the GhidraScript class. This class forces the\r\nscript to implement the run method, which is the starting point of the script’s code. Due to the inheritance of the\r\nGhidraScript class, the user has access to the FlatProgramAPI. Functions might be added to this class, but never\r\nremoved. Or, as the NSA explains in the accompanied JavaDoc:\r\nThis class is a flattened version of the Program API.\r\nNOTE:\r\n 1. NO METHODS SHOULD EVER BE REMOVED FROM THIS CLASS.\r\n 2. NO METHOD SIGNATURES SHOULD EVER BE CHANGED IN THIS CLASS.\r\nThis class is used by GhidraScript.\r\nChanging this class will break user scripts.\r\nThat is bad. Don't do that.\r\nUsing Eclipse’s built-in auto-completion and JavaDoc viewer, its easy to view all functions that are directly\r\naccessible from the GhidraScript class. Alternatively, or additionally, one can read the publicly available JavaDoc\r\non the NSA’s website.\r\nAs is specified in the GhidraScript class, there are several variables that one can access within a script, without the\r\nneed to initialise them. An excerpt of the Ghidra documentation is given below.\r\nAll scripts, when run, will be handed the current state in the form of class instance variable. These\r\ncurrentProgram: the active program\r\ncurrentAddress: the address of the current cursor location in the tool\r\ncurrentLocation: the program location of the current cursor location in the tool, or null if no prog\r\ncurrentSelection: the current selection in the tool, or null if no selection exists\r\ncurrentHighlight: the current highlight in the tool, or null if no highlight exists\r\nKnowing the basics of scripting before starting to write code will result in a more efficient use of your time, as\r\nwell as cleaner code.\r\nOutline\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 2 of 22\n\nThe goal of the script is to automatically decrypt the encrypted strings that are present within the binary. As such,\r\nit is essential to be able to decrypt the strings. For this reason, it is the first step in this article. After that, it is\r\nimportant to get the required information from the user. What that is precisely, will follow logically from the\r\ndecryption routine.\r\nKnowing how to decrypt the strings is only part of the job, as one will also need to find all references to the\r\ndecryption function call and its argument(s). Knowing the arguments for each call, and the decryption routine\r\nitself, will allow the script to decrypt all strings. Knowing how to add comments to the disassembly and\r\ndecompiler view will show the result in an organised fashion to the analyst.\r\nBefore putting all the pieces are put together, a method to cache decrypted strings will be introduced. This will\r\nreduce the time the script needs to run if the same string is encountered multiple times.\r\nIn this article, a Ghidra build from the 21st of December 2020 (which has a few more commits than Ghidra 9.2.1)\r\nis used. Due to the usage of the FlatProgramAPI, it should run on future versions, and is likely to run on older\r\nversions.\r\nFinding the decryption routine\r\nAfter loading this sample in Ghidra and running the default analysers, it becomes apparent that the sample’s\r\nsymbols were not stripped during the compilation. As such, it becomes easy to find the main function, as is given\r\nbelow. Note that the function is called _main.\r\nint __cdecl _main(int _Argc,char **_Argv,char **_Env)\r\n{\r\n char *pcVar1;\r\n size_t in_stack_fffffff0;\r\n __alloca(in_stack_fffffff0);\r\n ___main();\r\n __Z10aBypassUACv();\r\n pcVar1 = __Z12aGetSelfPathv();\r\n __Z13aDropToSystemPc(pcVar1);\r\n pcVar1 = __Z19aGetSelfDestinationi(0);\r\n __Z11aAutoRunSetPc(pcVar1);\r\n __Z6aBasici(0);\r\n return 0;\r\n}\r\nThe mangled names provide insight into what the functions do. These symbols can be misleading, and should not\r\nalways be trusted as-is, but the symbols in this sample are representative for the functionality that is within the\r\nfunctions. The ___main function, note the triple underscore, does not contain code that was made by the author.\r\nSearching for encrypted strings can be done in a variety of ways. One can use the Defined Strings view (as found\r\nin the Window toolstrip menu), browse through the function tree to look for a name that is likely to handle\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 3 of 22\n\nencrypted strings (which is __Z8aDecryptPc), or click through the functions until one encounters a function call\r\nthat seems to decrypt a string (as can be seen in __Z6aBasici).\r\nThe decryption routine is given below.\r\nundefined * __cdecl __Z8aDecryptPc(char *param_1)\r\n{\r\n size_t sVar1;\r\n uint local_10;\r\n _memset(\u0026_ZZ8aDecryptPcE14aDecryptResult,0,0x400);\r\n local_10 = 0;\r\n while( true ) {\r\n sVar1 = _strlen(param_1);\r\n if (sVar1 \u003c= local_10) break;\r\n sVar1 = _strlen(s_1ee76e11929a07445c5abd744aa407db_00405000);\r\n (\u0026_ZZ8aDecryptPcE14aDecryptResult)[local_10] =\r\n param_1[local_10] - s_1ee76e11929a07445c5abd744aa407db_00405000[local_10 % sVar1];\r\n local_10 = local_10 + 1;\r\n }\r\n return \u0026_ZZ8aDecryptPcE14aDecryptResult;\r\n}\r\nAt first, two variables are declared, after which the memory buffer for the output is set. The variable local_10 is\r\nincremented with one at the end of every iteration in the while loop. Only when the length of the input is equal or\r\nbigger than the amount of iterations that have taken place, the endless loop breaks. At last, the result is returned.\r\nThe decryption itself is based on the used key, together with the iterative value. Refactoring the method with more\r\nreadable names, the function becomes easily readable.\r\nundefined * __cdecl __Z8aDecryptPc(char *input)\r\n{\r\n size_t inputLength;\r\n uint i;\r\n _memset(\u0026result,0,0x400);\r\n i = 0;\r\n while( true ) {\r\n inputLength = _strlen(input);\r\n if (inputLength \u003c= i) break;\r\n inputLength = _strlen(key);\r\n (\u0026result)[i] = input[i] - key[i % inputLength];\r\n i = i + 1;\r\n }\r\n return \u0026result;\r\n}\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 4 of 22\n\nIn conclusion, this function requires one argument, which is decrypted using a hardcoded key, after which the\r\ndecrypted value is returned.\r\nRemaking the decryption routine\r\nAs the Ghidra script is to be written in Java, the decryption routine is also to be written in Java. The ported\r\nfunction will take two arguments, rather than one. The first one is the input, which is the string to decrypt. As\r\nmemory is read from the sample, the type of this variable is a byte array. The second argument is the decryption\r\nkey, which is represented as a string. The ported function is more readable than the decompiled code, as can be\r\nseen below.\r\nprivate String decrypt(byte[] input, String key) {\r\nchar[] keyArray = key.toCharArray();\r\nint keyLength = keyArray.length;\r\nbyte[] output = new byte[input.length];\r\nfor (int i = 0; i \u003c input.length; i++) {\r\noutput[i] = (byte) (input[i] - keyArray[i % keyLength]);\r\n}\r\nreturn new String(output);\r\n}\r\nGetting user-input\r\nGetting information from the user is a useful way to make a script more generic. If a specific malware family uses\r\nthe same decryption routine with a different key per sample, one can request the key from the user, without the\r\nneed to alter the decryption script.\r\nIn Ghidra, one can request values from the user using the ask* functions, where the asterisk should be read as a\r\nwildcard, as there are many functions present to help. Aside from the added convenience of not having to write\r\nsuch a function, it is important to note that a user cannot provide an empty string to this dialog. The requested\r\nstring cannot be null either, as closing the dialog will lead to the termination of the script, which is clearly shown\r\nto the user in Ghidra’s console.\r\nTo print data to the console, one can use the built-in println and print functions. The difference is that the latter\r\ndoes not print the script name into the console.\r\nIn this case, the askString function is used to request a string from the user. Within the script, two values will be\r\nrequired: the name of the decryption function and the key that is used to decrypt the encrypted input.\r\nGetting all cross references for the decryption function\r\nThe decryption function’s name is obtained earlier on in the script, as the user provides the name. Based on that,\r\none can get a list of functions that use this name. As symbol names do not have to be unique in Ghidra, it is\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 5 of 22\n\npossible that there are more functions with the same name. The code to obtain such a list is given below.\r\nList\u003cFunction\u003e functions = getGlobalFunctions(functionName);\r\nBasic sanity checks to see if there are more functions with the given name can be implemented. The\r\nReferenceIterator class is present in the currentProgram variable, which is already initialised. Using the\r\ngetReferencesTo function, one can use an Address object to get all references to that address. To convert a raw\r\naddress, which is represented as a Long, to an Address object, one can use the toAddr function. This function is\r\naccessible via the extended GhidraScript class.\r\nReferenceIterator references = currentProgram.getReferenceManager().getReferencesTo(toAddr(decryption\r\nTo iterate over all references, one can use a simple for-loop, as is shown below.\r\nfor (Reference reference : references) {\r\n Address address = reference.getFromAddress();\r\n //...\r\n}\r\nThis for-loop is the basis for the following steps, as these have to be done per reference. Obtaining the address for\r\nthe reference is the first action that has to be completed.\r\nIterating all decryption calls\r\nThis part of the script is based upon two steps. The first one is being able to decrypt a given string, which is\r\npossible due to the decryption method that was written in an earlier step. The second step is to obtain the\r\nencrypted string for each function call. To do so, one can use code from the earlier mentioned blog by Lars A.\r\nWallenborn and Jesko H. Hüttenhain.\r\nThe function named getConstantCallArgument is used. This function requires two arguments, the first being an\r\nAddress object, and the second is an integer array. The Address object is the address of the function call. The\r\ninteger array is used to obtain one or more arguments of the given function’s call. The indices of the arguments in\r\nthis array correspond with the arguments for the function call, where the first index is 1, unlike the usual 0 in an\r\narray. The function is given below.\r\n//Code by Lars A. Wallenborn and Jesko H. Hüttenhain (see https://blag.nullteilerfrei.de/2020/02/02/d\r\nprivate OptionalLong[] getConstantCallArgument(Address addr, int[] argumentIndices)\r\nthrows IllegalStateException, IllegalArgumentException {\r\nint argumentPos = 0;\r\nOptionalLong argumentValues[] = new OptionalLong[argumentIndices.length];\r\nFunction caller = getFunctionBefore(addr);\r\nif (caller == null)\r\nthrow new IllegalStateException();\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 6 of 22\n\nDecompInterface decompInterface = new DecompInterface();\r\ndecompInterface.openProgram(currentProgram);\r\nDecompileResults decompileResults = decompInterface.decompileFunction(caller, 120, monitor);\r\nif (!decompileResults.decompileCompleted())\r\nthrow new IllegalStateException();\r\nHighFunction highFunction = decompileResults.getHighFunction();\r\nIterator\u003cPcodeOpAST\u003e pCodes = highFunction.getPcodeOps(addr);\r\nwhile (pCodes.hasNext()) {\r\nPcodeOpAST instruction = pCodes.next();\r\nif (instruction.getOpcode() == PcodeOp.CALL) {\r\nfor (int index : argumentIndices) {\r\nargumentValues[argumentPos] = traceVarnodeValue(instruction.getInput\r\nargumentPos++;\r\n}\r\n}\r\n}\r\nreturn argumentValues;\r\n}\r\nThis function returns an array of OptionalLong objects. Such an object can contain a Long, although it might not.\r\nIn some cases, there might occur an error whilst retrieving the address, meaning that the object itself is actually set\r\nto null. To avoid returning null, the OptionalLong object is used.\r\nWithin the function, the decompiler interface is used to get access to the PCode values. For each call, the\r\nVarnode‘s value is traced using traceVarnodeValue, which is also written by Lars A. Wallenborn and Jesko H.\r\nHüttenhain. The code is given below.\r\n//Code by Lars A. Wallenborn and Jesko H. Hüttenhain (see https://blag.nullteilerfrei.de/2020/02/02/d\r\nprivate OptionalLong traceVarnodeValue(Varnode argument) throws IllegalArgumentException {\r\nwhile (!argument.isConstant()) {\r\nPcodeOp ins = argument.getDef();\r\nif (ins == null)\r\nbreak;\r\nswitch (ins.getOpcode()) {\r\ncase PcodeOp.CAST:\r\ncase PcodeOp.COPY:\r\nargument = ins.getInput(0);\r\nbreak;\r\ncase PcodeOp.PTRSUB:\r\ncase PcodeOp.PTRADD:\r\nargument = ins.getInput(1);\r\nbreak;\r\ncase PcodeOp.INT_MULT:\r\ncase PcodeOp.MULTIEQUAL:\r\nreturn OptionalLong.empty();\r\ndefault:\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 7 of 22\n\nthrow new IllegalArgumentException(String.format(\"Unknown opcode %s for varia\r\nins.getMnemonic(), argument.getAddress().getOffset()));\r\n}\r\n}\r\nreturn OptionalLong.of(argument.getOffset());\r\n}\r\nWithin the decryption routine in Amadey 1.09, only a single argument is used. This argument points to an\r\nencrypted value of a string. As such, the index one wants to retrieve is the only equal to one, meaning an integer\r\narray with the value 1 at index 0 is required as input. The address for the function is the address of each referenced\r\nfunction call of the decryption routine. The integer array is given below.\r\nint[] argumentIndices = { 1 };\r\nOne can get the Long value from the OptionalLong object by using the getAsLong function, as can be seen in the\r\ncode below.\r\nLong argument = arguments[0].getAsLong();\r\nRight now, all information to decrypt a string has been obtained, as the address of the encrypted string, the\r\ndecryption key, and the decryption routine have been collected. The getDecryptedArgument function contains all\r\ncode to decrypt an argument, which is then returned as a string. It requires an address as a Long, and the\r\ndecryption string as a String. The code for the function is given below.\r\nprivate String getDecryptedArgument(Long argument, String key) throws MemoryAccessException {\r\nMemoryBlock block = getMemoryBlock(toAddr(argument));\r\nint size = ((Long) block.getSize()).intValue();\r\nbyte[] input = getBytes(toAddr(argument), size);\r\nString decryptedValue = decrypt(input, key);\r\ndecryptedValue = decryptedValue.replace(\"\\n\", \"\\\\n\").replace(\"\\r\", \"\\\\r\");\r\nreturn getFirstReadableString(decryptedValue);\r\n}\r\nAt first, a memory block is read, based on the address of the given argument. The getMemoryBlock function is\r\naccessible via the GhidraScript class. The size of the block is stored in a different variable to increase the\r\nreadability of the code. The getBytes function, also accessible via the GhidraScript class, gets the bytes from the\r\ngiven argument’s location until the location plus the given size.\r\nThe decryptedValue string contains the decrypted string. If any newline and carriage return values are present in\r\nthat string, they are escaped using the chained replace function calls. The input that was provided to the\r\ndecryption function is much bigger than the actual string. As such, the first human readable string has to be\r\nrecovered, which is done using the getFirstReadableString method.\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 8 of 22\n\nThis method, as can be seen below, requires a string as input, and will return the first human readable string.\r\nprivate String getFirstReadableString(String input) {\r\nint beginIndex = -1;\r\nint endIndex = -1;\r\nint asciiLow = 0;\r\nint asciiHigh = 255;\r\nfor (int i = 0; i \u003c input.toCharArray().length; i++) {\r\nchar currentChar = input.charAt(i);\r\nif (currentChar \u003c asciiHigh || currentChar \u003e asciiLow) {\r\nbeginIndex = i;\r\nbreak;\r\n}\r\n}\r\nfor (int i = 0; i \u003c input.toCharArray().length; i++) {\r\nchar currentChar = input.charAt(i);\r\nif (currentChar \u003e asciiHigh || currentChar \u003c asciiLow) {\r\nendIndex = i;\r\nbreak;\r\n}\r\n}\r\nif (beginIndex \u003e= 0 \u0026\u0026 endIndex \u003e= 0) {\r\nreturn input.substring(beginIndex, endIndex);\r\n}\r\nreturn \"NO_ASCII_STRING_FOUND\";\r\n}\r\nThis function declares four local integers, named beginIndex, endIndex, asciiLow, and asciiHigh. The first two are\r\nset to -1, whereas the last two are set to 0 and 255 respectively. The beginIndex and endIndex are used to store the\r\nbeginning and ending indices of the first human readable string, whereas the latter two variables are hardcoded to\r\ndefine the beginning and ending of the human readable range of ASCII characters. Wide strings are out of scope\r\nfor this function, as they are not used within this sample.\r\nThe given string is iterated over twice: once for the first character, and once for the last character. Whilst this can\r\nbe done in a single loop, this would decrease the code’s readability. As this function is rather rudimentary at best,\r\nthe code’s optimisation has not been included in this article.\r\nThe return value of this function is a substring of the provided input. If none of the characters are readable, then a\r\ndefault value is returned. This default value is never returned in the used sample.\r\nCaching the decrypted results\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 9 of 22\n\nEven though some optimisation steps were left out in the previous step, this step is purely meant as an\r\noptimisation. Caching results is a useful way to easily decrease the time that the script takes, without adding a\r\nneedless complex layer of logic into the code.\r\nAt first, a mapping is created. Mappings are often known as dictionaries in other languages. In this case, the\r\nmapping will use addresses (as a Long) as a key, where the value at a given key is a String. The mapping’s keys\r\nare the locations of the encrypted variables, whereas the mapping’s values are decrypted strings. The creation of\r\nthe mapping is given below.\r\nMap\u003cLong, String\u003e handled = new HashMap\u003c\u003e();\r\nWhen iterating over the argument locations, the following if-statement is required to implement the caching of\r\ndecrypted values.\r\nif (handled.containsKey(argument)) {\r\n //...\r\n} else {\r\n //...\r\n}\r\nAs such, it checks if the given address already present in the mapping. If it is, the value for the given key should\r\nbe used. Otherwise, it can go through the normal decryption process, and add the outcome to the given mapping.\r\nThe lookup in the mapping is much quicker than the decryption routine, thereby saving several seconds in a small\r\nsample such as this. In testing on my local machine, the script’s runtime went down from 22 seconds to 16\r\nseconds. Slower computers might benefit more of the caching, whereas faster computers might benefit less.\r\nAdding comments and bookmarks\r\nOnce the decrypted values have been obtained, they need to be handed back to the user. This is done in multiple\r\nways. One can add comments (both to the disassembly and decompiler views), bookmarks, and print the results in\r\nthe output window. In the code below, both comment methods, as well as the bookmark creation, have been listed.\r\nDo note that existing bookmarks for a specific address will be overwritten with the createBookmark function.\r\n//Decompiler comment\r\ncurrentProgram.getListing().getCodeUnitAt(toAddr(argument)).setComment(CodeUnit.PLATE_COMMENT, commen\r\n//Disassembly comment\r\ncurrentProgram.getListing().getCodeUnitAt(toAddr(argument)).setComment(CodeUnit.PRE_COMMENT, comment\r\n//Bookmark creation\r\ncreateBookmark(toAddr(argument), \"Decrypted string\", \"The variable named \" + getSymbolAt(toAddr(argum\r\nPutting it all together\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 10 of 22\n\nThe complete script is given below. It contains a few more sanity checks, the most notable being the user feedback\r\nwhen providing details, and the check if there is only a single function with the provided name. When running the\r\nscript, the following output is observed at the end, aside from the list of decrypted values in Ghidra’s console:\r\nDecrypted 47 strings (using 11 cached strings), placed 210 comments, and\r\ncreated 47 bookmarks in 16 seconds!\r\nTo show the difference in the decompiled code, two excerpts are given. The first excerpt is the decompiler output\r\nin Ghidra prior to running the string decryption script, as can be seen below.\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV00);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n uVar3 = bVar1 != false;\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV01);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 2;\r\n }\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV02);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 3;\r\n }\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV03);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 4;\r\n }\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV04);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 5;\r\n }\r\nThe second one contains the comments that have been added by the script.\r\n /* Decrypted value: \"AVAST Software\" */\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV00);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n uVar3 = bVar1 != false;\r\n /* Decrypted value: \"Avira\" */\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 11 of 22\n\npcVar2 = __Z8aDecryptPc(\u0026_aAV01);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 2;\r\n }\r\n /* Decrypted value: \"Kaspersky Lab\" */\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV02);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 3;\r\n }\r\n /* Decrypted value: \"ESET\" */\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV03);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 4;\r\n }\r\n /* Decrypted value: \"Panda Security\" */\r\n pcVar2 = __Z8aDecryptPc(\u0026_aAV04);\r\n bVar1 = __Z7aPathAVPc(pcVar2);\r\n if (bVar1 != false) {\r\n uVar3 = 5;\r\n }\r\nThe decompiled code becomes easily readable, especially with the included symbols. This allows the analyst to\r\nquickly analyse the malware’s functionality.\r\nConclusion\r\nScripting will greatly reduce the amount of time that an analyst needs to spend when looking at a repetitive task.\r\nUnderstanding the basics of Ghidra’s architecture will greatly reduce the amount of time an analyst needs to write\r\nsuch a script. By creating a heavily documented script, it becomes reusable for similar tasks in other samples. As\r\nsuch, each new script will make future work easier.\r\nNote that not all parts of the script in this article are required, more specifically the caching part. Even though it\r\ndoes save some time in this script, the overhead that it requires to write the code is longer than the time that is\r\nsaved with it. When looking at a sample that has a really heavy decryption routine, and/or a lot of encrypted\r\nstrings, the overhead might be worth it.\r\nThis trade-off is up to the analyst to decide, and is also depending on the goal of analyst. Learning how to write\r\nsuch a script might involve more work that is not necessarily the most efficient for a given sample, but has a\r\npositive influence on the analyst’s future scripting abilities.\r\nTo contact me, you can e-mail me at [info][at][maxkersten][dot][nl], or DM me on BlueSky @maxkersten.nl.\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 12 of 22\n\nThe complete script\r\nThe complete script is given below. It can be added as a file to any working ghidra_script directory, or one can\r\nuse the simple editor within Ghidra to create a new script and paste this script in it.\r\n//This script is used to annotate function calls to decrypt strings with the decrypted string. The de\r\n//@author Max 'Libra' Kersten\r\n//@category String decryption\r\n//@keybinding\r\n//@menupath\r\n//@toolbar\r\nimport java.time.Duration;\r\nimport java.time.Instant;\r\nimport java.util.HashMap;\r\nimport java.util.Iterator;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.OptionalLong;\r\nimport ghidra.app.decompiler.DecompInterface;\r\nimport ghidra.app.decompiler.DecompileResults;\r\nimport ghidra.app.script.GhidraScript;\r\nimport ghidra.program.model.address.Address;\r\nimport ghidra.program.model.listing.CodeUnit;\r\nimport ghidra.program.model.listing.Function;\r\nimport ghidra.program.model.mem.MemoryAccessException;\r\nimport ghidra.program.model.mem.MemoryBlock;\r\nimport ghidra.program.model.pcode.HighFunction;\r\nimport ghidra.program.model.pcode.PcodeOp;\r\nimport ghidra.program.model.pcode.PcodeOpAST;\r\nimport ghidra.program.model.pcode.Varnode;\r\nimport ghidra.program.model.symbol.Reference;\r\nimport ghidra.program.model.symbol.ReferenceIterator;\r\npublic class Amadey extends GhidraScript {\r\n/**\r\n * Global variable that is used to keep track of the amount of decrypted strings\r\n */\r\nprivate int decryptionCount;\r\n/**\r\n * Global variable that is used to keep track of the amount of comments that\r\n * have been set\r\n */\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 13 of 22\n\nprivate int commentCount;\r\n/**\r\n * Global variable that is used to keep track of the amount of bookmarks that\r\n * have been set\r\n */\r\nprivate int bookmarkCount;\r\n/**\r\n * Global variable that is used to keep track of the amount of variables that\r\n * have been cached\r\n */\r\nprivate int cacheCount;\r\n/**\r\n * This function is the first that is called by Ghidra when running the script.\r\n * In here, variables need to be initialised first. This script uses this\r\n * function to obtain information from the user, and to verify this information.\r\n * After the verification and other sanity checks, the \"handle\" function is\r\n * called. In there, the decryption logic starts.\r\n */\r\npublic void run() throws Exception {\r\n// Initialisation of the three counters, starting at zero\r\ndecryptionCount = 0;\r\ncommentCount = 0;\r\nbookmarkCount = 0;\r\ncacheCount = 0;\r\n/*\r\n * Requesting the function name in a pop-up dialog. The result is provided as\r\n * the return value, which is stored into the variable\r\n */\r\nString functionName = askString(\"Function name required\",\r\n\"Please provide the name of the function that decrypts the strings:\"\r\n// Provide feedback to the user regarding the provided function name\r\nprintln(\"Received function name: \" + functionName);\r\n/*\r\n * Request the decryption key in a pop-up dialog. The result is provided as the\r\n * return value, which is stored into the variable\r\n */\r\nString key = askString(\"Decryption key required\",\r\n\"Please provide the name of the function that is used during the stri\r\n// Provide feedback to the user regarding the provided decryption key\r\nprintln(\"Received decryption key: \" + key);\r\n/*\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 14 of 22\n\n* The lack of checks for null values or empty strings are omitted, as the\r\n * pop-up dialog does not accept empty strings. Closing the dialog will result\r\n * in the cancellation of the whole script, which is out of scope to handle\r\n * here.\r\n */\r\n/*\r\n * Get all the functions for the given name. Symbol names do not need to be\r\n * unique in Ghidra, hence the fact that a list is returned\r\n */\r\nList\u003cFunction\u003e functions = getGlobalFunctions(functionName);\r\n// If there is only one function with that name, it is safe to continue\r\nif (functions.size() == 1) {\r\n// The first, and only, function resides at index 0\r\nFunction function = functions.get(0);\r\n/*\r\n * The function address is the first address of the function, which is the\r\n * minimum.\r\n */\r\nlong decryptionFunctionAddress = function.getBody().getMinAddress().getOffset\r\n// Provide feedback to the user about the decryption function details\r\nprintln(\"Decryption function (\" + function.getName() + \" found at 0x\"\r\n+ Long.toHexString(decryptionFunctionAddress) + \")\");\r\n// A divider is printed as the decryption process is about to start\r\nprintln(\"-----------------------------------------------------------------\")\r\n// To keep track of the time, the start time is saved\r\nInstant begin = Instant.now();\r\n// The \"handle\" function deals with the decryption logic\r\nhandle(decryptionFunctionAddress, key);\r\n// After the function returns, the end time is saved\r\nInstant end = Instant.now();\r\n// The difference between the two is calculated\r\nDuration duration = Duration.between(begin, end);\r\n// As the decryption routine has finished, a new divided is printed\r\nprintln(\"-----------------------------------------------------------------\")\r\n// The collected statistics, as well as the time the script took, are then\r\n// printed\r\nprintln(\"Decrypted \" + decryptionCount + \" strings (using \" + cacheCount + \"\r\n+ commentCount + \" comments, and created \" + bookmarkCount +\r\n+ \" seconds!\");\r\n// Provide more information about the script to the user\r\nprintln(\"If you have any questions or suggestions, feel free to ping me on Tw\r\n} else if (functions.size() == 0) {\r\n/*\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 15 of 22\n\n* If no function is found, the user is notified and the script returns\r\n */\r\nprintln(\"No functions were found for the given name, please make sure the nam\r\n} else if (functions.size() \u003e= 2) {\r\n/*\r\n * If multiple functions are using the same name, the user should pick a uniq\r\n * name for the decryption function and try again\r\n */\r\nprintln(\"More than one function with this name has been found. Ensure that th\r\n}\r\n}\r\n/**\r\n * This function handles the string decryption logic. It caches the decrypted\r\n * strings, meaning strings that are decrypted more than once, do not need to be\r\n * decrypted, as the mapping already exists. The decrypted variables and\r\n * cross-references are commented in both the disassembly view, as well as the\r\n * decompiler. Additionally, bookmarks are added for each decrypted string.\r\n *\r\n * @param decryptionFunctionAddress the address of the decryption function\r\n * within the sample\r\n * @param key the decryption key\r\n */\r\nprivate void handle(long decryptionFunctionAddress, String key) {\r\n/*\r\n * Create a mapping for handled strings and the address of the variable the\r\n * encrypted content resides at\r\n */\r\nMap\u003cLong, String\u003e handled = new HashMap\u003c\u003e();\r\n/*\r\n * The reference iterator is not limited to a reference amount limit, whereas\r\n * some other methods are\r\n */\r\nReferenceIterator references = currentProgram.getReferenceManager()\r\n.getReferencesTo(toAddr(decryptionFunctionAddress));\r\n// Iterate over all references\r\nfor (Reference reference : references) {\r\n/*\r\n * Get the address of the location that calls the string decryption function\r\n */\r\nAddress address = reference.getFromAddress();\r\n/*\r\n * Get the index of the argument that is to be decrypted. Note that the index\r\n * count for this starts at 1 (unlike the usual starting point of 0).\r\n */\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 16 of 22\n\nint[] argumentIndices = { 1 }; // The decryption routine has only one argumen\r\ntry {\r\n// Get an array with the addresses of the given indices, in this case\r\nOptionalLong[] arguments = getConstantCallArgument(address, argumentI\r\n// Get the address from the array as a long\r\nLong argument = arguments[0].getAsLong();\r\n// Initialise the variables to ensure there are no compiler errors\r\nString decryptedValue = \"\";\r\nString comment = \"\";\r\n/*\r\n * If the mapping already contains the address of the encrypted varia\r\n * already been encountered (and thus decrypted) before. Simply obtai\r\n * value in the mapping for the given key (the variable's address) yi\r\n * correct result and decreases the time the script needs to run\r\n */\r\nif (handled.containsKey(argument)) {\r\ndecryptedValue = handled.get(argument);\r\ncomment = \"Decrypted value (from cache): \\\"\" + decryptedValue\r\n//Increase the cache count with one\r\ncacheCount++;\r\n} else {\r\n/**\r\n * If the address of the encrypted variable is not present, i\r\n * encountered before. As such, it needs to be decrypted.\r\n */\r\ndecryptedValue = getDecryptedArgument(argument, key);\r\ncomment = \"Decrypted value: \\\"\" + decryptedValue + \"\\\"\";\r\n/*\r\n * Set comments at the variable itself in the disassembly and\r\n * respectively\r\n */\r\ncurrentProgram.getListing().getCodeUnitAt(toAddr(argument)).s\r\ncomment);\r\ncurrentProgram.getListing().getCodeUnitAt(toAddr(argument)).s\r\ncomment);\r\n// Increase the comment count with two, based on the above ad\r\ncommentCount += 2;\r\n// Create a bookmark at the encrypted variable's address\r\ncreateBookmark(toAddr(argument), \"Decrypted string\", \"The va\r\n+ getSymbolAt(toAddr(argument)) + \" is equal\r\n// Increase the bookmark count, based on the above added book\r\nbookmarkCount++;\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 17 of 22\n\n// Add this address (and the decrypted value) to the mapping\r\nhandled.put(argument, decryptedValue);\r\n}\r\n/*\r\n * Regardless how the data was obtained, the user is provided with fe\r\n * related to decryption of the string. The argument is also printed\r\n * becomes clickable in the console in Ghidra, allowing the user to n\r\n * it by double clicking.\r\n */\r\nprintln(comment + \" (located at 0x\" + Long.toHexString(argument) + \"\r\n/*\r\n * Set comments at the reference in the disassembly and decompiler vi\r\n * respectively\r\n */\r\ncurrentProgram.getListing().getCodeUnitAt(reference.getFromAddress()\r\ncomment);\r\ncurrentProgram.getListing().getCodeUnitAt(reference.getFromAddress()\r\ncomment);\r\n// Increase the comment count with two, based on the above added comm\r\ncommentCount += 2;\r\n} catch (Exception ex) {\r\nprintln(ex.getMessage());\r\n}\r\n}\r\n}\r\n/**\r\n * Gets the decrypted argument from the given address, which is decrypted using\r\n * the given key\r\n *\r\n * @param argument the address of the argument\r\n * @param key the key that is used to decrypted the value at the argument\r\n * @return the decrypted string\r\n * @throws MemoryAccessException if an error occurs when obtaining the bytes at\r\n * the given address\r\n */\r\nprivate String getDecryptedArgument(Long argument, String key) throws MemoryAccessException {\r\n// Gets the memory block at the argument's address\r\nMemoryBlock block = getMemoryBlock(toAddr(argument));\r\n// The size of the memory block\r\nint size = ((Long) block.getSize()).intValue();\r\n// Get the bytes at the given address for the given size\r\nbyte[] input = getBytes(toAddr(argument), size);\r\n// Get the raw string from the decryption routine\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 18 of 22\n\nString decryptedValue = decrypt(input, key);\r\n/*\r\n * Replace values that are lower than 32 in the ASCII table with escaped ones,\r\n * only those which are used in this sample are visible here\r\n */\r\ndecryptedValue = decryptedValue.replace(\"\\n\", \"\\\\n\").replace(\"\\r\", \"\\\\r\");\r\n// Get the first readable string from the block of memory and return that value\r\nreturn getFirstReadableString(decryptedValue);\r\n}\r\n/**\r\n * Gets the first readable ASCII string from a given input. If no such value can\r\n * be found, the function will return \"NO_ASCII_STRING_FOUND\".\r\n *\r\n * @param input the string to obtain the first readable ASCII string from\r\n * @return the first readable ASCII string\r\n */\r\nprivate String getFirstReadableString(String input) {\r\n// The begin index of the human readable ASCII string\r\nint beginIndex = -1;\r\n// The end index of the human readable ASCII string\r\nint endIndex = -1;\r\n// The lowest human readable ASCII value\r\nint asciiLow = 0;\r\n// The highest human readable ASCII value\r\nint asciiHigh = 255;\r\n/*\r\n * This loop iterates over the given string to find the first human readable\r\n * ASCII character. When it does, the beginIndex variable is set to that value\r\n * and the loop is broken.\r\n */\r\nfor (int i = 0; i \u003c input.toCharArray().length; i++) {\r\nchar currentChar = input.charAt(i);\r\nif (currentChar \u003c asciiHigh || currentChar \u003e asciiLow) {\r\nbeginIndex = i;\r\nbreak;\r\n}\r\n}\r\n/*\r\n * This loop iterates over the given string to find the last human readable\r\n * ASCII character. When it does, the endIndex variable is set to that value and\r\n * the loop is broken.\r\n */\r\nfor (int i = 0; i \u003c input.toCharArray().length; i++) {\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 19 of 22\n\nchar currentChar = input.charAt(i);\r\nif (currentChar \u003e asciiHigh || currentChar \u003c asciiLow) {\r\nendIndex = i;\r\nbreak;\r\n}\r\n}\r\n/*\r\n * If the beginIndex and endIndex are equal to zero or more, both values have\r\n * been found in the two loops. As such, the human readable substring can be\r\n * returned as a substring from the input at the given two indices.\r\n */\r\nif (beginIndex \u003e= 0 \u0026\u0026 endIndex \u003e= 0) {\r\nreturn input.substring(beginIndex, endIndex);\r\n}\r\n/*\r\n * If either (or both) of the indices could not be found, the default value is\r\n * returned\r\n */\r\nreturn \"NO_ASCII_STRING_FOUND\";\r\n}\r\n/**\r\n * The decryption routine that is present in this specific sample\r\n *\r\n * @param input the encrypted data\r\n * @param key the decryption key\r\n * @return the decrypted string\r\n */\r\nprivate String decrypt(byte[] input, String key) {\r\n// The key from the sample\r\nchar[] keyArray = key.toCharArray();\r\n// The length of the key in the sample\r\nint keyLength = keyArray.length;\r\n// The encrypted data is stored in input\r\n// The decrypted data\r\nbyte[] output = new byte[input.length];\r\n// Loop through the input\r\nfor (int i = 0; i \u003c input.length; i++) {\r\n// Decrypt the character\r\noutput[i] = (byte) (input[i] - keyArray[i % keyLength]);\r\n}\r\n// Increase the decryption count\r\ndecryptionCount++;\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 20 of 22\n\n// Return the decrypted string\r\nreturn new String(output);\r\n}\r\n/**\r\n * This function returns an array of optional longs. The size of this array is\r\n * equal to the size of the argument indices' size. The indices of the functoin\r\n * at the given address starts at 1, unlike the usual 0.\r\n *\r\n * @author Lars A. Wallenborn and Jesko H. Hüttenhain(see\r\n * https://blag.nullteilerfrei.de/2020/02/02/defeating-sodinokibi-revil-string-obfusc\r\n * with a slight change in the exception handling by Max 'Libra' Kersten\r\n *\r\n * @param addr the address of the function\r\n * @param argumentIndices the indices of the arguments of said function,\r\n * starting at 1\r\n * @return an array of optional longs, with the addresses of the variables\r\n * @throws IllegalStateException if the previously defined function is null or\r\n * if the decompiler fails to complete\r\n * @throws UnknownVariableCopy if the variable's varnode type is unknown\r\n */\r\nprivate OptionalLong[] getConstantCallArgument(Address addr, int[] argumentIndices)\r\nthrows IllegalStateException, IllegalArgumentException {\r\nint argumentPos = 0;\r\nOptionalLong argumentValues[] = new OptionalLong[argumentIndices.length];\r\nFunction caller = getFunctionBefore(addr);\r\nif (caller == null)\r\nthrow new IllegalStateException();\r\nDecompInterface decompInterface = new DecompInterface();\r\ndecompInterface.openProgram(currentProgram);\r\nDecompileResults decompileResults = decompInterface.decompileFunction(caller, 120, mo\r\nif (!decompileResults.decompileCompleted())\r\nthrow new IllegalStateException();\r\nHighFunction highFunction = decompileResults.getHighFunction();\r\nIterator\u003cPcodeOpAST\u003e pCodes = highFunction.getPcodeOps(addr);\r\nwhile (pCodes.hasNext()) {\r\nPcodeOpAST instruction = pCodes.next();\r\nif (instruction.getOpcode() == PcodeOp.CALL) {\r\nfor (int index : argumentIndices) {\r\nargumentValues[argumentPos] = traceVarnodeValue(instruction.g\r\nargumentPos++;\r\n}\r\n}\r\n}\r\nreturn argumentValues;\r\n}\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 21 of 22\n\n/**\r\n * This function returns an optional long for the given argument's value.\r\n *\r\n * @author Lars A. Wallenborn and Jesko H. Hüttenhain (see\r\n * https://blag.nullteilerfrei.de/2020/02/02/defeating-sodinokibi-revil-string-obfusc\r\n * with a slight change in the exception handling by Max 'Libra' Kersten\r\n *\r\n * @param argument the instruction at the given index as a Varnode object\r\n * @return the address of the argument's value\r\n * @throws UnknownVariableCopy if the variable is unknown\r\n */\r\nprivate OptionalLong traceVarnodeValue(Varnode argument) throws IllegalArgumentException {\r\nwhile (!argument.isConstant()) {\r\nPcodeOp ins = argument.getDef();\r\nif (ins == null)\r\nbreak;\r\nswitch (ins.getOpcode()) {\r\ncase PcodeOp.CAST:\r\ncase PcodeOp.COPY:\r\nargument = ins.getInput(0);\r\nbreak;\r\ncase PcodeOp.PTRSUB:\r\ncase PcodeOp.PTRADD:\r\nargument = ins.getInput(1);\r\nbreak;\r\ncase PcodeOp.INT_MULT:\r\ncase PcodeOp.MULTIEQUAL:\r\nreturn OptionalLong.empty();\r\ndefault:\r\nthrow new IllegalArgumentException(String.format(\"Unknown opcode %s f\r\nins.getMnemonic(), argument.getAddress().getOffset()\r\n}\r\n}\r\nreturn OptionalLong.of(argument.getOffset());\r\n}\r\n}\r\nSource: https://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/\r\nPage 22 of 22",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-strings-in-amadey-1-09/"
	],
	"report_names": [
		"ghidra-script-to-decrypt-strings-in-amadey-1-09"
	],
	"threat_actors": [
		{
			"id": "2864e40a-f233-4618-ac61-b03760a41cbb",
			"created_at": "2023-12-01T02:02:34.272108Z",
			"updated_at": "2026-04-10T02:00:04.97558Z",
			"deleted_at": null,
			"main_name": "WildCard",
			"aliases": [],
			"source_name": "ETDA:WildCard",
			"tools": [
				"RustDown",
				"SysJoker"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "256a6a2d-e8a2-4497-b399-628a7fad4b3e",
			"created_at": "2023-11-30T02:00:07.299845Z",
			"updated_at": "2026-04-10T02:00:03.484788Z",
			"deleted_at": null,
			"main_name": "WildCard",
			"aliases": [],
			"source_name": "MISPGALAXY:WildCard",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434647,
	"ts_updated_at": 1775826692,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/46ec2b6d605ae457ee032a9f9f3d903e94a6cdde.pdf",
		"text": "https://archive.orkl.eu/46ec2b6d605ae457ee032a9f9f3d903e94a6cdde.txt",
		"img": "https://archive.orkl.eu/46ec2b6d605ae457ee032a9f9f3d903e94a6cdde.jpg"
	}
}