pycdc failed to decompile run.pyc, so the next step i do is disassembling pyc file using pycdas.
It has so many lines and should take times if i manually convert it to python code. So my approach is by building custom python executable that printout value in specific opcode. In this case i try to dump the values during COMPARE_OP execution. Clone spesific version (3.10).
Line 2-11: function to print value for string data type
Line 23-32: print data type and value if data type is string
There is different way to print each type data in python internal, in this case reprint work for string type data which is used in COMPARED_OP. Next, just compile the python
./configuremake
Execute run.pyc with new compiled python and we will get the flag which is value compared in COMPARED_OP with string type data.
./pythonrun.pyc
Flag: ACSC{d1d_u_notice_the_oob_L04D_C0N5T?}
YaraReTa (250 pts)
Description
Yara... Re... Ta...
Solution
Given 5 files as follows.
Files overview:
yara
Yara executable
libyara.so.10
Library for yara executable
yarareta
Compiled yara rules
PrintFlag
Executable that decrypt the flag
check.sh
Wrapper for running yara with library, compiled yara rules, and file target
Running the wrapper (check.sh), we will get the following output
From the output and the PrintFlag binary i assume that the objective is to find the correct key.
At first, i tried to find any reference regarding yara compiled rules reverse engineering and i just found this reference. The article explain about yara internal including the VM and its structure. From that article i got information that function that becomes VM executor/runner yr_execute_code in libyara/exec. Because yara is open source and the version of Yara is known, so i tried to create modified yara.
Line 5 and 11: dump the OPCODE during execution process
./build.sh# wrapper to compile yara
After executing build.sh there will be yara executable which is bash script (wrapper for new compiled yara). Executing yarareta rule with new yara will generated output below
There is an error when we run yarareta with our new yara. Back to given library, i tried to dump the opcode by using gdb scripting.
Using given library and yara executable there is no error, so there is something wrong with new library or new yara. Reading the source code again, i tried to dump the module name loaded with OP_IMPORT.
libyara/exec.c
... case OP_IMPORT:YR_DEBUG_FPRINTF(2, stderr,"- case OP_IMPORT: // %s()\n", __FUNCTION__); r1.i =yr_unaligned_u64(ip); ip +=sizeof(uint64_t);printf("MODULE: %s\n", r1.p);#ifYR_PARANOID_EXECensure_within_rules_arena(r1.p);#endif result =yr_modules_load((char*) r1.p, context);if (result != ERROR_SUCCESS) stop =true;break;...
Line 7: print module name
make# compile yara
From image above we can see that yara tried to load acsc module which is not available in our new yara. Based on yara documentation , i tried to create fake module with name acsc.
create folder libyara/modules/acsc/
copy libyara/modules/demo/demo.c to libyara/modules/acsc/acsc.c
Finally just compile yara using make command and run yara again.
Now it has different error but in same opcode which is OP_IMPORT. Failed module to load is magic but magic is not custom module. It included in yara source code but not automatically included during basic compilation. From this reference, basically we just need to add argument --enable-magic when running configure script.
Now there is no error during opcode OP_IMPORT but there is an error during opcode OP_OBJ_FIELD.
libyara/exec.c
...case OP_OBJ_FIELD:YR_DEBUG_FPRINTF(2, stderr,"- case OP_OBJ_FIELD: // %s()\n", __FUNCTION__); identifier =yr_unaligned_char_ptr(ip);printf("IDENTIFIER: %s\n", identifier); ip +=sizeof(uint64_t);#ifYR_PARANOID_EXECensure_within_rules_arena(identifier); ...
Line 7: add print identifier
From image above we can see that there is an error caused by invalid identifier (identifier not found). Until this step, we must note there are 2 identifiers shown on image which are type and check. Besides that, acsc and magic import process are not error again so those modules should be stored in yara or library. Based on simple check, i found that "Hello World!" from my acsc module is stored on libyara.so.10.
Decompiling my own library, it shown that cross reference to "Hello World!" is acsc__load
Searching acsc on function name also shown that there are some function named "acsc_*" on my libyara.so.10. Lets compare with magic module.
We can see that there are 2 functions declaration which are mime_type -> magic_mime_type and type -> magic_type. So we can conclude that identifier during OP_OBJ_FIELD execution is function name or attribute name that the rule try to load from library.
Now take a look on acsc modules inside libyara.so.10 from the challenge.
From image above we can reconstruct declare_function for acsc.check which should be declare_function("check", "r", "i", check). From this reference, we know that the check function take regex as argument and use integer type for return value. Now, lets write fake function for our fake module.
...#defineMODULE_NAME acscdefine_function(check){int result =0; YR_SCAN_CONTEXT* ctx =yr_scan_context();if (yr_re_match(ctx, regexp_argument(1),"ABCDEFGHIJKLMNOP")>0) { result =1; }return_integer(result);}begin_declarationsdeclare_string("greeting");declare_function("check","r","i", check);end_declarations...
Compile with make and run yara again
Now there is no error and we can see some branches (OP_JFALSE). Before branch, there is OP_CALL and OP_OBJ_VALUE which indicates calling of function and usage of return value. My assumption is it becomes false because the return value from function call is false. Lets back to the given library and validate the assumption.
On check+82 (call yr_re_match) we can see each argument value and the last argument is the key on PrintFlag (value that will be validated)
On line 4 we can see that there is checking for return value from yr_re_match function.
If return value == -1, check will return 0 (v23)
else, check will return 1 (v23)
First, lets try to dump the regex value. yr_re_match will pass the argument to yr_re_exec. yr_re_exec will act as VM executor for regex. Each regex opcode can be found on libyara/include/yara/re.h.
After successfully dump regex opcode, next we will try to change the return of yr_re_match so the return of check function will be changed also. In this step, we assume that it continue the process if return is true (1).
My assumption is correct, the process will continue if return is true. Now create fake module always return true, so we will know all the regex opcode.
libyara/modules/acsc/acsc.c
...define_function(check){int result =1; YR_SCAN_CONTEXT* ctx =yr_scan_context();if (yr_re_match(ctx, regexp_argument(1),"ABCDEFGHIJKLMNOP")>0) { result =1; }return_integer(result);}...
Line 4: make result always true
We can see that used regex are RE_OPCODE_MATCH_AT_START and RE_OPCODE_CLASS. Through searching on testing i found that it was like ^[value].
Take a look on executed regex opcode, in RE_OPCODE_CLASS i found there is call of function _yr_re_is_char_in_class. _yr_re_is_char_in_class will validate is it target (chr) in range of class or not. There is also another flags such as case_insensitive and negated. In this step, my idea is try to dump all valid values by bruteforcing value in CHAR_IN_CLASS function. Last, to make the regex process continue, hard code the result variable to 1 (always return true).
libyara/re.c
...staticbool_yr_re_is_char_in_class( RE_CLASS* re_class,uint8_t chr,int case_insensitive){printf("%x valid_char: ", chr);for (int i =0; i <0x100; ++i) {ifCHAR_IN_CLASS(re_class->bitmap, i){printf("%d, ", i); } }int result =CHAR_IN_CLASS(re_class->bitmap, chr);if (case_insensitive){printf("|case_insensitive"); result |=CHAR_IN_CLASS(re_class->bitmap, yr_altercase[chr]); }if (re_class->negated){printf("|negated"); result =!result;