The ckriellideon 🔥

Flareon2022 Part1: Pixel Poker & Magic8Ball

Short writeups of the 2nd and 3rd FlareOn 2022 challenges

- 10 min

As part of reversing training, I’m going to go through this years Flare-On challenges. I played during the CTF, however it came during a transitional period for me, and so I wasn’t able to play a lot of it (also I have windows only on my desktop which didn’t help xp). But since I want to become better at reversing, and I consider it a great resource, I want to complete it even if the CTF is over. So looking forward to this journey!

PixelPoker

Running the executable

Once we extract the files from the archive, we get the binary PixelPoker and a readme which says:

1
2
3
4
5
Welcome to PixelPoker ^_^, the pixel game that's sweeping the nation!

Your goal is simple: find the correct pixel and click it

Good luck!

Let’s run the executable and find out what they’re talking about :eyes:

Once we run it, we are greeted with a window containing a lot of pixels at the different mouse coordinates of the window. At the top right, we can see the title of the window, which has the form PixelPoker (<pixel x coordinate>/<pixel y coordinate>) - #0/10 . As we move the mouse around, the coordinates change. If we click at a random pixel, the #0 changes to #1. So we can imagine it’s an attempt counter, and we have 10 chances. If we fail, a message box will pop up with the text Womp womp... :( and title Please play again!. The File and Help options in the top left don’t offer much help. So let’s start reversing the executable.

Reversing the executable

Once I opened it with IDA, I saw it was a 32-bit binary. So I opened it with ghidra to get decompilation. Once the binary is analyzed, I opened the entry function and was dropped into a function with a lot of stripped names. So to find myself in important code, I searched for some of the known strings we have from the program. In the different strings I found where the title messages which where described above. So I clicked on on PixelPoker (%d,%d) - #%d/%d, and followed it’s XREF into FUN_004012c0, which returned the function.

Before analyzing it, I quickly pulled up the function call graph, to see the calls made to reach this function, so I could understand the flow of the program better. This function is called only by FUN_00401120:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void FUN_00401120(HINSTANCE param_1)

{
  WNDCLASSEXW local_34;

  local_34.cbSize = 0x30;
  local_34.style = 3;
  local_34.lpfnWndProc = FUN_004012c0;
  local_34.cbClsExtra = 0;
  local_34.cbWndExtra = 0;
  local_34.hInstance = param_1;
  local_34.hIcon = LoadIconW(param_1,(LPCWSTR)0x6b);
  local_34.hCursor = LoadCursorW((HINSTANCE)0x0,(LPCWSTR)0x7f00);
  local_34.hbrBackground = (HBRUSH)0x6;
  local_34.lpszMenuName = (LPCWSTR)0x6d;
  local_34.lpszClassName = (LPCWSTR)&DAT_004131b0;
  local_34.hIconSm = LoadIconW(local_34.hInstance,(LPCWSTR)0x6c);
  RegisterClassExW(&local_34);
  return;
}

It seems that local_34 is a variable of the WNDCLASSEXA structure, which is set up, and is passed as a memory address to the RegisterClassExW function. As the WinAPI says, RegisterClassExW registers a window class which can be used when creating a window. The lpfnWndProc member is a pointer to the main window procedure. This means that whenever we interact with the window defined in this class, FUN_004012c0 will be called.

Before that is FUN_004016f0. This function can actually be found in the entry function towards the end:

1
2
3
4
5
6
...
	iVar2 = FUN_004016f0(0x400000,0,uVar3,uVar1);
	_exit(iVar2);
	__cexit();
	return iVar2;
}

Which could have potentially caught my eye because of the base address 0x400000. So if we rename param1 to something like base_address, FUN_004016f0 makes more sense. Also from IDA this function was called wWinMain.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int wWinMain(HINSTANCE base_address,HINSTANCE hPrevInstance,PWSTR pCmdLine,int nCmdShow)
{
  int iVar1;
  HACCEL hAccTable;
  tagMSG local_20;
  
  loaded_image = LoadImageW(base_address,(LPCWSTR)0x81,0,0,0,0x2000);
  LoadStringW(base_address,0x67,(LPWSTR)&loaded_string1,100);
  LoadStringW(base_address,0x6d,(LPWSTR)&loaded_string2,100);
  RegisterCheckClass(base_address);
  iVar1 = FUN_00401040(base_address,nCmdShow);
  if (iVar1 == 0) {
    return 0;
  }
  hAccTable = LoadAcceleratorsW(base_address,(LPCWSTR)0x6d);
  ...
}

So we understand the general flow what we care about. wWinMain is the main function of the program, which calls RegisterCheckClass. That function registers a window class, which set’s FUN_004012c0 (renamed to GamePixelCheckProcedure), which is called whenever CallWindowProc is called, according to WinAPI. If we look at the parameters described there, they will be of help (I think) into understanding the check procedure.

A closer inspection on GamePixelCheckProcedure

We see that different pieces of code are executed depending on the value of param_2. According to WNDPROC (or CallWindowProcA from before), this parameter holds the message passes to the procedure. The function probably has different checks to perform different operations depending on the message. For example:

1
2
3
4
5
6
7
sVar1 = (short)((uint)param_4 >> 0x10);
if (param_2 == 0x200) {
      FUN_004017be(local_148,0x104,"PixelPoker (%d,%d) - #%d/%d",(int)(short)param_4,(int)sVar1,
                   DAT_00413298,10);
      SetWindowTextA(param_1,local_148);
      return 0;
    }

I think sets the values the mouse passes through to the window. This makes sense considering the last number is 10. That makes param_4 the value of the x axis, sVar1 of the y axis (which makes sense considering it comes from param_4), and DAT_00413298 a memory section showing how many tries we have attempted. Under that light, we can rename some of the variables.

Below that, if uMsg is 0x201 is interesting:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    if (uMsg == 0x201) {
      x_val = (uint)(short)coordinates;
      y_val = (uint)y_coordinate;
      if (attempted_tries == 10) {
        MessageBoxA((HWND)0x0,"Womp womp... :(","Please play again!",0);
        DestroyWindow(param_1);
      }
      else {
        attempted_tries = attempted_tries + 1;
        if ((x_val == s_FLARE-On_00412004._0_4_ % DAT_00413280) &&
           (y_val == s_FLARE-On_00412004._4_4_ % DAT_00413284))
            ...

The x and y coordinates are loaded into variables, and a check is done for the attempted tries. If we reach 10 tries, we have failed. If we have attempted less than 10 times, attempted_tries is incremented, and this interesting if is executed. From what we’ve understood from the decompilation thus far, this checks if the input values for the coordinates is correct, with the correct values being the result of the modulo operation between s_FLARE-On_00412004._0_4_ % DAT_00413280, and s_FLARE-On_00412004._4_4_ % DAT_00413284. The s_FLARE-On data section has the FLARE-On string stored, while the DAT_ sections have nothing. After I searched a bit if anything is passed into these data sections and found nothing, I decided to use a debugger, since then we would get values for these addresses.

Dynamic analysis

We open x64dbg, and run the PixelPoker executable so we can attach it. Let’s place a breakpoint at address 0x40146F which is where attempted_tries is incremented. A couple of addresses down, div esi is executed. Based on the documentation for the unsigned divide, the remainder is stored in the edx register. After that instruction is executed, we can see that 0x5f is stored into edx (and 0x02e5 into esi, sto that was the modulo value). So let’s choose a second pixel, only this time let’s have the x coordinate be 95, and set a breakpoint at 0x40149B.

We successfully hit it, and see that the y coordinate is 0x139, or 313 in decimal. So instead of going back and choosing the correct pixel, let’s just change the value of ebx for the cmp instruction, and get the flag. Of course we could have also patched the instructions to pass with any coordinate input.

w1nN3r_W!NneR_cHick3n_d1nNer@flare-on.com

Magic8Ball

The interesting thing about this challenge is that when we extract the executable from the archive, many DLLs come with it.

Running the executable

If we run magic8ball, we can see that this is another GUI challenge. We are presented with a magic ball, with the window giving us two texts, Press arrow keys to shake the ball, and Start typing your question (max. 75 characters):. If we press an arrow key, the ball moves accordingly, and if we enter a string and hit enter, a random message appears at the center of the ball. These are the 2 ways we can interact with the GUI. Let’s proceed to reverse it.

Reversing the executable

If we open it with IDA, again we see that it’s a 32-bit executable. So again we go with ghidra for decompilation. However this time I decided to keep the wWinMain address from IDA, and start reversing from there. So in ghidra we enter the offset 403680.

The function is very small, essentially holding only a call to FUN_00403690. That function is pretty big, with a lot of function calls like GetPRocessHeap and HeapAlloc. Potentially it sets up the heap for the rest of the process, but I’m not sure. Amongst all these functions, we see this line uVar4 = FUN_004027a0();. Since this is the only stripped function, we can imagine it’s part of the original source, and so we proceed and investigate it further.

In it are calls to different functions. Let’s go through them and focus on some important parts

1
2
3
4
5
6
7
8
  _Dst = (void *)FUN_0040296d(0x174);
  if (_Dst == (void *)0x0) {
    DAT_00406090 = (char *)0x0;
  }
  else {
    memset(_Dst,0,0x174);
    DAT_00406090 = (char *)FUN_004012b0((int)_Dst);
  }

FUN-0040296d seems to allocate some memory with malloc, and returns a pointer to that chunk. Then memset sets 0’s to that entire memory space, and FUN_004012b0 passes the strings the ball displays into that allocated chunk.

I couldn’t quite understand FUN_004018f0. It passes a stack address, the string Magic 8 Ball, and 0xc which is the length of the string. So inside the function we can change the parameter names accordingly. The interesting part is that the stack address is recognized as the pointer *this. From Java I remember that this is used in classes to access data internal to each object of the class. So I assume that this function is a generator for a class, and a reference to it’s object is stored into the passed stack address.

After it FUN_00402090 seems to initialize different things, like fonts. Amongst all the things stored at different offsets of the this pointer, are these values:

1
2
3
4
            *(undefined4 *)((int)this + 0x5c) = 0x6d6d6967;
            *(undefined4 *)((int)this + 0x60) = 0x6c662065;
            *(undefined4 *)((int)this + 100) = 0x70206761;
            *(undefined4 *)((int)this + 0x68) = 0x3f736c;   

If we turn the hex bytes to chars, we get the string gimme flag pls?. If we enter it, it won’t give us the flag :(. However it’s something we should keep in mind.

After it, FUN_004024e0 has some very interesting instructions as well:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    if (*(char *)ppcVar4 == 'L') {
      ppcVar4 = this;
      if (0xf < uVar1) {
        ppcVar4 = (char **)*this;
      }
      if (*(char *)((int)ppcVar4 + 1) == 'L') {
        ppcVar4 = this;
        if (0xf < uVar1) {
          ppcVar4 = (char **)*this;
        }
        if (*(char *)((int)ppcVar4 + 2) == 'U') {
          ppcVar4 = this;
          if (0xf < uVar1) {
            ppcVar4 = (char **)*this;
          }
          if (*(char *)((int)ppcVar4 + 3) == 'R') {
            ppcVar4 = this;
            if (0xf < uVar1) {
              ppcVar4 = (char **)*this;
            }
            if (*(char *)(ppcVar4 + 1) == 'U') {
              ppcVar4 = this;
              if (0xf < uVar1) {
                ppcVar4 = (char **)*this;
              }
              if (*(char *)((int)ppcVar4 + 5) == 'L') {
                ppcVar4 = this;
                if (0xf < uVar1) {
                  ppcVar4 = (char **)*this;
                }
                if (*(char *)((int)ppcVar4 + 6) == 'D') {
                  ppcVar4 = this;
                  if (0xf < uVar1) {
                    ppcVar4 = (char **)*this;
                  }
                  if (*(char *)((int)ppcVar4 + 7) == 'U') {
                    ppcVar4 = this;
                    if (0xf < uVar1) {
                      ppcVar4 = (char **)*this;
                    }
                    if (*(char *)(ppcVar4 + 2) == 'L') {
                      _Str1 = (undefined4 *)((int)param_1 + 0xf8);

What if we enter these arrow keys to the ball? Nothing changes. How about these with the string? That’s indeed enough to get the flag.

In fact, if we were to look at the decompilation a bit better, this makes sense. After all these keyboard arrow checks, it executes

1
2
3
4
5
6
iVar2 = strncmp((char *)_Str1,(char *)((int)param_1 + 0x5c),0xf);
if (iVar2 == 0)
{
    FUN_00401220(&stack0xffffffc0,this);
    FUN_00401a10(param_1,in_stack_ffffffc0);
}

And if we were to look at FUN_004027a0, we would understand that the this pointer of the class object is stored into DAT_00406090. So what’s passed as param1 to FUN_004024e0 is the object of the class. And as we saw in the previous functions, different values where stored at different offsets. The offset where the gimme flag pls? string was stored was 0x5c. The same offset accesses with param1 in strcmp. So now we can be sure why this works.

U_cRackeD_th1$_maG1cBaLL_!!_@flare-on.com

Even though I solved the challenge I still have a gap into the logic behind reversing c++ classes. I was told by Shad3 that this course is really strong.