본문 바로가기
정보보안

정보보안2 7차시

by IT매니절 2024. 5. 25.

gdb 할 때 기본적인 disp 항목

disp $esp
disp $ebp
disp $eip
disp /i $eip

disp /4xw $esp

--

 

저번차시에 하다가 급하게 끝냈던 내용을 다시 되짚으며 처음부터 시작

 

 

소스코드 : int a=1, int b=2;

0x080484b0 <+3>:     sub    $0x8,%esp

→ 변수 2개를 선언하기 위해 공간을 만들었다 (int니까 4byte 2개)

확인해보니 %ebp 0xffffd458 / %esp 0xffffd450 로 8만큼 %esp가 낮아짐

( Centos7에서는 printf까지 다섯개 분량의 공간을 미리 만들고 mov로 복사 sub    $0x14,%esp

 실습환경인 Rocky8에서는 두 개만 만들고 나중에 push로 넣는다 )

0x080484b3 <+6>:     movl   $0x1,-0x4(%ebp)

0x080484ba <+13>:    movl   $0x2,-0x8(%ebp)

→ $ebp 기준으로 -4 위치에 1 대입, -8 위치에 2 대입

 

소스코드 : printf("%d, %d \n", a, b);

0x080484c1 <+20>:    pushl  -0x8(%ebp)
0x080484c4 <+23>:    pushl  -0x4(%ebp)

0x080484c7 <+26>:    push   $0x804856c

→ printf 함수의 세번째(b), 두번째(a), 첫번째(문자열) 인수를 스택에 저장한다.

( 마지막부터 거꾸로 실행한다. 후입선출. a, b 순으로 선언되었음. 따라서 b, a 순으로 저장하고 마지막에 문자열 저장 )

 

0x080484c1 <+20>:    pushl  -0x8(%ebp) 실행후

(gdb) x/4xw $esp
0xffffd43c:     0x00000002      0x00000002      0x00000001      0x00000000

0x080484c4 <+23>:    pushl  -0x4(%ebp) 실행후

(gdb) x/4xw $esp
0xffffd438:     0x00000001      0x00000002      0x00000002      0x00000001

=> 순서대로 b인 2의 값, a인 1의 값이 추가된 것

0x080484c7 <+26>:    push   $0x804856c

(gdb) x/4xw $esp
0xffffd434:     0x0804856c      0x00000001      0x00000002      0x00000002

=> 알아볼 순 없지만 문자열이 스택에 추가됨

( mov는 메모리에서 메모리로 이동할 수 없으므로, push를 사용한다 )

(gdb) x/s 0x0804856c
0x804856c:      "a: %d, b: %d\n"

=> x/s로 확인하면 문자열 확인 가능

(gdb) x/4xw 0x0804856c
0x804856c:      0x25203a61      0x62202c64      0x6425203a      0x0000000a

=> x/4xw로 확인한 결과

이를 아스키코드표와 함께 대응해 볼 때

0x  25 20 3a 61          0x 62 20 2c 64        0x 64 25 20 3a            0x0000000a
     % 공백  :   a               b  공백 ,   d              d  % 공백 :                              \n <- 개행이라고 하여 한 문자로 취급 LF
 ( NULL문자는 00 이다 )

 

0x080484cc <+31>:    call   0x8048350 <printf@plt>

→ printf 함수 호출. 이 때 printf함수 실행하고 종료되면 돌아올 RET(Return address)를 스택에 저장함

call은 복귀주소를 스택에 저장하고, 함수를 호출하고 점프한다. ( 복귀주소 : 0x080484d1 )

 

(gdb) si
0x08048350 in printf@plt ()
1: $esp = (void *) 0xffffd430
2: $ebp = (void *) 0xffffd448
3: $eip = (void (*)()) 0x8048350 <printf@plt>
4: x/i $eip
=> 0x8048350 <printf@plt>:      jmp    *0x804a00c ← printf는 동적 메모리이기 때문에 아직 이 주소에 없음. 채워주는 과정을 실행할 예정이다.

평소에는 n 또는 ni를 사용했지만
step into
즉 si로 printf 함수내로 들어가야 복귀주소를 확인할 수 있다

(gdb) x/8xw $esp
0xffffd430:     0x080484d1      0x0804856c      0x00000001      0x00000002
0xffffd440:     0x00000002      0x00000001      0x00000000      0xf7e34f36

(gdb) p printf
$1 = {<text variable, no debug info>} 0xf7e6e7e0 <printf>
=> printf의 위치

step into로 함수 안에 들어오면 disas main으로 위치를 파악할 수 없고 n이나 ni를 쓸 수 없다.
c로 쭉 이어서 실행하다가 main으로 돌아올 수 있지만, 함수의 끝까지 실행하므로
계속 분석하려면 bp 브레이크 포인트를 잡아주어야 한다
(gdb) b *main+36
(gdb) c
Breakpoint 3, 0x080484d1 in main () at test-att-sub.c:11
...
=> 브레이크 포인트에 멈췄다

(gdb) x/8xw $esp
0xffffd434:     0x0804856c      0x00000001      0x00000002      0x00000002
0xffffd444:     0x00000001      0x00000000      0xf7e34f36      0x00000001
=> 복귀주소 사라짐

 

0x080484d1 <+36>:    add    $0xc,%esp

→  printf 함수 호출이 끝나서 스택 복원 a=10, b=11, c=12.  12만큼 %esp의 위치를 옮긴다.

(gdb) x/8xw $esp
0xffffd440:     0x00000002      0x00000001      0x00000000      0xf7e34f36
0xffffd450:     0x00000001      0xffffd4d4      0xffffd4dc      0xffffd474
=> 0xffffd434 에서 0xffffd440로.
0x0804856c      0x00000001      0x00000002     12만큼의 데이터가 보이지 않게 되고, a=1, b=2 했을때로 되돌아왔다.

0x080484d4 <+39>:    mov    $0x0,%eax

→ 종료. %eax에는 종료 값으로 0이 저장된다 eax 레지스터는 리턴값이 저장됨

 

disas /m main

=> C언어 문법을 어셈블리어와 같이 출력해준다. 컴파일시 -g 옵션 필요

 

-4(%esp)

=> $esp가 가리키는 위치 -4의 메모리 주소(값)

%esp-4

=> $esp가 가리키는 위치에서 -4

 

* 만약 centos7에서 진행했다면? push 대신 movl로 대체되어 인수값을 복사한다.

movl  -0x8(%ebp), %eax

movl %eax, (%esp)

movl  -0x4(%ebp), %eax

movl %eax, (%esp)

movl $0x804856c,(%esp)

 

c언어에서는 무조건 1개의 값만 리턴가능하므로

구조체나 배열의 경우 첫 번째 주소값을 넘김.

 

함수의 에필로그

leave  <-- movl %ebp, %esp + pop %ebp

ret      <-- pop %eip + jmp %eip

 

leave

=> 낮아진 %esp에 %ebp를 넣어 다시 높아지도록 만든다. (esp: 440 -> 448)

pop으로 %esp에 들어있는 값(0)을 %ebp에 넣는다. 그래서 %ebp의 값이 0이 되고 pop때문에 %esp는 더 높아짐.

* pop 명령어 : %esp 레지스터의 값을 피연산자에 넣고 한 단계 높인다

(gdb) x/xw 0xffffd448
0xffffd448:     0x00000000
=> movl %ebp, %esp로 인해 %esp의 주소값 0xffffd448에 들어있는 값 0

(gdb) ni

1: x/i $eip
=> 0x80484d9 <main+44>: leave
(gdb) ni
(gdb) i r ebp esp
ebp            0x0                 0x0
esp            0xffffd44c          0xffffd44c

ebp : 0xffffd448 -> 0x0
esp : 0xffffd448 -> 0xffffd44c

 

ret

=> pop으로 %eip에 복귀주소를 저장하면서 %esp가 한 칸 높아진다

(gdb) ni
0xf7e34f36 in __libc_start_main () from /lib/libc.so.6
1: x/i $eip
=> 0xf7e34f36 <__libc_start_main+246>:  add    $0x10,%esp
(gdb) ni
(gdb) i r eip
eip            0xf7e34f36          0xf7e34f36 <__libc_start_main+246>

이 때 disas main 하면 화살표가 없다. main은 끝났으므로.
disas __libc_start_main 하면 화살표가 있음. 

=> ebp에 pop할 때는 0이었지만, 한 칸 높아지면서 0xffffd44c에 복귀주소가 저장되어 있다
0xffffd44c의 복귀주소를 %eip에 저장. %esp는 한 칸 높아진다.


 

SFP 세이브 프레임 포인터

=> 함수종료시에 %esp가 가리키는 주소의 값을 %ebp에 집어넣은 후 새 프로그램을 시작한다

 

실습)

임의의 함수 A를 만들지만, 호출하지 않는(void myShell(); 이렇게 선언만 하고 호출X) c언어 코드를 만든다.

gdb로 함수 A의 주소를 확인한다.

 

(gdb) p myShell
$1 = {void ()} 0x80484f4 <myShell>

 

(gdb) x/5wx $ebp
0xffffd458:     0x00000000      0xf7e34f36      0x00000001      0xffffd4e4

                            ebp                    ret                   argc                  argv
0xffffd468:     0xffffd4ec

                           envp

( int main(int argc, char *argv[], char *envp[]) c언어 기본 함수 )

 

ret 주소를 함수 A의 주소로 바꿔치기한다.

 

(gdb) set {int}($ebp+4)=0x80484f4

(gdb) x/8wx $ebp
0xffffd458:     0x00000000      0x080484f4      0x00000001      0xffffd4e4
0xffffd468:     0xffffd4ec      0xffffd484      0x0804a014      0x00000010

=> 변조된 상태

 

(gdb) ni

3: $eip = (void (*)()) 0x80484f4 <myShell>

=> ni를 여러번 하여 끝에 도달하자 다음에 실행할 명령어, eip에 함수A의 주소가 담긴다

 

(gdb) ni
0x080484fc      16          system("/bin/sh");
1: $ebp = (void *) 0xffffd45c
2: $esp = (void *) 0xffffd458
3: $eip = (void (*)()) 0x80484fc <myShell+8>
4: x/i $pc
=> 0x80484fc <myShell+8>:       call   0x8048380 <system@plt>
5: x/4xw $esp
0xffffd458:     0x080485ab      0x00000000      0x00000001      0xffffd4e4
(gdb) ni
[Detaching after vfork from child process 1902]
sh-4.4#

 

=> 함수A 실행이 종료되자 함수A에서 실행한 /bin/sh 쉘 탈취 명령어가 실행되었다.

 

 

실습) 함수의 분석

 

(gdb) p main
$1 = {int ()} 0x80484ad <main>

=> main의 주소값도 확인할 수 있다

 

Dump of assembler code for function main:
   0x080484ad <+0>:     push   %ebp
   0x080484ae <+1>:     mov    %esp,%ebp

 

(gdb) i r $esp $ebp
esp            0xffffd458          0xffffd458
ebp            0x0                 0x0

=> 처음에 시작할 때 찍어보면 ebp값이 0으로 되어 있다

 

(gdb) i r $esp $ebp
esp            0xffffd458          0xffffd458
ebp            0xffffd458          0xffffd458

=> mov 명령어가 실행된 후 $esp와 $ebp의 값이 같아진다. 이제 소스코드 시작.

 

0x80484b0 <main+3>:  sub    $0x4,%esp

(gdb) i r $esp $ebp
esp            0xffffd454          0xffffd454
ebp            0xffffd458          0xffffd458

=> 변수를 위해 sub로 공간 확보, $esp의 위치가  4byte 낮아진다

 

0x080484b3 <+6>:     movl   $0x1,-0x4(%ebp)

=> ebp에서 -4위치에 1 넣기

 

0x080484ba <+13>:    pushl  -0x4(%ebp)
0x080484bd <+16>:    push   $0x804858c

=> printf 함수의 인수를 저장. i와 문자열.

 

0x080484c2 <+21>:    call   0x8048350 <printf@plt>

=> printf 함수 call

 

0x080484f0 <+26>:    add    $0x8,%esp

=> 두 칸(4바이트 x 2)을 다시 복귀시켜서 $esp 복귀. 0xffffd44c -> 0xffffd454로 복귀됨

 

0x080484ca <+29>:    call   0x80484d6 <func1>

=> finc1 함수 call

 

이 때는 si로 step into.

 

(gdb) disas func1

   0x080484d6 <+0>:     push   %ebp
   0x080484d7 <+1>:     mov    %esp,%ebp
   0x080484d9 <+3>:     sub    $0x4,%esp
   0x080484dc <+6>:     movl   $0x2,-0x4(%ebp)
   0x080484e3 <+13>:    pushl  -0x4(%ebp)
   0x080484e6 <+16>:    push   $0x8048593
   0x080484eb <+21>:    call   0x8048350 <printf@plt>
   0x080484f0 <+26>:    add    $0x8,%esp
   0x080484f3 <+29>:    nop
   0x080484f4 <+30>:    leave
   0x080484f5 <+31>:    ret

 

함수 내용이 차례대로 실행된다.

 

(gdb) i r $ebp $esp
ebp            0xffffd458          0xffffd458
esp            0xffffd44c          0xffffd44c

 

0x080484d7 <+1>:     mov    %esp,%ebp          실행 후

 

(gdb) i r $ebp $esp
ebp            0xffffd44c          0xffffd44c
esp            0xffffd44c          0xffffd44c

 

=> 함수 내에서도 처음에 $ebp와 $esp의 위치를 맞추어주는 작업이 있다

 

(gdb) x/4xw $ebp
0xffffd44c:     0xffffd458      0x080484cf      0x00000001      0x00000000

=> 이 때 $ebp를 조사해보면

0xffffd458은 함수 호출전의 ebp값이고 0x080484cf는 함수를 끝내고 돌아갈 복귀주소이다

 

   0x080484f3 <+29>:    nop

=> nop는 공백을 처리를 위한 함수이다. cpu가 쉬어감

nop를 왜 사용하는가? ( https://shinluckyarchive.tistory.com/113

 

   0x080484f4 <+30>:    leave
   0x080484f5 <+31>:    ret

(gdb) i r $ebp $esp
ebp            0xffffd458          0xffffd458
esp            0xffffd450          0xffffd450

=> leave 명령어를 통해 ebp의 위치가 변경되었다

 

(gdb) ni

(gdb) i r $ebp $esp
ebp            0xffffd458          0xffffd458
esp            0xffffd454          0xffffd454

=> ret 명령어 후, main으로 돌아온 esp 레지스터의 위치

 

0x80484d4 <main+39>: leave

(gdb) i r $ebp $esp
ebp            0x0                 0x0
esp            0xffffd45c          0xffffd45c

=> 다시 main의 종료과정. leave 명령어로 인해

esp와 ebp가 같아지고, ebp 0으로 초기화되고, esp 한 바이트 높아짐

 

0x80484d5 <main+40>: ret

(gdb) ni

x/i $eip
=> 0xf7e34f36 <__libc_start_main+246>:  add    $0x10,%esp

(gdb) i r $ebp $esp
ebp            0x0                 0x0
esp            0xffffd460          0xffffd460

=> eip에 셋팅해주고 esp 한 바이트 높아짐

 

종료 ~

'정보보안' 카테고리의 다른 글

정보보안3 1차시  (0) 2024.06.01
정보보안2 8차시  (0) 2024.05.26
정보보안2 6차시  (0) 2024.05.19
정보보안2 5차시  (0) 2024.05.18
정보보안2 4차시  (0) 2024.05.12