Plaid CTF 2019

Let's take a look at the binary:

$    file icancount
icancount: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=e75719f2cd90c042f04af29a0cd1263bb72c7417, not stripped
$    pwn checksec icancount
[*] '/Hackery/pod/modules/angr/plaid19_icancount/icancount'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./icancount
We're going to count numbers, starting from one and
counting all the way up to the flag!
Are you ready? Go!
> 15935728
No, the correct number is 1.
But I believe in you. Let's try again sometime!
$    ./icancount
We're going to count numbers, starting from one and
counting all the way up to the flag!
Are you ready? Go!
> 1
Correct.
> 2
Yes.
> 3
Yes!
> 4
Congratz
> 5
Yep!
> 6
Right-o.
> 7
Wonderful.
> 8^C

So we can see that we are dealing with a 32 bit binary with PIE. When we run it, it prompts us for numbers that increments by 1. When we take a look at the main function in Ghidra, we see this:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void main(void)

{
  uint __seed;
  size_t len;
  size_t sVar1;
  int iVar2;
  char *compliment;
  char input [31];
 
  __seed = time((time_t *)0x0);
  srand(__seed);
  puts("We\'re going to count numbers, starting from one and");
  puts("counting all the way up to the flag!");
  puts("Are you ready? Go!");
  while( true ) {
    incr_flag();
    printf("> ");
    fflush(stdout);
    fgets(input + 1,0x1e,stdin);
    if (input[1] != '\0') {
      len = strlen(input + 1);
      if (input[len] < ' ') {
        sVar1 = strlen(input + 1);
        input[sVar1] = '\0';
      }
    }
    iVar2 = strcmp(input + 1,flag_buf);
    if (iVar2 != 0) break;
    compliment = (char *)get_compliment();
    puts(compliment);
    check_flag();
  }
  printf("No, the correct number is %s.\n",flag_buf);
  puts("But I believe in you. Let\'s try again sometime!");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

So we can see that it prints out some text, sets the rng seed to time, then drops us into an infinite loop. The loop starts off by running the incr_flag function which we can see it increments flag_buf which is stored in the bss at address 0x13048:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void incr_flag(void)

{
  size_t sVar1;
  size_t local_10;
 
  local_10 = strlen(flag_buf);
  while( true ) {
    if ((int)local_10 < 1) {
      sVar1 = strlen(flag_buf);
      if (sVar1 != 0x13) {
        sVar1 = strlen(flag_buf);
        flag_buf[sVar1] = 0x30;
        flag_buf[0] = 0x31;
        return;
      }
                    /* WARNING: Subroutine does not return */
      exit(2);
    }
    if (*(char *)((int)&__dso_handle + local_10 + 3) != '9') break;
    *(undefined *)((int)&__dso_handle + local_10 + 3) = 0x30;
    local_10 = local_10 - 1;
  }
  *(char *)((int)&__dso_handle + local_10 + 3) =
       *(char *)((int)&__dso_handle + local_10 + 3) + '\x01';
  return;
}

A couple of things from this, first if we weren't sure before we can see that flag_bug is only filled with the bytes between 0x30-0x39 (ASCII 0-9). In addition to that, since if the length of flag_buf exceeds 19 (0x13) the program exits, our input is probably 19 characters long (and only consists of ASCII characters between 0-9).

Proceeding that in the main function, we see that it allows us to scan in 0x1e bytes into the stack char array input. It then checks if the last character in our inputted string has a value less than 0x20 (which corresponds to the space ' ' character). If it does, then that character is swapped out with a null byte.

Following that, it compares our input against flag_buf. If they are not equal, the infinite loop breaks and we get told what the correct number should be. If it doesn't break, then it will print a random character and run the check_flag function which looks like this:

void check_flag(void)

{
  longlong lVar1;
  uint b;
  uint x;
  uint y;
  uint z;
  uint uVar2;
  int unaff_ESI;
  ulonglong a;
  ulonglong c;
  ulonglong d;
  ulonglong uVar3;
  ulonglong uVar4;
  ulonglong e;
  ulonglong g;
  ulonglong uVar5;
  longlong f;
  int i;
  char inputChar;
 
  __x86.get_pc_thunk.si();
  i = 0;
  while( true ) {
    if (0x13 < i) {
      printf((char *)(unaff_ESI + 0x93c),unaff_ESI + 0x25f2);
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    inputChar = *(char *)(i + unaff_ESI + 0x25f2);
    x = (int)inputChar & 3;
    y = (int)(inputChar >> 2) & 3;
    z = (int)(inputChar >> 4) & 0xf;
    a = rol(x + 0xa55aa559,(uint)(0x5aa55aa6 < x) + 0xa55a,2);
    b = y - (uint)a;
    c = rol(b + 0xa55aa559,
            (-(uint)(y < (uint)a) - (int)(a >> 0x20)) + 0xa55a + (uint)(0x5aa55aa6 < b),0xd);
    c._4_4_ = (uint)(c >> 0x20);
    c._0_4_ = (uint)c;
    d = rol((z - (uint)c) + 0xa55aa559,
            (-(uint)(z < (uint)c) - c._4_4_) + 0xa55a + (uint)(0x5aa55aa6 < z - (uint)c),0x11);
    d._4_4_ = (uint)(d >> 0x20);
    uVar5 = c ^ a ^ d;
    lVar1 = a + CONCAT44((uint)((d & uVar5) >> 0x20) | ~(uint)(uVar5 >> 0x20) & c._4_4_,
                         (uint)(d & uVar5) | ~(uint)uVar5 & (uint)c);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = z + (uint)c;
    uVar3 = rol(c._4_4_ + 0xf01f83c6,
                (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(z,(uint)c) + 0xf +
                (uint)(0xfe07c39 < c._4_4_),3);
    uVar2 = (uint)(uVar3 >> 0x20);
    lVar1 = c + CONCAT44((uint)((uVar3 & uVar5) >> 0x20) | ~uVar2 & d._4_4_,
                         (uint)(uVar3 & uVar5) | ~(uint)uVar3 & (uint)d);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = x + (uint)c;
    uVar4 = rol(c._4_4_ + 0xf01f83c6,
                (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(x,(uint)c) + 0xf +
                (uint)(0xfe07c39 < c._4_4_),0xb);
    lVar1 = uVar5 + CONCAT44((uint)((d & uVar4) >> 0x20) | ~d._4_4_ & uVar2,
                             (uint)(d & uVar4) | ~(uint)d & (uint)uVar3);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = y + (uint)c;
    e = rol(c._4_4_ + 0xf01f83c6,
            (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(y,(uint)c) + 0xf +
            (uint)(0xfe07c39 < c._4_4_),0x13);
    lVar1 = uVar3 + (e ^ d ^ uVar4);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = y + (uint)c;
    g = rol(c._4_4_ + 0x867b8ca6,
            (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(y,(uint)c) + 0xb744 +
            (uint)(0x79847359 < c._4_4_),5);
    lVar1 = d + (uVar4 ^ g ^ e);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = x + (uint)c;
    uVar5 = rol(c._4_4_ + 0x867b8ca6,
                (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(x,(uint)c) + 0xb744 +
                (uint)(0x79847359 < c._4_4_),7);
    lVar1 = e + (uVar5 ^ uVar4 ^ g);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = z + (uint)c;
    f = rol(c._4_4_ + 0x867b8ca6,
            (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(z,(uint)c) + 0xb744 +
            (uint)(0x79847359 < c._4_4_),0x17);
    lVar1 = uVar4 + uVar5 + g + f;
    c._0_4_ = (uint)lVar1 ^ (uint)((ulonglong)lVar1 >> 0x20);
    c._0_4_ = (uint)c ^ (uint)c >> 0x10;
    if (*(byte *)(i + *(int *)(unaff_ESI + 0x2692)) != (byte)((byte)(uint)c ^ (byte)((uint)c >> 8)))
    break;
    i = i + 1;
  }
  return;
}

This may seem like a mess, but we don't need to understand a lot about what's going on. We can see that the loop runs for 0x13 times (iteration count stored in i). If it runs that many times then it will call printf (probably will print the flag). Also we can see that it checks our input which is stored in inputChar at 0x10a73:

gef➤  pie b *0xa73
gef➤  pie run
Stopped due to shared library event (no libraries added or removed)
We're going to count numbers, starting from one and
counting all the way up to the flag!
Are you ready? Go!
> 1
Congratz

Breakpoint 1, 0x56555a73 in check_flag ()
[+] base address 0x56555000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x56558048  →  0x00000031 ("1"?)
$ebx   : 0x56558000  →  0x00002ef0
$ecx   : 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
$edx   : 0x56558048  →  0x00000031 ("1"?)
$esp   : 0xffffcf20  →  0x00000000
$ebp   : 0xffffd028  →  0xffffd058  →  0x00000000
$esi   : 0x56558000  →  0x00002ef0
$edi   : 0x0       
$eip   : 0x56555a73  →  <check_flag+46> movzx eax, BYTE PTR [eax]
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffcf20│+0x0000: 0x00000000     ← $esp
0xffffcf24│+0x0004: 0x00000009
0xffffcf28│+0x0008: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf2c│+0x000c: 0xf7e48dab  →  <_IO_file_write+43> add esp, 0x10
0xffffcf30│+0x0010: 0x00000001
0xffffcf34│+0x0014: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf38│+0x0018: 0x00000009
0xffffcf3c│+0x001c: 0xf7ffd000  →  0x00026f34
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0x56555a68 <check_flag+35>  lea    edx, [esi+0x48]
   0x56555a6e <check_flag+41>  mov    eax, DWORD PTR [ebp-0x1c]
   0x56555a71 <check_flag+44>  add    eax, edx
 → 0x56555a73 <check_flag+46>  movzx  eax, BYTE PTR [eax]
   0x56555a76 <check_flag+49>  mov    BYTE PTR [ebp-0x1d], al
   0x56555a79 <check_flag+52>  movsx  eax, BYTE PTR [ebp-0x1d]
   0x56555a7d <check_flag+56>  cdq    
   0x56555a7e <check_flag+57>  mov    ecx, eax
   0x56555a80 <check_flag+59>  and    ecx, 0x3
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "icancount", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x56555a73 → check_flag()
[#1] 0x56556109 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x31      
$ebx   : 0x56558000  →  0x00002ef0
$ecx   : 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
$edx   : 0x56558048  →  0x00000031 ("1"?)
$esp   : 0xffffcf20  →  0x00000000
$ebp   : 0xffffd028  →  0xffffd058  →  0x00000000
$esi   : 0x56558000  →  0x00002ef0
$edi   : 0x0       
$eip   : 0x56555a76  →  <check_flag+49> mov BYTE PTR [ebp-0x1d], al
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffcf20│+0x0000: 0x00000000     ← $esp
0xffffcf24│+0x0004: 0x00000009
0xffffcf28│+0x0008: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf2c│+0x000c: 0xf7e48dab  →  <_IO_file_write+43> add esp, 0x10
0xffffcf30│+0x0010: 0x00000001
0xffffcf34│+0x0014: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf38│+0x0018: 0x00000009
0xffffcf3c│+0x001c: 0xf7ffd000  →  0x00026f34
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0x56555a67 <check_flag+34>  add    BYTE PTR [ebp+0x4896], cl
   0x56555a6d <check_flag+40>  add    BYTE PTR [ebx-0x2ffe1bbb], cl
   0x56555a73 <check_flag+46>  movzx  eax, BYTE PTR [eax]
 → 0x56555a76 <check_flag+49>  mov    BYTE PTR [ebp-0x1d], al
   0x56555a79 <check_flag+52>  movsx  eax, BYTE PTR [ebp-0x1d]
   0x56555a7d <check_flag+56>  cdq    
   0x56555a7e <check_flag+57>  mov    ecx, eax
   0x56555a80 <check_flag+59>  and    ecx, 0x3
   0x56555a83 <check_flag+62>  mov    DWORD PTR [ebp-0x28], ecx
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "icancount", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x56555a76 → check_flag()
[#1] 0x56556109 → main()
────────────────────────────────────────────────────────────────────────────────
0x56555a76 in check_flag ()
gef➤  p $eax
$1 = 0x31

There is an if then check at the end which is ran at the very end, if the check fails the loop ends (which means we don't have the correct input):

    if (*(byte *)(i + *(int *)(unaff_ESI + 0x2692)) != (byte)((byte)(uint)c ^ (byte)((uint)c >> 8)))
    break;

So to solve this challenge, we can use Angr. We need three things, what input it takes (which we know), an instruction pointer that if it's executed the problem is solved, and an instruction pointer that if it's executed then we know we have the wrong input.

For the address that designates a failed address, in the check_flag function we see at the end there is the if then check, which if it fails it will make a jump to 0x10fae:

        00010f75 38 c2           CMP        f,f
        00010f77 75 35           JNZ        LAB_00010fae

Which we can see that at the address it just exits. Since this code path is executed when we don't have the right input, I choose to use the address 0xfae:

                             LAB_00010fae                                    XREF[1]:     00010f77(j)  
        00010fae 90              NOP
        00010faf 8d 65 f4        LEA        ESP=>local_10,[EBP + -0xc]
        00010fb2 5b              POP        EBX
        00010fb3 5e              POP        ESI
        00010fb4 5f              POP        EDI
        00010fb5 5d              POP        EBP
        00010fb6 c3              RET

Now we need the instruction address that if it's executed, it means we have the correct input. For this I choose 0xf9a since that is the printf call that has been made if the loop has ran 19 times, and it probably is printing the flag (which means that this code path is ran when we have the correct input):

        00010f98 89 f3           MOV        EBX,ESI
        00010f9a e8 b1 f6        CALL       printf                                           int printf(char * __format, ...)
                 ff ff
        00010f9f 83 c4 10        ADD        ESP,0x10
        00010fa2 83 ec 0c        SUB        ESP,0xc
        00010fa5 6a 00           PUSH       0x0
        00010fa7 89 f3           MOV        EBX,ESI
        00010fa9 e8 f2 f6        CALL       exit                                             void exit(int __status)
                 ff ff
                             -- Flow Override: CALL_RETURN (CALL_TERMINATOR)

Also one last thing about the Angr script. We will set the enter state to be the start of the check_flag function. The reason for this being is if we were to start from the beginning of the binary, we would have to essentially brute force the binary because it checks if our input is equal to flag_buf, and it is initialized at 0 and incremented by 1 each time (so we would have to brute force it by entering 0, then 1, then 2 ...). Also since it expects our input in flag_buf, we will just establish our input and set flag_buf equal to our input. With that we have everything we need for our Angr Script:

import angr
import claripy

# Establish the project

target = angr.Project('icancount', auto_load_libs=False)

# Because PIE is enabled, we have to grab the randomized addresses for various things

# Grab the address of flag_buf which stores our input
flag_buf = target.loader.find_symbol('flag_buf').rebased_addr

# Grab the address of the check_flag function which is where we will start
check_flag = target.loader.find_symbol('check_flag').rebased_addr

# Grab the instruction addresses which indicate either a success or a failure

desired_adr = 0xf9a + target.loader.main_object.min_addr
failed_adr = 0xfae + target.loader.main_object.min_addr

# Establish the entry state
entry_state = target.factory.blank_state(addr = check_flag)

# Establish our input, 0x13 bytes
inp = claripy.BVS('inp', 0x13*8)

# Assign the condition that each byte of our input must be between `0-9` (0x30 - 0x39)
for i in inp.chop(8):
    entry_state.solver.add(entry_state.solver.And(i >= '0', i <= '9'))

# Set the memory region of flag_buf equal to our input
entry_state.memory.store(flag_buf, inp)

# Establish the simulation
simulation = target.factory.simulation_manager(entry_state)

# Setup the simulation with the addresses to specify a success / failure
simulation.use_technique(angr.exploration_techniques.Explorer(find = desired_adr, avoid = failed_adr))

# Run the simulation
simulation.run()

# Parse out the solution, and print it
flag_int = simulation.found[0].solver.eval(inp)

flag = ""
for i in xrange(19):
    flag = chr(flag_int & 0xff) + flag
    flag_int = flag_int >> 8

print "flag: PCTF{" + flag + "}"

When we run it:

$	python rev.py 
WARNING | 2019-07-21 16:19:08,277 | angr.analyses.disassembly_utils | Your version of capstone does not support MIPS instruction groups.
WARNING | 2019-07-21 16:19:08,324 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
flag: PCTF{2052419606511006177}

Just like that, we captured the flag!