[BUUCTF] [wangdingbei 2020 Qinglong formation] jocker analysis and record

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

Added by x1nick on Sun, 23 Jan 2022 12:11:23 +0200