본문 바로가기
정보보안

정보보안2 4차시

by IT매니절 2024. 5. 12.

 

변수는 스택에 저장되는데,

높은 주소에서 낮은 주소로 ↓ 채워지게 된다. (LIFO, 선입후출=후입선출)

 

sizeof(변수)

=> 함수가 아니라 연산자이다. 변수의 크기를 확인.

 

 

printf("%c\n", 'A');

=> 'A' 이부분은 리터럴 상수라고 한다. 변수에 변하지 않는 데이터를 넣는것.

 

char txt[20];
=> 배열. 동일한 자료형으로, 연속된 공간 20개를 메모리에 확보

txt 는 &가 없어도 배열의 시작지점 주소를 반환한다.

 

리눅스에서 ASLR 주소 랜덤화 기능이 기본적으로 활성화되어 있으므로,

같은 변수를 초기화 하여도 실행할 때마다 주소값이 달라진다.

 

변수 주소 출력

&aslr: 0xff89aabc
&aslr: 0xffc3341c
&aslr: 0xff8357bc ...

 

ASLR on/off는 커널 파라미터값으로 제어되는데, /proc/sys/kernel/randomize_va_space
이 값이 0이면 ASLR Off, 1과 2는 ASLR ON

 

int A = 10;

printf("%d \n", *&A);

=> 10이 출력된다. *는 주소의 값을 출력하고, &A는 A변수의 주소값을 의미하기 때문.

또, 연산순서가 *보다 &이 높기 때문에 * 와 &A로 분리됨.

 

포인터를 쓰는 이유

call by value => 값만 복사

call by reference => 주소에 직접 접근 가능

주소에 직접 접근하여 계산하기 위해.

 

포인터 사용

int a = 7;

int *p = &a;

printf("%d \n", *p);

=> a의 값인 7이 출력된다.

 

*

1) 산술 곱셈연산

2) 인다이렉션(간접참조) 연산자 (ex *&A )

3) 포인터 선언시 사용

 

echo 0 > /proc/sys/kernel/randomize_va_space

&aslr: 0xffffd4ac

&aslr: 0xffffd4ac

&aslr: 0xffffd4ac

 

=> ASLR을 OFF 시키자 변수 주소가 고정되었음

( 단, 이것은 임시적인 방법으로 부팅하면 다시 ON 된다 )

 

영구적으로 ASLR을 OFF 시키는 방법

vi /etc/sysctl.conf

/proc/sys/ 부분을 생략하고 / 슬래시를 . 으로 바꾸어 설정함

reboot하여도 0으로 변동없음

 

 

 

 /usr/libexec/gcc/x86_64-redhat-linux/8/cc1 -E aslr_test.c -o aslr_test.i

=> 컴파일러로 직접 -E 옵션을 사용해 .i 파일 생성

 

/usr/libexec/gcc/x86_64-redhat-linux/8/cc1 aslr_test.i 

=> 컴파일러로 aslr_test.o 파일 생성

 

 

eip 레지스터

=> 다음에 실행할 명령이 담겨있는 레지스터

 

(gdb) i r eip
eip            0x80484ee           0x80484ee <main+17>
(gdb) disas main
Dump of assembler code for function main:
   0x080484dd <+0>:     lea    ecx,[esp+0x4]
   0x080484e1 <+4>:     and    esp,0xfffffff0
   0x080484e4 <+7>:     push   DWORD PTR [ecx-0x4]
   0x080484e7 <+10>:    push   ebp
   0x080484e8 <+11>:    mov    ebp,esp
   0x080484ea <+13>:    push   ecx
   0x080484eb <+14>:    sub    esp,0x14
=> 0x080484ee <+17>:    mov    BYTE PTR [ebp-0x9],0x41

   0x080484f2 <+21>:    mov    WORD PTR [ebp-0xc],0x7e4

... 하략

 

=> 화살표로 표시된 곳이 next하면 실행될 명령어 위치를 의미함. eip 레지스터와 동일.

( mov 어셈블리어로 저장하라는 뜻 )

해당하는 소스코드 : c = 'A';

0x41이 'A'를 의미함

 

(gdb) disas /r main
Dump of assembler code for function main:
   0x080484dd <+0>:     8d 4c 24 04     lea    ecx,[esp+0x4]
   0x080484e1 <+4>:     83 e4 f0        and    esp,0xfffffff0
   0x080484e4 <+7>:     ff 71 fc        push   DWORD PTR [ecx-0x4]
   0x080484e7 <+10>:    55      push   ebp
   0x080484e8 <+11>:    89 e5   mov    ebp,esp
   0x080484ea <+13>:    51      push   ecx
   0x080484eb <+14>:    83 ec 14        sub    esp,0x14
   0x080484ee <+17>:    c6 45 f7 41     mov    BYTE PTR [ebp-0x9],0x41
=> 0x080484f2 <+21>:    66 c7 45 f4 e4 07       mov    WORD PTR [ebp-0xc],0x7e4
   0x080484f8 <+27>:    c7 45 f0 40 42 0f 00    mov    DWORD PTR [ebp-0x10],0xf4240

=> 한 번 next 한 후의 결과. <+0>, <+4>, <+7> 이런 숫자가 의미하는 바는 기계어 코드의 개수이다

0x080484dd <+0>:     8d 4c 24 04

=> 4개          <+4>

0x080484e1 <+4>:     83 e4 f0

=> 3개          <+7>

0x080484e4 <+7>:     ff 71 fc

=> 3개          <+10>

0x080484e7 <+10>:    55

=> 1개          <+11>

... 이런식으로 기계어 코드의 개수를 합산하여 표기된 것임

 

 

메모리 영역

      T(C)         D      B       H        S

Text(Code), Data, Bss, Heap, Stack

High Memory
- Stack - 
- Heap -
- Bss -
- Data -
- Text(Code) -
Low Memory

Text 영역에 올라간 데이터 주소가 0x080484dd, 0x080484e1, 0x080484e4 요런것들 ...

 

구분

        주소값    | offset    | 기계어코드    |  어셈블리코드      |

   0x080484dd <+0>:     8d 4c 24 04     lea    ecx,[esp+0x4]

 

next 한 번 한다고 해서 하나의 라인을 실행하는 것은 아니다.

소스코드는 한 줄이어도, 기계어코드/어셈블리 코드는 몇 줄씩 묶여있을 수 있다

 

ex)

24          f = 3.14f;
   0x080484ff <+34>:    fld    DWORD PTR ds:0x8048674
   0x08048505 <+40>:    fstp   DWORD PTR [ebp-0x14]

=> 소스코드 한 줄을 실행하는 데에 두 라인이 필요

 

next와 step 명령어

+ next (n) : 명령어를 실행한 후 다음줄로 이동한다. (함수 내부로 들어가지 않음)

여러 개의 어셈블리 명령어가 묶여 있을 때 한번에 여러개를 실행한다.

+ next instruction (ni) : 명령어를 실행한 후 다음줄로 이동하되, (함수 내부로 들어가지 않음)

여러 개의 어셈블리 명령어가 묶여 있을 때 하나씩 실행한다.

 

+ step (s) : 다음 줄로 이동한다 (함수 내부로 들어간다)

여러 개의 어셈블리 명령어가 묶여 있을 때 한번에 여러개를 실행한다.

+ step instruction (si) : 다음 줄로 이동한다. (함수 내부로 들어간다)

여러 개의 어셈블리 명령어가 묶여 있을 때 하나씩 실행한다.

 

(gdb) ni
5: $eip = (void (*)()) 0x8048520 <main+59>
5: $eip = (void (*)()) 0x8048520 <main+63>
5: $eip = (void (*)()) 0x8048520 <main+66>
5: $eip = (void (*)()) 0x8048520 <main+67>
(gdb) n
5: $eip = (void (*)()) 0x8048520 <main+80>

=> ni 하면 한 라인씩, 어셈블리 코드가 조금씩 수행된다.

n 하면 훌쩍 건너뛰어 여러개씩 수행된다.

 

증감연산자 ++, --

int a = 10;

printf("%d \n", ++a);

printf("%d \n", a++);

=> 11, 11

변수 앞에 붙으면 해당 라인에서 증가된다.

변수 뒤에 붙으면 해당 라인이 수행되고 난 뒤에 증가된다.

 

list 명령어로 소스코드를 10줄 볼 수 있다. (브레이크 포인트 기준)

이를 set listsize 50 이런식으로 늘릴 수 있는데,

영구적이지 않으므로 gdb에 다시 접속하면 10으로 돌아간다.

 

 

EBP 레지스터 : 스택 프레임의 시작주소인 맨 아래쪽(High Memory)을 가리킨다 (함수가 끝날때까지 고정)

ESP 레지스터 : 스택 프레임의 시작주소인 맨 아래쪽(High Memory)에서부터 시작하여, 함수가 실행되면서 이동된다. 다음 명령어가 실행되면서 쌓일 주소, 즉 현재 시점의 가장 맨 꼭대기(Low Memory로 내려감)를 가리킨다

 

 

x/16xw $ebp의 경우, 맨 아래쪽을 가리키기 때문에 -16을 해주어야,

16개 전 ~ 맨 아래쪽까지의 주소를 정상적으로 볼 수 있다.

 

 

스택은 기본적으로 16바이트로 정렬함
이 정렬을 빼주려면

gcc -m32 -Wall -fno-stack-protector -mpreferred-stack-boundary=2 -g ~
g++ -m32 -Wall -fno-stack-protector -mpreferred-stack-boundary=2 -g ~

 

컴파일 할 때 이렇게 설정해주어야 함

 

(gdb) disas main
Dump of assembler code for function main:
   0x080484ad <+0>:     push   ebp
   0x080484ae <+1>:     mov    ebp,esp
   0x080484b0 <+3>:     sub    esp,0x8
=> 0x080484b3 <+6>:     mov    DWORD PTR [ebp-0x4],0x2
   0x080484ba <+13>:    add    DWORD PTR [ebp-0x4],0x1
   0x080484be <+17>:    mov    eax,DWORD PTR [ebp-0x4]
   0x080484c1 <+20>:    mov    DWORD PTR [ebp-0x8],eax
   0x080484c4 <+23>:    push   DWORD PTR [ebp-0x4]
   0x080484c7 <+26>:    push   DWORD PTR [ebp-0x8]
   0x080484ca <+29>:    push   0x804856c
   0x080484cf <+34>:    call   0x8048350 <printf@plt>
   0x080484d4 <+39>:    add    esp,0xc
   0x080484d7 <+42>:    mov    eax,0x0
   0x080484dc <+47>:    leave
   0x080484dd <+48>:    ret
End of assembler dump.

 

=> 정렬 전보다 훨씬 코드양이 줄어든 것이 보인다.

 

(gdb) n
12          a = ++b; //  a = ?  b = ?
1: a = 0
2: b = 2
3: $esp = (void *) 0xffffd430
4: $ebp = (void *) 0xffffd438
5: $eip = (void (*)()) 0x80484ba <main+13>
(gdb) p &a
$1 = (int *) 0xffffd430
(gdb) p &b
$2 = (int *) 0xffffd434

 

=> ebp가 스택 프레임의 시작지점. 438이고, b가 434 a가 430. esp도 430.

4바이트 단위로 주소가 지정되고 있다는 것이 보인다.

426 427 428 429  
430 431 432 433 ← a, esp
434 435 436 437 ← b
438  ← ebp 위치

 

 

소스코드:  a = ++ b;

0x080484c5 <+24>:    add    DWORD PTR [ebp-0xc],0x1
0x080484be <+17>:    mov    eax,DWORD PTR [ebp-0x4]
0x080484c1 <+20>:    mov    DWORD PTR [ebp-0x8],eax

=> DWORD PTR [ebp-0xc]에  0x1를 add 하는 어셈블리 명령어

=> eax에 [ebp-0x4] ebp가 가리키는 주소로부터 0x4만큼 떨어진 곳을 저장시킨다

=> eax에 저장된 데이터를 [ebp-0x8] ebp가 가리키는 주소로부터 0x8만큼 떨어진 곳에 저장시킨다

램에서 램으로 바로 못 집어넣기 때문에 번거로운 과정을 거침

 

i r $eax
eax            0x3                 3

=> eax 레지스터에는 3이 들어가있다.

 

* 함수가 끝날 때 return 값은 레지스터 eax에 들어간다 (약속)

 

 

어셈블리어 문법

- intel : 윈도우에서 많이 사용

- at&t : 리눅스에서 많이 사용

os에서 서로 섞어서 사용할 수도 있다

intel 문법을 더 많이 사용하지만 둘 다 알아야 함

at&t 문법을 먼저 공부하고 나중에 intel 문법 학습

 

문법 차이

ADD

at&t

형식 : addl [제 1피연산자], [제 2피연산자]

intel

형식 : add [제 2피연산자], [제 1피연산자]

=> 제 1피연산자와 제 2피연산자를 더한 값을 2피연산자에 저장한다

 

PUSH

at&t

형식 : pushl [제 1피연산자]

intel

형식 : push [제 1피연산자]

=> 제1 피연산자를 스택에 저장한다. push되면 ESP값이 32bit(4Byte) 쌓이면서 감소한다.

 

MOV

at&t

형식 : mov [제 1피연산자], [제 2피연산자]

intel

형식 : mov [제 2피연산자], [제 1피연산자]

=> 제 1피연산자에 있는 값을 제 2피연산자에 저장한다

=> 제 1피연산자는 값, 레지스터, 메모리가 들어갈 수 있다. 제 2피연산자에는 값이 들어갈 수 없다.

=> 메모리 -> 메모리 이동은 불가능.

 

 

어셈블러 종류

- GAS : 리눅스, 유닉스 환경에서 사용. x86, ARM, MIPS 등 지원

- NASM : x86, x86-64 아키텍처에서 사용되는 오픈소스 어셈블러.

- MASM : 마이크로소프트사에서 개발했고 윈도우 환경에서 사용. 고급 어셈블리 언어 지원

 

어셈블리어 파일 생성

: n 파일명.s               ( GAS )

: n 파일명 .asm         ( NASM )

 

 

gcc -m32 -masm=intel -S 파일명.c

=> intel 문법으로 컴파일하는 경우

해당 옵션이 없으면 기본값인 at&t 문법으로 컴파일 한다.

 

intel 문법으로 컴파일한 .s 파일

=> 여기서 쓸데없는 코드를 제거하면

 

=> 이렇게 간략하게 정리된다.

=> at&t 문법으로 기본 코드를 만들어 놓은 상태. 

CPU 레지스터에 %가 붙는다.값은 $값으로 표시한다.

 

gcc -m32 -masm=intel -mpreferred-stack-boundary=2 -o sample sample.s

=> 컴파일하여 ./sample로 실행.

 

( 이외 레지스터들 : eax, ebx, ecx, edx )

컴파일하여 실행해도 printf가 없으므로 나오는 것이 없다

대신 gdb로 확인한다

 

disas main at&t문법

(gdb) b *0x0804847d
Breakpoint 1 at 0x804847d

=> 브레이크 포인트 설정. b main으로 걸어도 상관은 없음.

 

(gdb) ni
0x0804848f in main ()
1: $eip = (void (*)()) 0x804848f <main+18>
3: $eax = 1
4: $ebx = 2
5: $ecx = 3
6: $edx = -11052
7: $ebp = (void *) 0xffffd4a8

=> 코드가 없으므로 n으로 하면 한번에 휙 지나가서 안됨

ni로 하나씩 확인하면, 값이 하나씩 들어가는 것을 확인할 수 있다.

 

 

과제)

공유한 c언어 소스를 gdb로 분석 해보기

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

정보보안2 6차시  (0) 2024.05.19
정보보안2 5차시  (0) 2024.05.18
정보보안2 3차시  (0) 2024.05.11
과제)  (0) 2024.05.09
정보보안2 2차시  (0) 2024.05.05