Skip to content

Instantly share code, notes, and snippets.

@cwgreene
Created June 15, 2020 06:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cwgreene/5253cfa3904871535126b34ebbcb050f to your computer and use it in GitHub Desktop.
Save cwgreene/5253cfa3904871535126b34ebbcb050f to your computer and use it in GitHub Desktop.

Defenit-2020 Mom's Touch

We download the binary and open it in ghidra.

void entry(void)

{
  __libc_start_main(FUN_08048840);
  do {
                    /* WARNING: Do nothing block with infinite loop */
  } while( true );
}

This is actually not quite right; but we'll come back to that later. Proceeding to main we have

undefined4 FUN_08048840(void)

{
  char cVar1;
  ssize_t sVar2;
  size_t len;
  char *local_1c;
  char *__s;
  
  FUN_080486b0();
  puts("Mom Give Me The FLAG!");
  __s = (char *)malloc(100);
  if (__s == (char *)0x0) {
    __s = "[*]Error : malloc()";
  }
  else {
    sVar2 = read(0,__s,100);
    if (-1 < sVar2) {
      if (__s[sVar2 + -1] == '\n') {
        __s[sVar2 + -1] = '\0';
      }
      len = strlen(__s);
      if (len == 0x49) {
        cVar1 = mainfunc(__s);
        if (cVar1 == '\0') {
          local_1c = "Try Again..";
        }
        else {
          local_1c = "Correct! Mom! Input is FLAG!";
        }
        puts(local_1c);
        free(__s);
        return 0;
      }
      puts("Mom, Check the legnth..");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    __s = "[*]Error : read()";
  }
  perror(__s);
                    /* WARNING: Subroutine does not return */
  exit(-1);
}

So we read the input, which should be 0x49 characters long, and then pass it against our verifier function mainfunc.

undefined4 mainfunc(char *param_1)

{
  uint uVar1;
  int iVar2;
  uint uVar3;
  int iVar4;
  
  iVar4 = 0;
  do {
    uVar1 = (&DAT_080492ac)[iVar4];
    iVar2 = rand();
    uVar3 = iVar2 + ((iVar2 / 0xff + (iVar2 >> 0x1f)) - (int)((longlong)iVar2 * 0x80808081 >> 0x3f))
                    * -0xff;
    if (((int)param_1[iVar4] ^ (&DAT_080492ac)[(uVar1 >> 4 | uVar1 << 4) & 0xff] ^
        (&DAT_080492ac)[(uVar3 >> 2 | uVar3 * 4) & 0xff]) != (&DAT_08049144)[iVar4]) {
      return 0;
    }
    iVar4 = iVar4 + 1;
  } while (iVar4 < 0x49);
  return 1;
}

So, we have two arrays, one, DAT_080492ac, which gets transformed with rand() and then xor'd with the input, and then gets comapred to the second array DAT_08049144. By simple arithmetic this means

X = (&DAT_080492ac)[(uVar1 >> 4 | uVar1 << 4) & 0xff] ^
        (&DAT_080492ac)[(uVar3 >> 2 | uVar3 * 4) & 0xff])
param_1[i] = (&DAT_08049144)[iVar4]) ^ X 

Visiting those array locations in Ghidra, however, reveals zero arrays. Strange, but no matter, we can break point in this function and read the values directly out from memory using gdb. So something like this

    for(int i = 0; i < 73;i++) {
        int chr = array1[i];
        int rnd =rand();
        int uVar1 = rnd + ((rnd / 0xff + (rnd >> 0x1f)) - (int)((long long)rnd * 0x80808081 >> 0x3f)) * -0xff;
        int d = array1[(chr >> 4 | chr << 4) & 0xff] ^ array1[(uVar1 >> 2 | uVar1 * 4) & 0xff] ^array2[i];
        printf("%c",d);
    }

Since srand() isn't being called, we can just call rand() directly and, since it's deterministic, we will generate the same values. (This also answers the mystery of how this is guaranteed to work, and not just sometimes).

However, this doesn't work. srand() is in fact being called. And we can find it using ghidra's XREFS. Which reveals this function.

/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */

void _INIT_1(void)

{
  int iVar1;
  int iVar2;
  
  srand(0xff);
  _DAT_080492a4 = rand();
  _DAT_080492a4 =
       _DAT_080492a4 +
       ((_DAT_080492a4 / 0xff + (_DAT_080492a4 >> 0x1f)) -
       (int)((longlong)_DAT_080492a4 * 0x80808081 >> 0x3f)) * -0xff;
  iVar2 = -0x400;
  do {
    iVar1 = rand();
    *(int *)(iVar2 + 0x80496ac) =
         iVar1 + ((iVar1 / 0xff + (iVar1 >> 0x1f)) - (int)((longlong)iVar1 * 0x80808081 >> 0x3f)) *
                 -0xff;
    iVar2 = iVar2 + 4;
  } while (iVar2 != 0);
  return;
}

Ah! So that's how the arrays are getting populated! So we have additional calls to rand here, plus an initialization using srand. We'll need to add these calls to our deobfuscator.

int main() {
    srand(0xff);
    rand();
    for(int j = 0; j < 256; j++) {
        rand(); 
    }
    for(int i = 0; i < 73;i++) {
        int chr = array1[i];
        int rnd =rand();
        int uVar1 = rnd + ((rnd / 0xff + (rnd >> 0x1f)) - (int)((long long)rnd * 0x80808081 >> 0x3f)) * -0xff;
        int d = array1[(chr >> 4 | chr << 4) & 0xff] ^ array1[(uVar1 >> 2 | uVar1 * 4) & 0xff] ^array2[i];
        printf("%c",d);
    }
}

This spits out

Defenit{ea40d42bfaf7d1f599abf284a35c535c607ccadbff38f7c39d6d57e238c4425e}

Huzzah! But wait... where did INIT_1 get called?

The Land Before main

Let's revisit the entry point.

void entry(void)

{
  __libc_start_main(FUN_08048840);
  do {
                    /* WARNING: Do nothing block with infinite loop */
  } while( true );
}

__libc_start_main signature is wrong! It should be

int __libc_start_main(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end));

The first argument should be main, followed by argc, and then argv! That should then be followed by a function pointer, followed by another function pointer.

Looking at the disassembly we sometimes

        0804859b 68 70 89        PUSH       FUN_08048970
                 04 08
        080485a0 68 10 89        PUSH       FUN_08048910
                 04 08
        080485a5 51              PUSH       ECX
        080485a6 56              PUSH       ESI
        080485a7 68 40 88        PUSH       FUN_08048840
                 04 08
        080485ac e8 9f ff        CALL       __libc_start_main                                undefined __libc_start_main()
                 ff ff

We find the init function is this:

void FUN_08048910(undefined4 param_1,undefined4 param_2,undefined4 param_3)

{
  int iVar1;
  
  _DT_INIT();
  iVar1 = 0;
  do {
    (*(code *)(&__DT_INIT_ARRAY)[iVar1])(param_1,param_2,param_3);
    iVar1 = iVar1 + 1;
  } while (iVar1 != 2);
  return;
}

This is actually the libc_csu_init function. It gets called prior to invoking main (we can prove this using gdb, just set a breakpoint).

Visiting the __DT_INIT_ARRAY we find

                             //
                             // .init_array 
                             // SHT_INIT_ARRAY  [0x8049000 - 0x8049007]
                             // ram: 08049000-08049007
                             //
                             __DT_INIT_ARRAY                                 XREF[5]:     0804809c(*), 
                                                                                          FUN_08048910:08048931(*), 
                                                                                          FUN_08048910:08048954(R), 
                                                                                          0804902c(*), 
                                                                                          _elfSectionHeaders::000002dc(*)  
        08049000 60 86 04 08     ddw        _INIT_0
                             DWORD_08049004                                  XREF[1]:     FUN_08048910:08048954(R)  
        08049004 30 87 04 08     ddw        _INIT_1

And there's the init function! So libc_csu_init is calling everything in the array, in particular, it calls _INIT_1 (INIT_0 btw, is boring and does nothing, I'm not sure why it's there).

In C++, one of these functions will call all the static initializers for global and static objects, so this is actually good to know about.

So why did Ghidra botch the function definition for __libc_start_main? That's a story for another time (namely, once I figure it out);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment