본문 바로가기
정보보안

정보보안2 3차시

by IT매니절 2024. 5. 11.

 

환경설정 yum 설치

yum -y install vim ctags gdb wget man man-pages \
gcc make gdb glibc.i686  glibc-devel.i686  \
libgcc.i686  libstdc++.i686 glibc  glibc-devel  libgcc  libstdc++

dnf -y install --enablerepo=powertools glibc-static glibc-static.686

 

 

보안 관련 공격코드나 분석툴이 파이썬으로 되어있어 보안에 중요

 

파이썬.py

for i in range(1, 6):
  print(f"{i}. Hello !")

간단한 for 문 출력

 

C 프로그램의 컴파일 과정

전처리기(gcc -E)  컴파일러(cc1)  어셈블러(as)   링커(ld)

               gcc -E              gcc -S               gcc -c            gcc -o   
test.c        →      test.i          →  test.s            → test.o      → test
소스파일          전처리파일    어셈블리파일      목적파일      실행파일

 

define : 전처리 지시어. 매크로 정의에 사용됨.
전처리기(gcc -E)에 의해 define 처리된 문자열을 값으로 변경하여 컴파일에 사용
#define TEST "TEST_STR"

 

전처리기(gcc -E)

gcc -E test.c -o test.i

=> test.i 를 열어보면

printf("%s: 컴파일 과정 분석하기\n", TODAY); 였는데 전처리기에 의해 값으로 대체됨

 

컴파일러(cc1)

어셈블러가 어셈블리 파일로 생성한다

gcc -S test.i -o test.s

gcc -masm=intel -S test.i -o test.s

 

어셈블리어 문법

1) at&t 문법 (기본값, 생략)

2) intel 문법 : -masm=intel 옵션 사용

 

아키텍처

- 64bit가 기본값 (옵션 생략)

- 32bit는 -m32옵션을 사용

 

+ ebp, esp, eax, ebx, ecx 등이 들어가면 32비트이다

+ rbp, rsp, rax, rbx, rcx 등이 들어가면 64비트 문법

 

64비트 at&t
64비트 intel

 

32비트 at&t
32비트 intel

 

intel 문법에는 
.intel_syntax noprefix 라인이 들어가있다

 

64비트는 .LC0: .align 8
32비트는 .LC0: .align 4

 

 

어셈블러(as)

gcc -c test.s

=> 실행하면 오브젝트(목적) 파일이 생성된다. test.o 

 

32비트의 경우 -m32 추가

 

gcc -c test.s
test.s: Assembler messages:
test.s:18: Error: invalid instruction suffix for `push'
test.s:19: Error: invalid instruction suffix for `push'
test.s:22: Error: invalid instruction suffix for `push'
test.s:26: Error: invalid instruction suffix for `push'
test.s:27: Error: invalid instruction suffix for `push'

=> 32비트 test.s파일을 -m32 옵션없이 64비트 오브젝트 파일을 만들려고 하자 에러 발생함

즉, 32비트는 어셈블리 파일도 32비트여야 하고, 64비트 또한 어셈블리 파일이 64비트여야 한다

 

 

링커(ld)

gcc -o test test.o 

=> test 실행파일 생성됨.

 

생략되었지만, 링커는 printf.o 파일과 test.o 파일을 결합시켜 최종 실행파일을 생성한다.

또 C라이브러리인 -lc 옵션이 원래 들어가 있는데 기본값이므로 생략되었음.

 

정적 컴파일 -static

gcc -static -o test_st test.o

=> ldd로 정보 확인불가능(not a dynamic executable). file로 확인하면

test_st: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=23845f3cf143f1177c08889e82ed17d65b9ad1cb, not stripped, too many notes (256)

이렇게 출력되어 확인할 수 있다

정적 컴파일은 파일 내부에 라이브러리를 포함하여, 파일 크기가 커진다. 장점은 외부 공유 라이브러리에 의존할 필요가 없다는 점.

 

동적 컴파일은 기본값으로 옵션이 생략된다.

( 라이브러리 주소만 가지고 있는 방식 )

크기가 비교적 작다. 다만 실행 시스템에 버전에 맞는 라이브러리가 없으면 실행 불가능

 

-rwxr-xr-x. 1 root root   18048  5월 11 15:42 test
-rwxr-xr-x. 1 root root 1708088  5월 11 16:02 test_st

=> 동적 컴파일과 정적 컴파일의 파일 크기 차이.

 

 

실습) gdb를 이용한 바이너리 분석

 

gdb란
GNU Debugger로 리눅스에서 사용되는 실행파일, 프로세스 등 분석하는 디버깅 툴.
이 툴을 사용하여 실행파일의 내부구조를 확인할 수 있고
실행파일의 소스코드를 복원할 수 있다

( 향후 사용할 파이썬을 이용한 gdb 확장 프로그램도 gdb를 잘 알아야 도움이 된다 )

 

gdb의 목적은 프로그램 실행동안 프로그램 내부에서 진행되고 있는 것이 무엇인지 (또는 프로그램이 죽는 순간 무엇을 했는지) 알기 위함이다

지정된 조건에서 멈추도록 하거나
멈췄을 때 무엇이 일어났는지 테스트 하거나
프로그램 시작시 영향을 줄 수 있는 것을 지정하거나 할 수 있다.

참고 사이트 : http://korea.gnu.org/manual/release/gdb/gdb.html

환경설정 패키지
yum or dnf -y install gcc make gdb glibc.i686  glibc-devel.i686 \
libgcc.i686  libstdc++.i686 glibc  glibc-devel  libgcc  libstdc++

 

 

gdb를 이용해 바이너리(실행파일)을 분석하려면 컴파일 과정에서 디버깅 정보 -g 옵션을 삽입하여야 한다
ex) gcc -g -o 실행파일 소스파일

 

(gdb)프롬프트

 

 

(gdb) help
(gdb) help 명령어
(gdb) help all            <- 모든 명령어
(gdb) help run
=> 도움말 출력

(gdb) ctrl + d
(gdb) q
(gdb) quit
=> gdb 종료/빠져나가기

 

명령어 r (run의 축약)

r은 프로그램 실행을 의미한다.

화면의 경우, c_Chap01Ex04의 실행파일을 gdb로 실행한 상태.

 

disassemble 명령어를 사용해서 실행파일을 디스어셈블한다 (disas 로 축약 가능)
실행파일에 포함된 기계어 코드(0,1)를 해석해서 어셈블리 코드로 변환해준다
기본 문법은 at&t 문법을 사용한다.

set disassembly-flavor intel
=> intel 문법으로 변경하려면 이렇게 작성하고 (윈도우에서 주로 사용)
set disassembly-flavor att
=> 다시 기본값 at&t로 돌아오려면 이렇게 사용

 

intel 문법으로 변경 후 disas main

 

명령어의 축약

run -> r
break -> b
continue -> c

disassemble -> disas

quit -> q

 

Dump of assembler code for function main:
warning: Source file is more recent than executable.
10      {
   0x080484ad <+0>:     lea    0x4(%esp),%ecx
   0x080484b1 <+4>:     and    $0xfffffff0,%esp
   0x080484b4 <+7>:     pushl  -0x4(%ecx)
   0x080484b7 <+10>:    push   %ebp
   0x080484b8 <+11>:    mov    %esp,%ebp
   0x080484ba <+13>:    push   %ecx
   0x080484bb <+14>:    sub    $0x14,%esp

11          int number;  // 변수 number 를 선언한다.
12          number = 1;  // 변수 number에 값 1을 대입한다.
   0x080484be <+17>:    movl   $0x1,-0xc(%ebp)

13
14          while (number <= 5) // number의 값이 5보다 작거나 같다면
   0x080484c5 <+24>:    jmp    0x80484de <main+49>
   0x080484de <+49>:    cmpl   $0x5,-0xc(%ebp)
   0x080484e2 <+53>:    jle    0x80484c7 <main+26>

15          {
16              printf("number = %d\n", number); // number에 저장된 값을 출력한다.
   0x080484c7 <+26>:    sub    $0x8,%esp
   0x080484ca <+29>:    pushl  -0xc(%ebp)
   0x080484cd <+32>:    push   $0x804858c
--Type <RET> for more, q to quit, c to continue without paging--
   0x080484d2 <+37>:    call   0x8048350 <printf@plt>
   0x080484d7 <+42>:    add    $0x10,%esp

17              number++;
   0x080484da <+45>:    addl   $0x1,-0xc(%ebp)

18          }
19
20          return 0;
   0x080484e4 <+55>:    mov    $0x0,%eax

21      }
   0x080484e9 <+60>:    mov    -0x4(%ebp),%ecx
   0x080484ec <+63>:    leave
   0x080484ed <+64>:    lea    -0x4(%ecx),%esp
   0x080484f0 <+67>:    ret

End of assembler dump.

disas /m main
=> 소스코드에 대한 어셈블리 코드 출력 ( 원본 소스코드의 경우, for문을 통해 printf 출력하는 내용 )

 

아래는 인텔

더보기

Dump of assembler code for function main:
10      {
   0x080484ad <+0>:     lea    ecx,[esp+0x4]
   0x080484b1 <+4>:     and    esp,0xfffffff0
   0x080484b4 <+7>:     push   DWORD PTR [ecx-0x4]
   0x080484b7 <+10>:    push   ebp
   0x080484b8 <+11>:    mov    ebp,esp
   0x080484ba <+13>:    push   ecx
   0x080484bb <+14>:    sub    esp,0x14

11          int number;  // 변수 number 를 선언한다.
12          number = 1;  // 변수 number에 값 1을 대입한다.
   0x080484be <+17>:    mov    DWORD PTR [ebp-0xc],0x1

13
14          while (number <= 5) // number의 값이 5보다 작거나 같다면
   0x080484c5 <+24>:    jmp    0x80484de <main+49>
   0x080484de <+49>:    cmp    DWORD PTR [ebp-0xc],0x5
   0x080484e2 <+53>:    jle    0x80484c7 <main+26>

15          {
16              printf("number = %d\n", number); // number에 저장된 값을 출력한다.
   0x080484c7 <+26>:    sub    esp,0x8
   0x080484ca <+29>:    push   DWORD PTR [ebp-0xc]
   0x080484cd <+32>:    push   0x804858c
--Type <RET> for more, q to quit, c to continue without paging--
   0x080484d2 <+37>:    call   0x8048350 <printf@plt>
   0x080484d7 <+42>:    add    esp,0x10

17              number++;
   0x080484da <+45>:    add    DWORD PTR [ebp-0xc],0x1

18          }
19
20          return 0;
   0x080484e4 <+55>:    mov    eax,0x0

21      }
   0x080484e9 <+60>:    mov    ecx,DWORD PTR [ebp-0x4]
   0x080484ec <+63>:    leave
   0x080484ed <+64>:    lea    esp,[ecx-0x4]
   0x080484f0 <+67>:    ret

End of assembler dump.

Dump of assembler code for function main:
   0x080484ad <+0>:     8d 4c 24 04     lea    0x4(%esp),%ecx
   0x080484b1 <+4>:     83 e4 f0        and    $0xfffffff0,%esp
   0x080484b4 <+7>:     ff 71 fc        pushl  -0x4(%ecx)
   0x080484b7 <+10>:    55      push   %ebp
   0x080484b8 <+11>:    89 e5   mov    %esp,%ebp
   0x080484ba <+13>:    51      push   %ecx
   0x080484bb <+14>:    83 ec 14        sub    $0x14,%esp
   0x080484be <+17>:    c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%ebp)
   0x080484c5 <+24>:    eb 17   jmp    0x80484de <main+49>
   0x080484c7 <+26>:    83 ec 08        sub    $0x8,%esp
   0x080484ca <+29>:    ff 75 f4        pushl  -0xc(%ebp)
   0x080484cd <+32>:    68 8c 85 04 08  push   $0x804858c
   0x080484d2 <+37>:    e8 79 fe ff ff  call   0x8048350 <printf@plt>
   0x080484d7 <+42>:    83 c4 10        add    $0x10,%esp
   0x080484da <+45>:    83 45 f4 01     addl   $0x1,-0xc(%ebp)
   0x080484de <+49>:    83 7d f4 05     cmpl   $0x5,-0xc(%ebp)
   0x080484e2 <+53>:    7e e3   jle    0x80484c7 <main+26>
   0x080484e4 <+55>:    b8 00 00 00 00  mov    $0x0,%eax
   0x080484e9 <+60>:    8b 4d fc        mov    -0x4(%ebp),%ecx
   0x080484ec <+63>:    c9      leave
   0x080484ed <+64>:    8d 61 fc        lea    -0x4(%ecx),%esp
   0x080484f0 <+67>:    c3      ret
End of assembler dump.

disas /r main
=> 소스코드에 대한 기계어 코드를 출력

 

아래는 인텔

더보기

Dump of assembler code for function main:
   0x080484ad <+0>:     8d 4c 24 04     lea    ecx,[esp+0x4]
   0x080484b1 <+4>:     83 e4 f0        and    esp,0xfffffff0
   0x080484b4 <+7>:     ff 71 fc        push   DWORD PTR [ecx-0x4]
   0x080484b7 <+10>:    55      push   ebp
   0x080484b8 <+11>:    89 e5   mov    ebp,esp
   0x080484ba <+13>:    51      push   ecx
   0x080484bb <+14>:    83 ec 14        sub    esp,0x14
   0x080484be <+17>:    c7 45 f4 01 00 00 00    mov    DWORD PTR [ebp-0xc],0x1
   0x080484c5 <+24>:    eb 17   jmp    0x80484de <main+49>
   0x080484c7 <+26>:    83 ec 08        sub    esp,0x8
   0x080484ca <+29>:    ff 75 f4        push   DWORD PTR [ebp-0xc]
   0x080484cd <+32>:    68 8c 85 04 08  push   0x804858c
   0x080484d2 <+37>:    e8 79 fe ff ff  call   0x8048350 <printf@plt>
   0x080484d7 <+42>:    83 c4 10        add    esp,0x10
   0x080484da <+45>:    83 45 f4 01     add    DWORD PTR [ebp-0xc],0x1
   0x080484de <+49>:    83 7d f4 05     cmp    DWORD PTR [ebp-0xc],0x5
   0x080484e2 <+53>:    7e e3   jle    0x80484c7 <main+26>
   0x080484e4 <+55>:    b8 00 00 00 00  mov    eax,0x0
   0x080484e9 <+60>:    8b 4d fc        mov    ecx,DWORD PTR [ebp-0x4]
   0x080484ec <+63>:    c9      leave
   0x080484ed <+64>:    8d 61 fc        lea    esp,[ecx-0x4]
   0x080484f0 <+67>:    c3      ret
End of assembler dump.

 

breakpoint

=> 프로그램을 멈추는 포인트. help b 로 상세 내용을 볼 수 있음.

 

b main
Breakpoint 1 at 0x80484be: file c_Chap01Ex04.c, line 12.

b main

r

i b

n

n

p number

n

n

c

=> main 소스코드에 대한 브레이크 포인트 지정. r로 run 실행. i b로 브레이크 포인트 정보 확인.

n은 next로, 다음 코드로 진행을 뜻함.

p는 print로, number값을 출력하였음.

c는 continue로 나머지 프로그램을 마저 전부 진행함.

 

+)

n은 run. 프로그램 실행.

set : 매개변수나 환경설정을 셋팅할 때 사용

show : 수많은 매개변수와 환경설정의 현재 설정을 표시해준다

l은 list이며 현재 코드의 소스 코드를 출력해준다.
watch : 지정된 변수에 변화가 생기면 실행 중지
info : 다양한 유형의 정보 출력 ex) into locals, info b

bt : 현재 실행하고 있는 스택 출력(함수 호출 스택)

examine (x) : 메모리 주소 검사

 

자신의 홈 디렉터리에 .gdbinit 파일을 만들고, 그 안에 설정을 넣으면 gdb를 실행할 때마다 적용한다.

ex)

set disassembly-flavor intel

=> gdb를 실행할 때마다 intel 문법으로 셋팅한다.

 

 

(gdb) disas main
Dump of assembler code for function main:
=> 0x080484ad <+0>:     lea    ecx,[esp+0x4]

하략.

↑처음 실행했을 때 disas main 결과

 

(gdb) disas main
Dump of assembler code for function main:
   0x080484ad <+0>:     lea    ecx,[esp+0x4]
   0x080484b1 <+4>:     and    esp,0xfffffff0
   0x080484b4 <+7>:     push   DWORD PTR [ecx-0x4]
   0x080484b7 <+10>:    push   ebp
   0x080484b8 <+11>:    mov    ebp,esp
   0x080484ba <+13>:    push   ecx
   0x080484bb <+14>:    sub    esp,0x14
   0x080484be <+17>:    mov    DWORD PTR [ebp-0xc],0x1
   0x080484c5 <+24>:    jmp    0x80484de <main+49>
=> 0x080484c7 <+26>:    sub    esp,0x8

하략.

↑ n으로 몇줄 더 실행하고 나서 disas main 결과

=> 화살표로 현재 위치를 표시해주고 있다

 

(gdb) p number
$1 = 134513951
(gdb) n
14          while (number <= 5) // number의 값이 5보다 작거나 같다면
(gdb) p number
$2 = 1

=> number에 쓰레기값이 들어가 있다가 1로 초기화되는 것 확인

 

(gdb) p &number
$3 = (int *) 0xffffd42c

=> number의 주소값도 확인 가능

 

(gdb) x/xw &number
0xffffd42c:     0x00000001

(gdb) x/2xw &number
0xffffd42c:     0x00000001      0xf7fd14d0
(gdb) x/3xw &number
0xffffd42c:     0x00000001      0xf7fd14d0      0xffffd450
(gdb) x/4xw &number
0xffffd42c:     0x00000001      0xf7fd14d0      0xffffd450      0x00000000

=> x로 메모리 조사. x는 hex(16진수)형태이고, w는 word(4byte 단위)를 의미한다.

2wx, 3xw, 4xw는 반복숫자를 넣은 것이다. &number의 시작 주소에서 word 4byte만큼 2번, 3번, 4번 보여준다는 뜻.

 

형식 : x/[반복숫자][출력형태][크기] [메모리주소]

 

출력형태

o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char) and s(string)

 

크기
b(byte), h(halfword, 2 bytes), w(word, 4 bytes), g(giant, 8 bytes)

 

결과값을 살펴보면

0xffffd42c:     0x00000001

                                      └ 0xffffd42c

0xffffd42c:     0x00000001

                                  └ 0xffffd42d

0xffffd42c:     0x00000001

                              └ 0xffffd42e

0xffffd42c:     0x00000001

                          └ 0xffffd42f

주소값이 이렇게 책정된다 16진수 0 ~ f 까지의 값을 생각하면 됨.

 

두번째 값을 살펴보면

0xffffd42c:     0x00000001      0xf7fd14d0

                                                              └ 0xffffd430

0xffffd42c:     0x00000001      0xf7fd14d0

                                                          └ 0xffffd431

0xffffd42c:     0x00000001      0xf7fd14d0

                                                       └ 0xffffd432

0xffffd42c:     0x00000001      0xf7fd14d0

                                                    └ 0xffffd433

 

이렇게 뒤에서부터 주소값이 책정된다.

 

(gdb) x/xb &number
0xffffd42c:     0x01
(gdb) x/2xb &number
0xffffd42c:     0x01    0x00

=> b 바이트단위로 보여준다

 

(gdb) x/16xw $ebp
0xffffd438:     0x00000000      0xf7e34f36      0x00000000      0x08048370
0xffffd448:     0x00000000      0xf7e34f36      0x00000001      0xffffd4d4
0xffffd458:     0xffffd4dc      0xffffd474      0x0804a010      0x00000008
0xffffd468:     0xffffd4dc      0xf7fbc000      0xf7e2475c      0xf7fbc000
(gdb) x/16xw $ebp-16
0xffffd428:     0xffffd4dc      0x00000001      0xf7fd14d0      0xffffd450
0xffffd438:     0x00000000      0xf7e34f36      0x00000000      0x08048370
0xffffd448:     0x00000000      0xf7e34f36      0x00000001      0xffffd4d4
0xffffd458:     0xffffd4dc      0xffffd474      0x0804a010      0x00000008

=> w word 단위로 보여주는데, 16개를 보여준다.

cpu에 대한 메모리 공간(레지스터), 그 중에 ebp는 스택의 바닥을 가리킨다.

스택의 바닥에서부터 16개만큼의 공간을 보여달라는 의미이다.

-16은 16개 이전이라는 뜻.

 

최상단 : esp 레지스터

 

레지스터 : cpu 안에 들어있는 고속의 메모리 공간.

 

 

(gdb) display number
1: number = 134513951

=> number 변수를 모니터링 display 한다.

(gdb) n
16              printf("number = %d\n", number); // number에 저장된 값을 출력한다.
1: number = 2
(gdb) n
number = 2
17              number++;
1: number = 2

=> n (next) 할 때마다 number가 같이 출력된다.

축약어는 disp

 

 

 

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

정보보안2 5차시  (0) 2024.05.18
정보보안2 4차시  (0) 2024.05.12
과제)  (0) 2024.05.09
정보보안2 2차시  (0) 2024.05.05
정보보안2 1차시  (0) 2024.05.04