samedi 17 octobre 2009

GDB reverse debugging tutorial

Update : Thanks to Michael Snyder for the tip on making watchpoints work.

GDB 7.0 was released last week with a major new feature : reverse debugging. Even though some related commands are documented in the gdb manual, I have yet to find an article explaining how to use this neat feature. So here it is : my GDB reverse debugging tutorial.

If you are impatient to test it and already know gdb, here are the useful commands :
  • break main
  • run
  • record
  • continue to a certain point
  • reverse-step, reverse-next, reverse-continue, reverse-finish
First, you need to get version 7 of GDB. If like me you are using Arch Linux, then it is already available in the official repositories, else you'll need to go through the compilation process.

Next, compile your program with the -ggdb switch. This is not mandatory but will allow you to follow the program execution line by line (instead of relying on the assembly instructions). For this article, we'll use a very basic C program.

#include <stdio.h>

void foo() {
printf("inside foo()");
int x = 6;
x += 2;
}

int main() {

int x = 0;
x = x+2;
foo();
printf("x = %d\n", x);
x = 4;
return(0);
}

We compile it with the -ggdb switch :

gdb -ggdb -o test test.c

Next we open the program with gdb as usual :

gdb test

Now, we need to record the execution of the program, but we need to first start the program to do so. We'll set a breakpoint on the main function so that the program stops at the very beginning of the code.

(gdb) break main
Breakpoint 1 at 0x80483ed: file test.c, line 11.
(gdb) run
Starting program: /home/ekse/test Breakpoint 1, main () at test.c:11 11 int x = 0;
(gdb)

Our program is now started. To record the execution, simply type the record command :

(gdb) record

The execution is now recorded. We'll now execute the first lines of code with the next command :

(gdb) next
12 x = x+2;
(gdb) next
13 foo();
(gdb) print x
$1 = 2
As you can see, the value of the x variable is now 2. Now, we can go back one line with the reverse-next command :

(gdb) reverse-next
12 x = x+2;
(gdb) print x
$2 = 0
Reverse execution worked as expected ! Breakpoints also work in reverse execution. For example, we'll set a breakpoint on the line 16 to stop just before the end of the program.

(gdb) break 16
Breakpoint 5 at 0x8048414: file test.c, line 16.
(gdb) cont
Continuing. inside foo()x = 2
Breakpoint 5, main () at test.c:16 15 return(0);

We are now at the end of the program. Now let's say we want to go back to when the foo() function was called. We set a breakpoint on it and use reverse-continue :

(gdb) break foo
Breakpoint 6 at 0x80483ca: file test.c, line 4.
(gdb) reverse-continue
Continuing.

Breakpoint 6, foo () at test.c:4
4 printf("inside foo()");

It is also possible to use watchpoints. By using the watch command, we can make the program stop when the value of a variable is changed. To make it work, we must disable hardware watchpoints prior to setting our watchpoint :


(gdb) set can-use-hw-watchpoints 0
(gdb) watch x
Watchpoint 3: x
(gdb) reverse-continue
Continuing.
Watchpoint 3: x

Old value = 4
New value = 2
main () at test.c:14
14 x = 4;

Unfortunately, the reverse execution support is not perfect. For example, if you continue the program further, it will remove the breakpoint when reaching the printf() call inside of foo() Looking at the backtrace makes it obvious that GDB is confused about it's current location the code (that might be because the stack is not completely reconstructed when executing backwards):

(gdb) reverse-continue
Continuing.

Watchpoint 3 deleted because the program has left the block in
which its expression is valid.
0xb7ec6ea3 in vfprintf () from /lib/libc.so.6
(gdb) list foo
1 #include
2
3 void foo() {
4 printf("inside foo()");
5 int x = 6;
6 x += 2;
7 }
8
9 int main() {
10
(gdb) backtrace
#0 0xb7ec6ea3 in vfprintf () from /lib/libc.so.6
#1 0x00000000 in ?? ()

Still, reverse debugging is a really neat feature and with further enhancement it could become a game changer and avoid many headaches to programmers and QA workers.

mercredi 7 octobre 2009

Analyse : Dopewars 1.5.12 Server Denial of Service

Dans cet article, je fais l'analyse d'un Denial of Service découvert dans le serveur du jeu Dopewars 1.5.12. Le bug a été découvert par dougtko et l'avis original peut-être lu ici : http://seclists.org/bugtraq/2009/Oct/36.

Le problème est rencontré lorsqu'un joueur utilise un jet pour se déplacer. Le code fautif est le suivant :

serverside.c : ligne 505
case C_REQUESTJET:
i = atoi(Data);
...
Plus loin, la valeur est utilisée de cette façon :
else if (i != Play->IsAt && (NumTurns == 0 || Play->Turn <>EventNum == E_NONE && Play->Health > 0) {
dopelog(4, LF_SERVER, "%s jets to %s", GetPlayerName(Play), Location[i].Name);
...
Location est un tableau de structures LOCATION qui représentent les villes du jeu. Comme le jeu ne contient que 8 villes, il est possible de lire bien en dehors du tableau. En utilisant une adresse suffisamment grande, une lecture en dehors de la limite de la mémoire du programme sera tenté ce qui cause un erreur de segmentation et fait planter le serveur.

Pour corriger le problème, le code suivant a été ajouté après avoir récupéré la valeur de i :

/* Make sure value is within range */
if (i <>= NumLocation) {
dopelog(3, LF_SERVER, _("%s: DENIED jet to invalid location %s"), GetPlayerName(Play), Data);
break;
}