cricket32

Categories: re
2020-07-24
by not_really

Flick will pay 390 bells for this cricket.

Author: kuilin

Files: cricket32.S

We’re given an .asm (well, .S) file and a comment on how to compile it. Let’s open up Ghidra, but just know that the decompilation isn’t going to be completely accurate.

In Ghidra, main won’t be a function by default, so press d on the first instruction to convert it to code.

void UndefinedFunction_0001121c(int param_1, int param_2) {
  ushort *puVar1;
  ushort uVar2;
  uint uVar3;
  char *__format;
  ushort *puVar4;
  int iVar5;
  uint *puVar6;
  uint uVar7;
  
  __format = "Usage: ./cricket32 flag\nFlag is ascii with form uiuctf{...}\n";
  if (param_1 == 2) {
    puVar6 = *(uint **)(param_2 + 4);
    iVar5 = -1;
    do {
      iVar5 = iVar5;
      iVar5++;
    } while ((*(byte *)((int)puVar6 + iVar5) & 0x7f) != 0);
    if (0 < iVar5 - 0x19) {
      uVar7 = 0;
      puVar4 = (ushort *)0x1122c;
      iVar5 = 7;
      do {
        uVar2 = *puVar4;
        puVar1 = puVar4 + 2;
        puVar4 = (ushort *)((int)puVar4 + 0xd);
        uVar3 = crc32(CONCAT22((ushort)((((uint)uVar2 & 0xff00) << 8) >> 0x10) |
                               (ushort)(((uint)uVar2 << 0x18) >> 0x10),
                               CONCAT11((char)*puVar1, (char)(*puVar1 >> 8))), *puVar6);
        uVar7 = uVar7 | uVar3 ^ *puVar6;
        puVar6++;
        iVar5--;
      } while (iVar5 != 0);
      __format = "Flag is correct! Good job!\n";
      if (uVar7 == 0) goto LAB_000112bc;
    }
    __format = "Flag is not correct.\n";
  }
LAB_000112bc:
  printf(__format);
  return;
}

Okay, so the pass condition seems to be if uVar7 (edi) == 0 after the do while. This code seems confusing and probably didn’t decompile well (iVar5 == iVar5?), so we can figure things out with debugging.

Before we get started in IDA, you’ll notice that it has the same problem as Ghidra where it’s not able to detect a function. However, if we try to press p to convert it into a function, it fails. The red text seems to indicate that there’s something up with this jump function.

image-20200723144433090

It seems to go to somewhere in the middle of a defined instruction (???). If we right click and click manual, we can edit the instruction to get rid of the +1. Then p on the first instruction again will get graph view working.

I was interested in what iVar5 was so I set a breakpoint after the first loop.

image-20200721160911486

We find out that the value in ebp at this point is 0x1d, the length of a dummy input parameter I gave it before I started it.

image-20200721161012900

Later, we see it has 0x1a subtracted from it, then 0x1 later on. So the length of our string probably needs to be 0x1b (27) or greater (not 0x19 like Ghidra shows).

Now to focus on how uVar7 (edi) is set (to zero).

image-20200721161626491

Hmm, so edi is set by oring edx, so edx must always be 0 for edi to stay 0 as well. And for edx to be 0, [esi] have to be the same value. This is interesting, because of the crc32 right above. Let’s look at the docs for this.

Starting with an initial value in the first operand (destination operand), accumulates a CRC32 (polynomial 11EDC6F41H) value for the second operand (source operand) and stores the result in the destination operand. The source operand can be a register or a memory location. The destination operand must be an r32 or r64 register. If the destination is an r64 register, then the 32-bit result is stored in the least significant double word and 00000000H is stored in the most significant double word of the r64 register.

Okay, so edx is actually input which is really weird (I’ll call it the crc “seed”). For us to pass, this condition must be true: crc32(edx, [esi]) == [esi]). In other words, the result of crc32 should also be it’s input. Also something weird: edx, the seed, is calculated by using values from [ebx] and [ebx + 4]. We can see ebx is initialized to loc_5663522A+2 which points to the assembly code bytes. What that means is that the flag is encoded in the assembly itself.

Now we could calculate edx ourselves, or we could just debug it and see what the values end up being. edx is never permanently affected by our input, so we can just put in garbage and see what edx ends up being at that specific position.

image-20200721164034892

Here’s what edx looks like on the first go.

So now we need a way to calculate what crc32 values equal themselves. We could write an assembly program to do that and uses the crc32 instruction, but eh, who uses assembly these days. We can just write it in c. Back on the assembly page, we see there’s a c equivalent:

Intel C/C++ Compiler Intrinsic Equivalent
unsigned int _mm_crc32_u8( unsigned int crc, unsigned char data )
unsigned int _mm_crc32_u16( unsigned int crc, unsigned short data )
unsigned int _mm_crc32_u32( unsigned int crc, unsigned int data )
unsinged __int64 _mm_crc32_u64( unsinged __int64 crc, unsigned __int64 data )

Since edx/esi is 4 byte, we’ll use the u32 version.

crc32 is pretty fast, so we can just brute force the values (thankfully it’s not u64 :D).

#include <stdio.h>
#include <stdint.h>
#include <intrin.h>

int main(int argc, char *argv[]) {
	unsigned int seed = strtoul(argv[1], NULL, 16);
	for (int i = 0; i < 0xfffffffe; i++) {
		unsigned int res = _mm_crc32_u32(seed, i);
		if (res == i) {
			printf("%08x\n", res);
			return 0;
		}
	}
	printf("fail\n");
	return 0;
}

Let’s compile it with gcc:

C:\Users\notrly\Documents\uiuctf>gcc crccalc.c -o crccalc
In file included from C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/9.3.0/include/immintrin.h:37,
                 from C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/9.3.0/include/x86intrin.h:32,
                 from C:/msys64/mingw64/x86_64-w64-mingw32/include/intrin.h:73,
                 from crccalc.c:3:
crccalc.c: In function 'main':
C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/9.3.0/include/smmintrin.h:839:1: error: inlining failed in call to always_inline '_mm_crc32_u32': target specific option mismatch
  839 | _mm_crc32_u32 (unsigned int __C, unsigned int __V)
      | ^~~~~~~~~~~~~
crccalc.c:8:22: note: called from here
    8 |   unsigned int res = _mm_crc32_u32(seed, i);
      |                      ^~~~~~~~~~~~~~~~~~~~~~

Hmm… Let’s see why this is happening. A quick search comes up with https://stackoverflow.com/a/54654877. Because it’s a cpu specific function, we need to use the -mavx flag to compile.

Now that it’s compiled, let’s test that first value, 4C10E5F7.

C:\Users\notrly\Documents\uiuctf>crccalc 4c10e5f7
63756975

That in ascii backwards is uiuc. Looks like we’re on the right track! Let’s step through with IDA and record the edx values right before crc32 is executed (see screenshot above).

4c10e5f7
f357d2d6
373724ff
90bbb46c
34d98bf6
d79ee67d
aa79007c

And if we decode them using crccalc:

4c10e5f7 - 63756975
f357d2d6 - 617b6674
373724ff - 6972635f
90bbb46c - 74656b63
34d98bf6 - 635f615f
d79ee67d - 6b636172
aa79007c - 007d7469

And if we put those together in reverse order:

7569756374667b615f637269636b65745f615f637261636b69747d00

Which is: uiuctf{a_cricket_a_crackit}.