Stomped

Control flow flattening

stomped
Author: Fawl

Help! I was trying to retrieve my flags from my super secure safe, but I accidentally stepped on my binary.

Background

Stomped was a challenge I made to explore an interesting obfuscation technique I'd run across a few times in the past, and was interested in testing out myself - control flow flattening. The challenge name alluded to that, somewhat.

Before we get to the challenge proper, I'd like to give the reader an overview of just what exactly control flow flattening accomplishes. This is with the goal of informing the reader's understanding of some terms I'll use, and the basic premise of the challenge itself.

Typically, programs execute in a manner that can be described as a flowchart when looking at the disassembly. At certain points, comparisons and checks may be made and flow of execution may divert down different diverging paths. By and large, loops aside, code proceeds in a linear manner when executed. This aids the reverse engineer - the flow of code execution can be traced quite simply using such a method.

Control-flow flattening aims to remove this indicator.

There are a few types of ways this can be done, and this link here covers them quite succintly. In brief, control flow flattening redirects the flow of execution using either switch statements, goto statements, indirect gotos through a jump table or through a table of function pointers.

In the interest of maximum fun, I chose the indirect function pointers (of course).

Undo-ing the Stomp

As always, our trusty file command:

❯ file chall
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=596999c6c592c91f08e0cd5350638e6436d5597d, for GNU/Linux 3.2.0, stripped

Stripped binaries are annoying but we'll manage. Since it's a standard ELF, we throw it into our disassembler of choice (IDA's mine).

Let's also run the binary once, just to see what it does.

❯ ./chall
Welcome back! Enter your password.
Hello
Incorrect! No hacking allowed.

So, it's a flagchecker then. Time for some static analysis.

Here's C pseudocode, annotated for brevity.

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char *v3; // rbp
  int v4; // eax
  __m128i si128; // xmm0
  __m128i v6; // xmm1
  __int16 v7; // dx
  __m128i v8; // xmm0
  unsigned int v9; // r12d
  int v11; // [rsp+4h] [rbp-C4h] BYREF
  int v12; // [rsp+Bh] [rbp-BDh] BYREF
  char v13; // [rsp+Fh] [rbp-B9h] BYREF
  __int128 s2[3]; // [rsp+10h] [rbp-B8h] BYREF
  __int64 v15; // [rsp+40h] [rbp-88h]
  __int16 v16; // [rsp+48h] [rbp-80h]
  char v17; // [rsp+4Ah] [rbp-7Eh]
  char s[16]; // [rsp+50h] [rbp-78h] BYREF
  __m128i v19; // [rsp+60h] [rbp-68h] BYREF
  __m128i v20; // [rsp+70h] [rbp-58h]
  __m128i v21; // [rsp+80h] [rbp-48h] BYREF

  v15 = 0xB1FB75EFA4D8168DLL;
  s2[0] = (__int128)_mm_load_si128((const __m128i *)&xmmword_2530);
  v3 = (char *)&v12;
  v12 = 50528258;
  s2[1] = (__int128)_mm_load_si128((const __m128i *)&xmmword_2540);
  v13 = 0;
  v16 = 8142;
  v17 = 40;
  s2[2] = (__int128)_mm_load_si128((const __m128i *)&xmmword_2550);
  do
  {
    v4 = (unsigned __int8)*v3;
    *(_QWORD *)&s[8] = &v11;
    v19.m128i_i64[1] = 1LL;
    v11 = v4;
    sub_1390(s, a2, a3);
    while ( v19.m128i_i64[1] <= 3uLL )
      ((void (__fastcall *)(char *, char **, char **))off_3DC0[v19.m128i_i64[1] - 1])(s, a2, a3);
    *v3++ = v19.m128i_i8[0] + 68;
  }
  while ( v3 != &v13 );
  puts("Welcome back! Enter your password.");
  fgets(s, 64, stdin);
  if ( strlen(s) == 60 )
  {
    sub_1470(&v12, 4LL, s, 59LL);
    si128 = _mm_load_si128((const __m128i *)&xmmword_2510);
    v6 = _mm_load_si128((const __m128i *)&xmmword_2520);
    *(__m128i *)s = _mm_add_epi8(_mm_xor_si128(_mm_load_si128((const __m128i *)s), si128), v6);
    LOBYTE(v7) = (v21.m128i_i8[8] ^ 0x69) + 43;
    v19 = _mm_add_epi8(_mm_xor_si128(_mm_load_si128(&v19), si128), v6);
    v20 = _mm_add_epi8(_mm_xor_si128(si128, v20), v6);
    HIBYTE(v7) = (v21.m128i_i8[9] ^ 0x69) + 43;
    v8 = _mm_loadl_epi64(&v21);
    v21.m128i_i16[4] = v7;
    v21.m128i_i8[10] = (v21.m128i_i8[10] ^ 0x69) + 43;
    v21.m128i_i64[0] = _mm_add_epi8(
                         _mm_xor_si128(v8, _mm_loadl_epi64((const __m128i *)&xmmword_2510)),
                         _mm_loadl_epi64((const __m128i *)&xmmword_2520)).m128i_u64[0];
    v9 = memcmp(s, s2, 0x3BuLL);
    if ( v9 )
    {
      v9 = -1;
      puts(
        "okay, kid im done. i doubt you even have basic knowlege of hacking. i doul boot linux so i can run my scripts. y"
        "ou made a big mistake of replying to my comment without using a proxy, because i'm already tracking youre ip. si"
        "nce ur so hacking iliterate, that means internet protocol. once i find your ip i can easily install a backdoor t"
        "rojan into your pc, not to mention your email will be in my hands. dont even bother turning off your pc, because"
        " i can rout malware into your power system so i can turn your excuse of a computer on at any time. it might be a"
        " good time to cancel your credit card since ill have that too. if i wanted i could release your home information"
        " onto my secure irc chat and maybe if your unlucky someone will come knocking at your door. id highly suggest yo"
        "u take your little comment about me back since i am no script kiddie. i know java and c++ fluently and make my o"
        "wn scripts and source code. because im a nice guy ill give you a chance to take it back. you have 4 hours in uni"
        "x time, clock is ticking. ill let you know when the time is up by sending you an email to [redacted] which I aqu"
        "ired with a java program i just wrote. see you then :)");
    }
    else
    {
      puts("Congratulations! There's your flag.");
    }
  }
  else
  {
    v9 = -1;
    puts("Incorrect! No hacking allowed.");
  }
  return v9;
}

At a first glance, some things are quite apparent:

  • An input of size 60 is expected.

  • Some data is loaded using XMM instructions to stack variables

  • A while loop

Last updated