c

스터디 노트 - 0x250 직접 해보기 (메모리)

Posted on April 6, 2019
티스토리로 블로그 이사중입니다.
http://kminito.tistory.com/ ×
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!"