1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(){
int i;
for(i=1;i<10;i++){
printf("Hello, world!\n");
}
return 0;
}
objdump
로 바이너리 파일을 살펴볼 수 있다.
AT&T 문법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kminito@min-ideapad:~/workspace/art_of$ objdump -D a.out | grep -A20 main.:
000000000000063a <main>:
63a: 55 push %rbp
63b: 48 89 e5 mov %rsp,%rbp
63e: 48 83 ec 10 sub $0x10,%rsp
642: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
649: eb 10 jmp 65b <main+0x21>
64b: 48 8d 3d a2 00 00 00 lea 0xa2(%rip),%rdi # 6f4 <_IO_stdin_used+0x4>
652: e8 b9 fe ff ff callq 510 <puts@plt>
657: 83 45 fc 01 addl $0x1,-0x4(%rbp)
65b: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
65f: 7e ea jle 64b <main+0x11>
661: b8 00 00 00 00 mov $0x0,%eax
666: c9 leaveq
667: c3 retq
668: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
66f: 00
0000000000000670 <__libc_csu_init>:
670: 41 57 push %r15
672: 41 56 push %r14
674: 49 89 d7 mov %rdx,%r15
Intel 문법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kminito@min-ideapad:~/workspace/art_of$ objdump -M intel -D a.out | grep -A20 main.:
000000000000063a <main>:
63a: 55 push rbp
63b: 48 89 e5 mov rbp,rsp
63e: 48 83 ec 10 sub rsp,0x10
642: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1
649: eb 10 jmp 65b <main+0x21>
64b: 48 8d 3d a2 00 00 00 lea rdi,[rip+0xa2] # 6f4 <_IO_stdin_used+0x4>
652: e8 b9 fe ff ff call 510 <puts@plt>
657: 83 45 fc 01 add DWORD PTR [rbp-0x4],0x1
65b: 83 7d fc 09 cmp DWORD PTR [rbp-0x4],0x9
65f: 7e ea jle 64b <main+0x11>
661: b8 00 00 00 00 mov eax,0x0
666: c9 leave
667: c3 ret
668: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
66f: 00
0000000000000670 <__libc_csu_init>:
670: 41 57 push r15
672: 41 56 push r14
674: 49 89 d7 mov r15,rdx
GDB로 프로그램이 시작되기 바로 전 프로세서 레지스터의 상태를 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
kminito@min-ideapad:~/workspace/art_of$ gdb -q ./a.out
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) break main
Breakpoint 1 at 0x63e
(gdb) run
Starting program: /home/kminito/workspace/art_of/a.out
Breakpoint 1, 0x000055555555463e in main ()
(gdb) info registers
rax 0x55555555463a 93824992233018
rbx 0x0 0
rcx 0x555555554670 93824992233072
rdx 0x7fffffffdf78 140737488347000
rsi 0x7fffffffdf68 140737488346984
rdi 0x1 1
rbp 0x7fffffffde80 0x7fffffffde80
rsp 0x7fffffffde80 0x7fffffffde80
r8 0x7ffff7dd0d80 140737351847296
r9 0x7ffff7dd0d80 140737351847296
r10 0x2 2
r11 0x3 3
r12 0x555555554530 93824992232752
r13 0x7fffffffdf60 140737488346976
r14 0x0 0
r15 0x0 0
rip 0x55555555463e 0x55555555463e <main+4>
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
---Type <return> to continue, or q <return> to quit---
gs 0x0 0
(gdb) quit
이 책에서는 인텔 문법을 사용한단다. 인텔의 문법 어셈블리 명령은 보통 아래와 같다.
1
명령 <목적지>, <근원지>
gdb에서set disassembly-flavor intel
를 입력하면 인텔 문법으로 볼 수 있다.
어셈블리 명령 단계에서 프로그램을 살펴보자.
컴파일 할 때 -g 옵션을 넣으면 GDB에서 소스코드를 볼 수 있음
1
kminito@min-ideapad:~/workspace/art_of$ gcc -g firstprog.c
gdb -q ./a.outlist
1
2
3
4
5
6
7
8
9
10
11
(gdb) list
1 #include <stdio.h>
2
3 int main(){
4 int i;
5
6 for(i=1;i<10;i++){
7 printf("Hello, world!\n");
8 }
9 return 0;
10 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) disassemble main
Dump of assembler code for function main:
0x000000000000063a <+0>: push rbp
0x000000000000063b <+1>: mov rbp,rsp
0x000000000000063e <+4>: sub rsp,0x10
0x0000000000000642 <+8>: mov DWORD PTR [rbp-0x4],0x1
0x0000000000000649 <+15>: jmp 0x65b <main+33>
0x000000000000064b <+17>: lea rdi,[rip+0xa2] # 0x6f4
0x0000000000000652 <+24>: call 0x510 <puts@plt>
0x0000000000000657 <+29>: add DWORD PTR [rbp-0x4],0x1
0x000000000000065b <+33>: cmp DWORD PTR [rbp-0x4],0x9
0x000000000000065f <+37>: jle 0x64b <main+17>
0x0000000000000661 <+39>: mov eax,0x0
0x0000000000000666 <+44>: leave
0x0000000000000667 <+45>: ret
End of assembler dump.
1
2
3
4
5
6
7
8
9
10
(gdb) break main
Breakpoint 1 at 0x642: file firstprog.c, line 6.
(gdb) run
Starting program: /home/kminito/workspace/art_of/a.out
Breakpoint 1, main () at firstprog.c:6
6 for(i=1;i<10;i++){
(gdb) info register rip
rip 0x555555554642 0x555555554642 <main+8>
main()의 시작점에 중지점 설정. 따라서 메인의 어떤 명령도 실행하기 전에 멈춘다.
RIP의 값 : 0x555555554642
->
1
0x0000000000000642 <+8>: mov DWORD PTR [rbp-0x4],0x1
을 가리키고 있음.
이거 이전에 나오는 명령들은 Function Prologue라고 불림. 함수가 실행될 때 마다 일어나고, 함수 실행이 끝날 때 원래 호출된 함수로 다시 돌아가는 길을 기억하기 위한 거라고 생각하면 된다.
GDB는 ‘x’라는 명령어를 사용해서 메모리 체크
옵션 o: 8진법, x:16진법, u:unsigned 10진법, t:2진법
info register eip 는 i r eip로 줄여서 사용 가능
1
2
3
4
(gdb) i r rip
rip 0x555555554642 0x555555554642 <main+8>
(gdb) x/x $rip
0x555555554642 <main+8>: 0x01fc45c7
$rip는 그 순간에 rip가 갖고있는 값을 의미함
x명령에서 숫자는 목표 주소에서 여러개를 조사하기 위한 형식으로 사용
1
2
3
(gdb) x/2x $rip
0x555555554642 <main+8>: 0x01fc45c7 0xeb000000
이거 이해 안됨. 두개는 무엇이고 어디서 나오는 건지?
해커스쿨 답변
1
2
3
4
5
info reg eip 하셨을때 나온 값은 eip레지스터가 갖는 값이고
x/x $eip 하셨을때 나온 값은 eip레지스터가 가르키는 값입니다
x/x $eip 하셨을때
주소: 값 이런형태로 나오게 되는데
이 때 주소와 info reg eip하셨을때 나온 값은 같습니다
메모리 값 확인
기본은 4바이트 워드
유닛 표시 크기를 바꿀 수 있음
b:단일바이트, h:2바이트의 하프워드,w:4바이트의 워드,g:8바이트의 자이언트
(gdb) x/x $rip
1
2
3
4
5
6
7
8
0x555555554642 <main+8>: 0x01fc45c7
(gdb) x/8xb $rip
0x555555554642 <main+8>: 0xc7 0x45 0xfc 0x01 0x00 0x00 0x00 0xeb
(gdb) x/8xh $rip
0x555555554642 <main+8>: 0x45c7 0x01fc 0x0000 0xeb00 0x4810 0x3d8d 0x00a2 0x0000
(gdb) x/8xw $rip
0x555555554642 <main+8>: 0x01fc45c7 0xeb000000 0x3d8d4810 0x000000a2
0x555555554652 <main+24>: 0xfffeb9e8 0xfc4583ff 0xfc7d8301 0xb8ea7e09
더 큰 유닛으로 보면 바이트가 반대로 나옴. x86 프로세서는 하위 바이트가 처음에 저장된다고 한다. (Little endian 바이트 순서) 처음엔 좀 헷갈렸는데 이제 많이 봤더니 눈에 좀 익는다. 아무튼 바이트씩 끊어서 반대 순서로 놓으면 된다.
1
2
3
4
(gdb) x/3i $rip
=> 0x555555554642 <main+8>: mov DWORD PTR [rbp-0x4],0x1
0x555555554649 <main+15>: jmp 0x55555555465b <main+33>
0x55555555464b <main+17>: lea rdi,[rip+0xa2] # 0x5555555546f4
1
2
(gdb) x/7xb $rip
0x555555554642 <main+8>: 0xc7 0x45 0xfc 0x01 0x00 0x00 0x00
아까 한 objdump로 역어셈블을 보면 EIP가 가리키는 7바이트는 해당 어셈블리 명령을 위한 기계어임을 알 수 있다.
1
642: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1
0을 rbp 레지스터에서 4만큼 뺀 주소에 저장된 곳으로 옮긴다.
1
2
3
4
5
6
7
8
9
10
11
(gdb) i r rbp
rbp 0x7fffffffde80 0x7fffffffde80
(gdb) x/4xb $rbp-4
0x7fffffffde7c: 0x00 0x00 0x00 0x00
(gdb) print $rbp-4
$1 = (void *) 0x7fffffffde7c
(gdb) x/4xb $1
0x7fffffffde7c: 0x00 0x00 0x00 0x00
(gdb) x/xw $1
0x7fffffffde7c: 0x00000000
rbp 레지스터의 주소는 0x7fffffffde80
어셈블리 명령어는 그러면 4 작은 —7c에 쓰여짐
nexti
를 쓰면 현재 명령이 실행됨. 간단히는 ni
1
2
3
4
5
6
7
8
9
10
11
(gdb) nexti
0x0000555555554649 6 for(i=1;i<10;i++){
(gdb) x/4xb $1
0x7fffffffde7c: 0x01 0x00 0x00 0x00
(gdb) x/dw $1
0x7fffffffde7c: 1
(gdb) i r rip
rip 0x555555554649 0x555555554649 <main+15>
(gdb) x/i $rip
=> 0x555555554649 <main+15>: jmp 0x55555555465b <main+33>
내가 첨에 c 코드를 잘못 적어서 i가 1부터 시작…
그래서 RBP에서 4를 뺀 주소의 값의 4바이트가 1이 됨
나머지는 한방에 보면
1
2
3
4
5
6
7
8
9
10
11
(gdb) x/10i $rip
=> 0x555555554649 <main+15>: jmp 0x55555555465b <main+33>
0x55555555464b <main+17>: lea rdi,[rip+0xa2] # 0x5555555546f4
0x555555554652 <main+24>: call 0x555555554510 <puts@plt>
0x555555554657 <main+29>: add DWORD PTR [rbp-0x4],0x1
0x55555555465b <main+33>: cmp DWORD PTR [rbp-0x4],0x9
0x55555555465f <main+37>: jle 0x55555555464b <main+17>
0x555555554661 <main+39>: mov eax,0x0
0x555555554666 <main+44>: leave
0x555555554667 <main+45>: ret
0x555555554668: nop DWORD PTR [rax+rax*1+0x0]
cmp는 비교. jle는 작거나 같으면 점프, 아니면 jmp의 값으로 점프하는 if else의 구조
cmp: 465b에서 rbp-4의 메모리 값과 9를 비교해서
jle 작거나 같으면 –464b로 점프
1
2
3
4
(gdb) nexti
0x000055555555465b 6 for(i=1;i<10;i++){
(gdb) x/i $rip
=> 0x55555555465b <main+33>: cmp DWORD PTR [rbp-0x4],0x9
1보다 작으니 465b를 가리킴
1
2
3
4
5
6
7
8
9
10
11
12
(gdb) nexti
0x000055555555465f 6 for(i=1;i<10;i++){
(gdb) i r rip
rip 0x55555555465f 0x55555555465f <main+37>
(gdb) nexti
7 printf("Hello, world!\n");
(gdb) i r rip은
rip 0x55555555464b 0x55555555464b <main+17>
(gdb) x/2i $rip
=> 0x55555555464b <main+17>: lea rdi,[rip+0xa2] # 0x5555555546f4
0x555555554652 <main+24>: call 0x555555554510 <puts@plt>
1
2
3
4
(gdb) i r rdi
rdi 0x5555555546f4 93824992233204
(gdb) x/6xb 0x5555555546f4
0x5555555546f4: 0x48 0x65 0x6c 0x6c 0x6f 0x2c
gdb의 examine 명령어가 아스키 타입을 보게 해준다고 한다..!!
1
2
(gdb) x/6cb 0x5555555546f4
0x5555555546f4: 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 44 ','
1
2
(gdb) x/s 0x5555555546f4
0x5555555546f4: "Hello, world!"