본문 바로가기
스터디/system

[GDB-Analysis] GDB 바이너리 디버깅 1

by 깝태 2011. 8. 17.

제가 직접 간단하게 소스를 만들어 디버깅을 할 생각입니다.
워게임을 풀면서 많이나오는 소스들을 가지고 디버깅을 해볼 생각입니다. (argv, strncpy 인자 불러오기 등등)

물론 IDA 로해야지 쉽고 빠르지만 그것보다 평소 디버깅 실력을 높이기위해 도전해봅니다.
제일 처음으로 GDB 로 디버깅을 하여 강의할 프로그램은 다음과 같습니다.

[gate@localhost tmp]$ cat t1.c
#include<stdio.h>

int main(int argc, char *argv[])
{
        printf("%s  ", argv[0]);
        printf("%s  ", argv[1]);
        printf("%s  ", argv[2]);
        printf("%s\n", argv[3]);

        return 0;
}
[gate@localhost tmp]$ ./t1 1 2 3 4
./t1  1  2  3

인자 4개를 출력해주는 프로그램 입니다.
바로 디버깅을 시작해보겠습니다.
 
[gate@localhost tmp]$ gdb -q t1
(gdb) disas main
Dump of assembler code for function main:
0x80483d0 <main>:       push   %ebp
0x80483d1 <main+1>:     mov    %esp,%ebp
0x80483d3 <main+3>:     mov    0xc(%ebp),%eax
0x80483d6 <main+6>:     mov    (%eax),%edx
0x80483d8 <main+8>:     push   %edx
0x80483d9 <main+9>:     push   $0x8048490
0x80483de <main+14>:    call   0x8048308 <printf>
0x80483e3 <main+19>:    add    $0x8,%esp
0x80483e6 <main+22>:    mov    0xc(%ebp),%eax
0x80483e9 <main+25>:    add    $0x4,%eax
0x80483ec <main+28>:    mov    (%eax),%edx
0x80483ee <main+30>:    push   %edx
0x80483ef <main+31>:    push   $0x8048490
0x80483f4 <main+36>:    call   0x8048308 <printf>
0x80483f9 <main+41>:    add    $0x8,%esp
0x80483fc <main+44>:    mov    0xc(%ebp),%eax
0x80483ff <main+47>:    add    $0x8,%eax
0x8048402 <main+50>:    mov    (%eax),%edx
0x8048404 <main+52>:    push   %edx
0x8048405 <main+53>:    push   $0x8048490
0x804840a <main+58>:    call   0x8048308 <printf>
0x804840f <main+63>:    add    $0x8,%esp
---Type <return> to continue, or q <return> to quit---
0x8048412 <main+66>:    mov    0xc(%ebp),%eax
0x8048415 <main+69>:    add    $0xc,%eax
0x8048418 <main+72>:    mov    (%eax),%edx
0x804841a <main+74>:    push   %edx
0x804841b <main+75>:    push   $0x8048495
0x8048420 <main+80>:    call   0x8048308 <printf>
0x8048425 <main+85>:    add    $0x8,%esp
0x8048428 <main+88>:    xor    %eax,%eax
0x804842a <main+90>:    jmp    0x8048430 <main+96>
0x804842c <main+92>:    lea    0x0(%esi,1),%esi
0x8048430 <main+96>:    leave
0x8048431 <main+97>:    ret
0x8048432 <main+98>:    nop
0x8048433 <main+99>:    nop
0x8048434 <main+100>:   nop
0x8048435 <main+101>:   nop
0x8048436 <main+102>:   nop
0x8048437 <main+103>:   nop
0x8048438 <main+104>:   nop
0x8048439 <main+105>:   nop
0x804843a <main+106>:   nop
0x804843b <main+107>:   nop
0x804843c <main+108>:   nop
---Type <return> to continue, or q <return> to quit---
0x804843d <main+109>:   nop
0x804843e <main+110>:   nop
0x804843f <main+111>:   nop
End of assembler dump.
(gdb) [gate@localhost tmp]$ clear
[gate@localhost tmp]$ cat t1.c
#include<stdio.h>

int main(int argc, char *argv[])
{
        printf("%s  ", argv[0]);
        printf("%s  ", argv[1]);
        printf("%s  ", argv[2]);
        printf("%s\n", argv[3]);

        return 0;
}
[gate@localhost tmp]$ ./t1 1 2 3 4
./t1  1  2  3

대부분의 분들이 아시겠지만 모르는분들도 계시고, 복습차로 더해봅니다.
/*
EBP+4   == RET
 ↓ 4Byte
EBP+8   == Argc == 인자 개수
 ↓ 4Byte
EBP+12 == Argv[0] == 프로그램 이름 == t1
 ↓ 4Byte
EBP+16 == Argv[1] == 1
 ↓ 4Byte
EBP+20 == Argv[2] == 2

Argv[0] Argv[1] Argv[2] == Argc 3

mov A B == B 에 A 의 값을 복사하다, 대입하다, 넣다
*/
[gate@localhost tmp]$ gdb -q t1
(gdb) disas main
Dump of assembler code for function main:
0x80483d0 <main>:       push   %ebp
0x80483d1 <main+1>:     mov    %esp,%ebp // 프롤로그

0x80483d3 <main+3>:     mov    0xc(%ebp),%eax // EAX 에 (EBP+12) 값 복사 - (EBP+12) == argv[0]
0x80483d6 <main+6>:     mov    (%eax),%edx // EDX 에 EAX 값 복사
0x80483d8 <main+8>:     push   %edx // EDX 푸시 
0x80483d9 <main+9>:     push   $0x8048490 // 0x8048490 푸시 - %s
0x80483de <main+14>:    call   0x8048308 <printf>
/*
        printf("%s  ", argv[0]); 호출합니다.

위의 소스를 제대로 분석하셨다면 푸시한 EDX 에는 Argv[0] 이 들어가있는것과
0x8048490 에는 "%s  " 가 들어가있다는것을 아실 수 있습니다.
*/

0x80483e3 <main+19>:    add    $0x8,%esp // ESP+8
0x80483e6 <main+22>:    mov    0xc(%ebp),%eax // EAX 에 (EBP+12) 의 값 복사
0x80483e9 <main+25>:    add    $0x4,%eax // EAX + 4 == 아까 Argv[0] 에서 4를 더해 Argv[1] 이 됩니다.
0x80483ec <main+28>:    mov    (%eax),%edx // EDX 에 EAX 값을 넣습니다.
0x80483ee <main+30>:    push   %edx // EDX 를 푸시합니다.
0x80483ef <main+31>:    push   $0x8048490 // 0x8048490 을 푸시합니다. - 아까와 같은 과정을 거칩니다.
0x80483f4 <main+36>:    call   0x8048308 <printf>
// printf("%s  ", argv[1]); 굳이 설명하지 않아도 아실겁니다.

0x80483f9 <main+41>:    add    $0x8,%esp
0x80483fc <main+44>:    mov    0xc(%ebp),%eax // EAX 에 (EBP+12) 의 값 복사
0x80483ff <main+47>:    add    $0x8,%eax // EAX + 8 == 아까 Argv[0] 에서 8을 더해 Argv[2] 가 됩니다.
0x8048402 <main+50>:    mov    (%eax),%edx
0x8048404 <main+52>:    push   %edx
0x8048405 <main+53>:    push   $0x8048490
0x804840a <main+58>:    call   0x8048308 <printf>
// printf("%s  ", argv[2]);

0x804840f <main+63>:    add    $0x8,%esp
0x8048412 <main+66>:    mov    0xc(%ebp),%eax // EAX 에 (EBP+12) 의 값 복사
0x8048415 <main+69>:    add    $0xc,%eax // EAX + 12 == 아까 Argv[0] 에서 12을 더해 Argv[3] 가 됩니다.
0x8048418 <main+72>:    mov    (%eax),%edx
0x804841a <main+74>:    push   %edx
0x804841b <main+75>:    push   $0x8048495
0x8048420 <main+80>:    call   0x8048308 <printf>
// printf("%s\n", argv[3]);

0x8048425 <main+85>:    add    $0x8,%esp
0x8048428 <main+88>:    xor    %eax,%eax 
0x804842a <main+90>:    jmp    0x8048430 <main+96>
0x804842c <main+92>:    lea    0x0(%esi,1),%esi
0x8048430 <main+96>:    leave
0x8048431 <main+97>:    ret // 에필로그
/* 생략 */
End of assembler dump.

프로그램을 정상적으로 디버깅해보았습니다. 그러면 한번 얻어가는게 있어야하니깐
입력했을때 과연 EDX 에 정상적으로 들어가는지 확인해보겠습니다.

(gdb) b *main+14
Breakpoint 3 at 0x80483de 
/* 
Argv[0] 을 printf 하는 부분에 브레이크포인터를 걸었습니다.

아까 보셨듯이 Argv[0] 에는 프로그램의 이름이 들어갑니다.

(gdb) r a a a a
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/gate/xodnr/tmp/t1 a a a a
/bin/bash: /home/orc/.bashrc: Permission denied

Breakpoint 3, 0x80483de in main ()
(gdb) x/s $edx
0xbffffc88:      "/home/gate/xodnr/tmp/t1" // Argv[0] 참조

그러면 설명을 생략하고 Argv[1] , Argv[2], Argv[3] 에 모두 브포를 걸고 보여드리겠습니다

(gdb) b *main+36
Breakpoint 9 at 0x80483f4
(gdb) r 1 2 3 4
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/gate/xodnr/tmp/t1 1 2 3 4
/bin/bash: /home/orc/.bashrc: Permission denied

Breakpoint 9, 0x80483f4 in main ()
(gdb) x/s $edx
0xbffffca0:      "1" // Argv[1] 참조

(gdb) b *main+58
Breakpoint 10 at 0x804840a
(gdb) r 1 2 3 4
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/gate/xodnr/tmp/t1 1 2 3 4
/bin/bash: /home/orc/.bashrc: Permission denied

Breakpoint 10, 0x804840a in main ()
(gdb) x/s $edx
0xbffffca2:      "2" // Argv[2] 참조

(gdb) b *main+80
Breakpoint 11 at 0x8048420
(gdb) r 1 2 3 4
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/gate/xodnr/tmp/t1 1 2 3 4
/bin/bash: /home/orc/.bashrc: Permission denied

Breakpoint 11, 0x8048420 in main ()
(gdb) x/s $edx
0xbffffca4:      "3" // Argv[3] 참조

이렇게 GDB 를 이용한 바이너리 분석 문제 1을 끝마치겠습니다.