Without a shell, IDA can be opened to directly enter the main function:
Line 12 calls the VirtualProtect function to change the access protection permission at offset encrypt
BOOL VirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );
See: https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
The data here is 0x4:PAGE_READWRITE
Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, attempting to execute code in the committed region results in an access violation.
To put it simply, this piece of data can be read and written (usually only one of read / write can be available in the text segment)
Read on and find that the input value should match the length of 24 bytes, and then encounter the two functions of wrong and omg
char *__cdecl wrong(char *a1) { char *result; // eax int i; // [esp+Ch] [ebp-4h] for ( i = 0; i <= 23; ++i ) { result = &a1[i]; if ( (i & 1) != 0 ) a1[i] -= i; else a1[i] ^= i; } return result; }
int __cdecl omg(char *a1) { int result; // eax int v2[24]; // [esp+18h] [ebp-80h] BYREF int i; // [esp+78h] [ebp-20h] int v4; // [esp+7Ch] [ebp-1Ch] v4 = 1; qmemcpy(v2, &unk_4030C0, sizeof(v2)); for ( i = 0; i <= 23; ++i ) { if ( a1[i] != v2[i] ) v4 = 0; } if ( v4 == 1 ) result = puts("hahahaha_do_you_find_me?"); else result = puts("wrong ~~ But seems a little program"); return result; }
Wrong performs some addition, subtraction or XOR processing on the input value, and then the result is the same as unk in omg_ Compare the data at 4030c0; The inverse algorithm of wrong is easy to implement. Just copy it
(now I know that I can quickly extract data through the export window. It's too stupid to copy manually all the time)
unsigned int k[24] = { 0x66,0x6b,0x63,0x64,0x7f,0x61,0x67,0x64,0x3b,0x56,0x6b,0x61,0x7b,0x26,0x3b,0x50,0x63,0x5f,0x4d,0x5a,0x71,0xc,0x37,0x66 }; for (int i = 0;i < 24; i++) { if ((i & 1) != 0) { k[i] += i; } else { k[i] ^= i; } cout << (char)k[i]; } cout << endl;
Get the result flag{fak3_alw35_sp_me!!}, Errors found in submission; Since there are still key encrypt segments not analyzed, you don't have to doubt whether the flag is miscalculated. You can boldly treat it as a fake flag
Read down the for loop and find that it XOR the offset encrypt. Judge that it is a code segment decryption, and you can use the dynamic call to go to this place
IDA failed to update in time, so we need to manually correct it as a function
Select 00401500~0040152F and mark it as force
Then create a function at 00401502 to get the appropriate result
// positive sp value has been detected, the output may be wrong! void __usercall __noreturn sub_401502(int a1@<ebp>) { unsigned __int32 v1; // eax v1 = __indword(0x57u); *(_DWORD *)(a1 - 32) = 1; qmemcpy((void *)(a1 - 108), &unk_403040, 0x4Cu); for ( *(_DWORD *)(a1 - 28) = 0; *(int *)(a1 - 28) <= 18; ++*(_DWORD *)(a1 - 28) ) { if ( (char)(*(_BYTE *)(*(_DWORD *)(a1 - 28) + *(_DWORD *)(a1 + 8)) ^ Buffer[*(_DWORD *)(a1 - 28)]) != *(_DWORD *)(a1 + 4 * *(_DWORD *)(a1 - 28) - 108) ) { puts("wrong ~"); *(_DWORD *)(a1 - 32) = 0; exit(0); } } if ( *(_DWORD *)(a1 - 32) == 1 ) puts("come here"); }
The code from IDA analysis is not so easy to read. Obviously, it translates some indexes incorrectly, but it is not incomprehensible
First, extract unk_ The data at 403040 is placed at (a1-108) and the Buffer used in the cycle
char Buffer[] = "hahahaha_do_you_find_me?"; unsigned int unk_403040[19] = {0x0E,0x0D ,0x09 ,0x06 ,0x13 ,0x05 ,0x58 ,0x56 ,0x3E ,0x06 ,0x0C ,0x3C ,0x1F ,0x57 ,0x14 ,0x6B ,0x57 ,0x59 ,0x0D };
* (a1-28) is actually an index, indicating that this cycle will be executed 19 times; And (* (a1 - 28) + *(a1 + 8)) is equivalent to the input value pointer plus an offset, and its content is our input value
The input value and the result after Buffer XOR should be equal to (a1 - 108), that is, unk_ It is also easy to write decryption code for the data at 403040
char key1[] = "hahahaha_do_you_find_me?"; unsigned int f[19] = {0x0E,0x0D ,0x09 ,0x06 ,0x13 ,0x05 ,0x58 ,0x56 ,0x3E ,0x06 ,0x0C ,0x3C ,0x1F ,0x57 ,0x14 ,0x6B ,0x57 ,0x59 ,0x0D }; for (int i = 0; i < 19; i++) { f[i] ^= key1[i]; cout << (char)f[i]; } cout << endl;
Get flag{d07abccf8a410c
We know that the flag should have 24 bytes, but the for loop is only 19 times, that is, 5 characters are missing; Since the encrypt function has been read, the result we need should be in the last function, that is, the finally function
Convert all the data at 40159A~40159D into code, and change the function to Undefine
Re create the function at 40159A to get the new function finally:
int __cdecl finally(char *a1) { unsigned int v1; // eax int result; // eax char v3[9]; // [esp+13h] [ebp-15h] BYREF int v4; // [esp+1Ch] [ebp-Ch] strcpy(v3, "%tp&:"); v1 = time(0); srand(v1); v4 = rand() % 100; if ( (v3[*&v3[5]] != a1[*&v3[5]]) == v4 ) result = puts("Really??? Did you find it?OMG!!!"); else result = puts("I hide the last part, you will not succeed!!!"); return result; }
time(0) is used to obtain the current time, the 10th line is used as the seed, and the 11th line is used to obtain the random number; With high probability, it is difficult for us to obtain the seeds obtained by the author. Therefore, if this random number is necessary, it should only be obtained through prediction
As well as the following if judgment conditions are too difficult to understand, you might as well try to use OD for dynamic adjustment (personally, I think OD dynamic adjustment will be better used, or OD can be analyzed if this function is not encrypted, otherwise you can only use IDA dynamic adjustment, although there is no difference...)
Even with OD dynamic tone, it is still not easy to understand its meaning
The key comparison is 401617. If it is equal, it means that the flag is lost correctly
Roughly, take the number of bits of the flag and%tp&: "the number of bits is equal; and this is exactly 5 bytes, which is likely to be the rest of the flag
But there seems to be no corresponding encryption process in the assembly code, so we can only guess that it has not been complicated encrypted
Guess that the last character should be '}' through the flag in the first half of the paragraph, and get 71 after XOR with the last XOR of "% TP &:" to get the final result
char key2[] = "%tp&:"; int v5 = '}' ^ key2[4]; for (int i = 0; i < 5; i++) { cout << (char)(key2[i] ^ v5); } //flag{d07abccf8a410cb37a}
I also tried to input the flag successfully submitted, but it still won't output the successful logo. It may be a little "malicious" from the author Finally, we need to guess to get the results. To be honest, it's a little difficult to let go. I always feel whether I missed something important