mercredi 22 février 2017

Stepping backward in gdb

0/ Intro

Yesterday, I discover an awesome feature of gdb: the concept of recording, and stepping backward in a binary.

You read it good! With gdb, you can run a binary step some instructions and then reverse your steps. You can even change values in memory or register, and continue execution.
It's a feature I've searched for solving crackmes. Usually, in crackme you try some passwords, step through functions and subfunctions, then branches are taken depending on the password, and sometimes, you just want to step backward in order to try another branch.
I've never imagined that it's possible with a vanilla gdb.

Reference doc:

1/ A heavy weight crackme!

Here is an example of a crackme:

 mitsurugi@dojo:~/chall/reverseGDB$ cat heavyweightcrackme.c   
 #include <stdio.h>  
 #include <stdlib.h>  
 #include <string.h>  
 int heavycalc(char *s) {  
      int a=0;  
      printf("\tBig computation in subfunction\n");  
      return a;  
 int main(int argc, char *argv[]) {  
      printf("Another Crackme\n");  
      if (heavycalc(argv[1]) == 6){  
           printf("Good Boy\n");  
      else {  
           printf("Bad boy\n");  

Nothing really hard, that's just for the demonstration.
The reverser have to input a 6 character long password in order to get the "Good boy" message. Any other length will tell "Bad Boy". The calculation is made in a subfunction.

2/ Reverse stepping GDB in action!

Just use your favorite gdb (higher than v7).

 mitsurugi@dojo:~/chall/reverseGDB$ gdb -q -nx heavyweightcrackme   
 Reading symbols from heavyweightcrackme...(no debugging symbols found)...done.  
 (gdb) b * main  
 Breakpoint 1 at 0x723  
 (gdb) r aaaa  
 Starting program: /home/mitsurugi/chall/reverseGDB/heavyweightcrackme aaaa  
 Breakpoint 1, 0x0000555555554723 in main ()  
 (gdb) record  

Here, we start the recording. Everything will be recorded, memory, breakpoints, registers, flags and so on. Gdb will be able to step backward and forward since that point.

 (gdb) c  
 Another Crackme  
      Big computation in subfunction  
 Bad boy  
 The next instruction is syscall exit_group. It will make the program exit. Do you want to stop the program?([y] or n) yes  
 Process record: inferior program stopped.  
 Program stopped.  
 0x00007ffff7af34c6 in __GI__exit (status=status@entry=0) at ../sysdeps/unix/sysv/linux/_exit.c:31  
 31     ../sysdeps/unix/sysv/linux/_exit.c: Aucun fichier ou dossier de ce type.  

Ok, we fail at solving this challenge, and we see the "Bad boy" message. (Protip: when gdb ask you to stop the program, answer yes, you will stay in the recorded session).
The magic begins here, with a reverse-continue. It will fast forward until a breakpoint. We have only one, at main.
 (gdb) reverse-continue   
 No more reverse-execution history.  
 0x0000555555554723 in main ()   
 (gdb) disass main  
 Dump of assembler code for function main:  
 => 0x0000555555554723 <+0>:     push  %rbp  
   0x0000555555554724 <+1>:     mov  %rsp,%rbp  
   0x0000555555554727 <+4>:     sub  $0x10,%rsp  
   0x000055555555472b <+8>:     mov  %edi,-0x4(%rbp)  
   0x000055555555472e <+11>:     mov  %rsi,-0x10(%rbp)  
   0x0000555555554732 <+15>:     lea  0xef(%rip),%rdi    # 0x555555554828  
   0x0000555555554739 <+22>:     callq 0x555555554590 <puts@plt>  
   0x000055555555473e <+27>:     mov  -0x10(%rbp),%rax  
   0x0000555555554742 <+31>:     add  $0x8,%rax  
   0x0000555555554746 <+35>:     mov  (%rax),%rax  
   0x0000555555554749 <+38>:     mov  %rax,%rdi  
   0x000055555555474c <+41>:     callq 0x5555555546f0 <heavycalc>    //Seems interesting  
   0x0000555555554751 <+46>:     cmp  $0x6,%eax  
   0x0000555555554754 <+49>:     jne  0x555555554764 <main+65>  
   0x0000555555554756 <+51>:     lea  0xdb(%rip),%rdi    # 0x555555554838  
   0x000055555555475d <+58>:     callq 0x555555554590 <puts@plt>  
   0x0000555555554762 <+63>:     jmp  0x555555554770 <main+77>  
   0x0000555555554764 <+65>:     lea  0xd6(%rip),%rdi    # 0x555555554841  
   0x000055555555476b <+72>:     callq 0x555555554590 <puts@plt>  
   0x0000555555554770 <+77>:     mov  $0x0,%eax  
   0x0000555555554775 <+82>:     leaveq   
   0x0000555555554776 <+83>:     retq    
 End of assembler dump.  

From now on, we can single step through the binary, examinate memory, add breakpoints (yeah, dynamically!). The heavycalc function seemes interesting because it's return value is checked.
 (gdb) b * 0x000055555555474c  
 Breakpoint 2 at 0x55555555474c  
 (gdb) c  
 Breakpoint 2, 0x000055555555474c in main ()  
 (gdb) nexti  
 0x0000555555554751 in main ()  
 (gdb) info reg rax  
 rax      0x4     4  

And we can change the execution flow. We see that the rax is compared to six. What happens if we set a 6?Obviously, from now on, the subsequent execution log is deleted and a new execution log starting from the current address will be recorded. This means we will abandon the previously recorded "future" and begin recording a new "future".
 (gdb) set $rax=6  
 Because GDB is in replay mode, changing the value of a register will make the 
execution log unusable from this point onward. Change register rax?(y or n) y 
 (gdb) c  
 Good Boy  
 The next instruction is syscall exit_group. It will make the program exit. 
Do you want to stop the program?([y] or n) yes  
 Process record: inferior program stopped.  
 Program stopped.  
 0x00007ffff7af34c6 in __GI__exit (status=status@entry=0) at ../sysdeps/unix/sysv/linux/_exit.c:31  
 31     in ../sysdeps/unix/sysv/linux/_exit.c  

Yeah! We have a "Good boy" message. So, it means that heavycalc have to return 6 in order to win. But what do we have in "heavycalc"? Easy, just step backward, and dive into this function:
 (gdb) reverse-continue   
 Breakpoint 2, 0x000055555555474c in main ()  
 (gdb) x/10i $rip  
 => 0x55555555474c <main+41>:     callq 0x5555555546f0 <heavycalc>  
   0x555555554751 <main+46>:     cmp  $0x6,%eax  
   0x555555554754 <main+49>:     jne  0x555555554764 <main+65>  
   0x555555554756 <main+51>:     lea  0xdb(%rip),%rdi    # 0x555555554838  
   0x55555555475d <main+58>:     callq 0x555555554590 <puts@plt>  
   0x555555554762 <main+63>:     jmp  0x555555554770 <main+77>  
   0x555555554764 <main+65>:     lea  0xd6(%rip),%rdi    # 0x555555554841  
   0x55555555476b <main+72>:     callq 0x555555554590 <puts@plt>  
   0x555555554770 <main+77>:     mov  $0x0,%eax  
   0x555555554775 <main+82>:     leaveq   
 (gdb) stepi  
 0x00005555555546f0 in heavycalc ()  
 (gdb) disass heavycalc   
 Dump of assembler code for function heavycalc:  
 => 0x00005555555546f0 <+0>:     push  %rbp  
   0x00005555555546f1 <+1>:     mov  %rsp,%rbp  
   0x00005555555546f4 <+4>:     sub  $0x20,%rsp  
   0x00005555555546f8 <+8>:     mov  %rdi,-0x18(%rbp)  
   0x00005555555546fc <+12>:     movl  $0x0,-0x4(%rbp)  
   0x0000555555554703 <+19>:     lea  0xfe(%rip),%rdi    # 0x555555554808  
   0x000055555555470a <+26>:     callq 0x555555554590 <puts@plt>  
   0x000055555555470f <+31>:     mov  -0x18(%rbp),%rax  
   0x0000555555554713 <+35>:     mov  %rax,%rdi  
   0x0000555555554716 <+38>:     callq 0x5555555545a0 <strlen@plt>  
   0x000055555555471b <+43>:     mov  %eax,-0x4(%rbp)  
   0x000055555555471e <+46>:     mov  -0x4(%rbp),%eax  
   0x0000555555554721 <+49>:     leaveq   
   0x0000555555554722 <+50>:     retq    
 End of assembler dump.  

And we can inspect memory again, etc, etc..

3/ Outro

This feature is AWESOME \o/
The concept of stepping backward, forward, inspecting memory and so on is full of fun. My next cracking session will be recorded, replayed and enhanced.
The gdb documentation gives a lot of options, actions and possibilities, this blogpost is just here to show a minimal reverseGDB sessions.

And the irony of reversing gdb while reversing binaries strikes on me :)

Winners trains. Loosers complains.

Aucun commentaire:

Enregistrer un commentaire