having fun with exploit 200 ctf 2013

June 13, 2013

This post will be more about explorint techniquies rather that reaching direct goals. It’s about the binary expl200 (SHA1: 873060b493f8627ec598dc84352064f49d005116) yurket gave me the other day.

File itself.

So far, we have expl200 with SUID-root vilnerable binary to get local root.

Before running it let’s look at it in some disassembler. I’m a bit hardcore, thus used objdump -D -R -r expl200.

It’s a typical C-written binary. I’ve annotated output a bit in comments. We are interested in 2 parts: main function and cycle data.

0000000000400614 <main>:
400614:       55                      push   %rbp
400615:       48 89 e5                mov    %rsp,%rbp
400618:       48 81 ec 20 02 00 00    sub    $0x220,%rsp           ; alloc 544 bytes on stack (%rbp points to bottom, %rsp to top), of which:
40061f:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
400626:       00 00 
400628:       48 89 45 f8             mov    %rax,-0x8(%rbp)       ; store '-fstack-protector' 8-byte canary, 536 stack bytes left
40062c:       31 c0                   xor    %eax,%eax             ; cleanse canary (otherwise it's broad infoleak)
40062e:       eb 01                   jmp    400631 <main+0x1d>    ;     GOTO again:
400630:       90                      nop                          ;   again_:
400631:       48 8d b5 f0 fd ff ff    lea    -0x210(%rbp),%rsi     ;   again: char buffer[512]; (+8 bytes unused); 16 bytes of stack left
400638:       b8 00 00 00 00          mov    $0x0,%eax             ;
40063d:       ba 40 00 00 00          mov    $0x40,%edx            ;
400642:       48 89 f7                mov    %rsi,%rdi             ;
400645:       48 89 d1                mov    %rdx,%rcx             ;    __builtin_memset (buffer, 0, sizeof (buffer)); just a memset, but not from libc
400648:       f3 48 ab                rep stos %rax,%es:(%rdi)
40064b:       48 8b 05 e6 09 20 00    mov    0x2009e6(%rip),%rax        # 601038 <__bss_start>
400652:       48 89 c2                mov    %rax,%rdx             ; arg1 = 'stdin@@GLIBC_2.2.5'
400655:       48 8d 85 f0 fd ff ff    lea    -0x210(%rbp),%rax     ; arg2 = 'buffer'
40065c:       be ff 01 00 00          mov    $0x1ff,%esi           ; arg3 = 511
400661:       48 89 c7                mov    %rax,%rdi
400664:       e8 a7 fe ff ff          callq  400510 <fgets@plt>    ;   fgets (stdin, buffer, 511);
400669:       48 8d 85 f0 fd ff ff    lea    -0x210(%rbp),%rax
400670:       0f b6 00                movzbl (%rax),%eax
400673:       84 c0                   test   %al,%al
400675:       75 0a                   jne    400681 <main+0x6d>    ;   if (buffer[0] != '\0') goto fine;
400677:       bf ff ff ff ff          mov    $0xffffffff,%edi
40067c:       e8 9f fe ff ff          callq  400520 <exit@plt>     ;   exit (-1);
400681:       48 8d 85 f0 fd ff ff    lea    -0x210(%rbp),%rax     ;   fine: arg1 = 'buffer'
400688:       48 89 c7                mov    %rax,%rdi
40068b:       b8 00 00 00 00          mov    $0x0,%eax
400690:       e8 5b fe ff ff          callq  4004f0 <printf@plt>   ;   printf (buffer); GAH!
400695:       8b 05 b5 09 20 00       mov    0x2009b5(%rip),%eax        # 601050 <cycle>
40069b:       25 ff ff 00 00          and    $0xffff,%eax
4006a0:       89 85 e8 fd ff ff       mov    %eax,-0x218(%rbp)     ;   int lo16 = cycle & 0xFFFF; low half; 12 bytes on stack left
4006a6:       8b 05 a4 09 20 00       mov    0x2009a4(%rip),%eax        # 601050 <cycle>
4006ac:       c1 e8 10                shr    $0x10,%eax
4006af:       89 85 ec fd ff ff       mov    %eax,-0x214(%rbp)     ;   int hi16 = (uint32_t)(cycle >> 16); high half; 8 bytes on stack left
4006b5:       8b 85 e8 fd ff ff       mov    -0x218(%rbp),%eax     ;   uint32_t v1 = lo16;
4006bb:       89 c2                   mov    %eax,%edx
4006bd:       0f af 95 e8 fd ff ff    imul   -0x218(%rbp),%edx
4006c4:       8b 85 ec fd ff ff       mov    -0x214(%rbp),%eax     ;   uint32_t v2 = hi16;
4006ca:       0f af 85 ec fd ff ff    imul   -0x214(%rbp),%eax
4006d1:       6b c0 e3                imul   $0xffffffe3,%eax,%eax
4006d4:       01 d0                   add    %edx,%eax
4006d6:       83 f8 01                cmp    $0x1,%eax             ;  if (v1*v1 - 29 * v2 * v2 != 1)
4006d9:       0f 85 51 ff ff ff       jne    400630 <main+0x1c>    ;      goto again_;
4006df:       bf dc 07 40 00          mov    $0x4007dc,%edi
4006e4:       e8 f7 fd ff ff          callq  4004e0 <system@plt>   ;  system ("exec /bin/sh"); aha! :]
4006e9:       e9 42 ff ff ff          jmpq   400630 <main+0x1c>    ;      goto again_;
4006ee:       90                      nop
4006ef:       90                      nop
...
0000000000601050 <cycle>:; int32_t cycle
...
0000000000601038 <stdin@@GLIBC_2.2.5>:
...
601038: R_X86_64_COPY   stdin

Once again, (a bit cleaned) program looks like that:

#include <stdio.h>
#include <stdlib.h>
//#include <string.h> /* memset */
int cycle = 0;
int main()
{
    int lo, hi;
    char buff[512];
    do
    {
        __builtin_memset (buff, 0, 512); /* force rep: stos even on unoptimized builds */
        fgets (buff, sizeof(buff) - 1, stdin);
        if (buff[0] == '\0')
            exit (-1);
        printf (buff);
        lo = cycle & 0xFFFF;
        hi = cycle >> 16;
    } while (lo*lo - 29*hi*hi != 1);
    system ("exec /bin/sh");
    return 0;
}

First, it tries to solve equation of form “x^2 - 29*y^2 == 1”. It’s so-called Pell’s equation, which is common problem to solve in PE tasks.

Some of their first solutions:

*Euler> solve_pell 29 PlusOne
(x,y):
(9801,1820)
(192119201,35675640)
(3765920568201,699313893460)
(73819574785756801,13707950903927280)
(1447011301184484245001,268703252919468649100)
(28364315451998685384752801,5267121150019473555730920)
(555997310043066929727440160201,103246108513978467719968844740)
(10898659243099882504518596635507201,2023830213823884774227355738862560)
(213635517927246586810506601521771993801,39671119748129680830426159473215056380)
...

The only ones that fit into 16 bits are firs two solutions: (9801,1820), or (0x2649, 0x071C). Thus one of solutions is to put 0x071C2649 value into ’cycle’global varioable.

The straightforward way is to exploit uncontrolled ‘printf’ argument vulnerability. Most of arguments in format string effect memory read accesses, but not writes. For example:

printf ("%s%i%d", whatever...);
// can only read from memory coded in 'whatever'

But:

printf ("123%n456%n", pi1, pi2);
// will effect in *(int*)pi1 = 3; *(int*)pi2 = 6;

Or even better:

printf ("123%hn456%hn", pi1, pi2);
// will effect in *(short*)pi1 = 3; *(short)pi2 = 6;

Thus we have mechanism of writing random 2-byte values into memory locations.

Now, the interesting part: how to control ‘printf’ arguments?

Let’s look at x86_64 C function call ABI first.

The following example

#include <stdio.h>
void test()
{
    printf ("", 2,3,4,5,6,7,8,9,10);
}

gets assembled into (gcc -fomit-frame-pointer -S arg_test.c -o arg_test.S)

.section        .rodata
.LC0:
.string ""
.text
test:
subq    $40, %rsp     ; reserve 8 * 8 on stack. always-16-byte stack alignment goes from SSE requirement.
movl    $10, 24(%rsp) ; 10:   , go on stack
movl    $9, 16(%rsp)  ;  9:  , all
movl    $8, 8(%rsp)   ;  8: , arguments
movl    $7, (%rsp)    ;  7: starting from 7-th
movl    $6, %r9d
movl    $5, %r8d
movl    $4, %ecx
movl    $3, %edx
movl    $2, %esi
movl    $.LC0, %edi
movl    $0, %eax
call    printf
addq    $40, %rsp
ret

Thus in order to get access to our stack we need to throw out (or use wisely) values situated in registers.

It’s time to look at the stack and register layout right before printf call (eip=0x400690) moment a bit closer:

%rsp+000h(or %rbp-0x220): [ uninitialized value, created, but not used by our program. can be handy! ]
%rsp+0008(or %rbp-0x218): [ pair of values: int lo16; int hi16; ]
%rsp+010h(or %rbp-0x210): [ buffer, 512 bytes ]
%rsp+210h(or %rbp-0x010): [ 8 bytes unused ]
%rsp+218h(or %rbp-0x008): [ stack protector canary ]
%rsp+220h(or %rbp-0x000): [ 8 bytes unused ]
%rsp+228h(or %rbp+0x008): [ previous frame (%rbp) ]
...
registers are in the state left from fgets() call.
Can be handy as well, but I don't care for now.

And our printf call will look like that:

printf ( %rdi = %rbp-210h /*aka buffer*/,
       , %rsi /* left from fgets */
       , %rdx /* left from fgets */
       , %rcx /* left from fgets */
       , %r8  /* left from fgets */
       , %r9  /* left from fgets */
       , %rbp-0x220
       , %rbp-0x218
       , %rbp-0x210 /* aha, 'buffer' met once again! */
       , %rbp-0x208
       ...
       );

Our goal is to control format string the way we could inject nice bits there,

Suppose, we pass the followint thing as a buffer:

printf ("%c%c%c%c%c%c%c%c\n" /* 8 times */ "%p");

It will show us value for ‘%rbp-0x210’. Program won’t crashm thus we will be able to pass it another format string with achieved info.

To be continued :]