본문 바로가기

dis & asm

disasmble 기초 정리 (1)


예전 글을 보니 역시 나도 보면서 정리가 깔끔하지 못했습니다.

다시 정리해서 글을 올려봅니다.

!! 저도 초보라서 틀릴수 있습니다. 고수분들 중에  문서에 틀린부분이 있으면 지적해 주세요 !!

예제)

int sum(int a, int b)
{
    int sum;

   sum=a+b;

   return sum;
}


int main()
{
    int a, b;
    int result;
   
    a=10;
    b=5;

    result=sum(a, b);

    printf("result = %d\n", result);

    return 0;
}

간단한 프로그램 입니다.

이걸 disasmble 해 보겠습니다.

저 같은 경우는 disasmble 할때 objdump 를 이용하여 파일을 추출한걸 열고 
gdb로 디버깅하여 서로 비교해 가면서 합니다.

gcc -g -o sum sum.c   <--   컴파일 하고

objdump -S sum > sum.txt  <--  바이너리파일에서 어셈코드를 추출합니다.

vi sum.txt  <--- 열어봅니다.(....)


gdb sum <-- gdb 로 디버깅 합니다.



프로그램이 실행되면 메모리에 올라갑니다.
밑에 그림은 프로세스(프로그램 실행상태)가 메모리에 저장된 구조 입니다.




위 그림에서 하나씩 살펴 보겠습니다.


# Argument of environnement  세그먼트(쉽게 말해 부분 , 영역 이라고 보시면 됩니다.)

여기에 들어가는 자료는 인자값과 환경변수입니다.

인자값은 int main(int argc, char *argv[])   <--- argc, argv   입니다.

환경변수는 일종의 설정 파일입니다. 

도스를 사용하신 분들은 쉽게 이해 하실텐데 autobat  <--- 이 파일 아시죠

처음 실행시 배치 파일입니다.

리눅스 콘솔에서 env 치시면 설정된 환경변수값들이 나옵니다.

자세한 설명은 "리눅스 환경변수" 검색해 보시길...


# stack 세그먼트

여기서 가장 중요한 부분이죠

스택은 지역변수가 저장되는 영역입니다.

변수가 증가하면 스택이 커지고 변수가 없어지면 스택은 다시 작아집니다.

커졌다 작아졌다 하는거죠 

위 그림에서 보듯이 스택이 자라는 방향은 밑으로 자랍니다.

이건 disasm 하면서 자세히 설명하겠습니다.


# heap 세그먼트

동적할당 영역입니다.

c언어에 malloc을 사용하면 여기에 할당이 됩니다.

스택과 달리 사용자가 할당하면 사용자가 해제를 시켜야 합니다.

프로그램이 종료될때까지 해제되지 않습니다.

뭐 이건 c언어를 해보신 분들이면 특별히 설명할게 없네요


# BSS 세그먼트

초기화 되지 않은 전역변수가 여기에 할당됩니다.


# DATA 세그먼트

초기화 된 전역변수나 static 변수가 할당됩니다.

static 변수는 값을 주지 않아도 자동으로 초기화 되죠?

그러니 data 세그먼트에 들어가는 겁니다.


BSS 세그먼트 와 DATA 는 정의된 값은 컴파일때 값이 들어가 있습니다


# CODE 세그먼트

다른 책들중에 TEXT 세그먼트 로 불리는 경우도 있습니다. 같은 말입니다.

소스 코드가 들어가 있습니다.

실제 소스 코드가 들어가 있는게 아니라 컴파일된 소스 코드입니다.

즉 2진수라는 말이죠  예를 들어

a=10;  ---> 0110 0110 1101  제가 그냥 숫자 아무 숫자나 넣은 겁니다. ;;

프로그램 실행시 code 주소를 읽어 cpu는 해석하는 겁니다.




자 이제 코드를 분석해 보겠습니다.

gcc 버전에 따라 어셈블러가 다르게 표현될수 있습니다.
제껀 gcc 4.3.2 최신버전..ㅡㅡ

main 함수부터..


/************************************************************/
080483da <main>:

int main()
{
 80483da:   8d 4c 24 04             lea    0x4(%esp),%ecx
 80483de:   83 e4 f0                and    $0xfffffff0,%esp
 80483e1:   ff 71 fc                pushl  -0x4(%ecx)
 80483e4:   55                      push   %ebp
 80483e5:   89 e5                   mov    %esp,%ebp
 80483e7:   51                      push   %ecx
 80483e8:   83 ec 24                sub    $0x24,%esp
    int a, b;
    int result;

    a=10;
 80483eb:   c7 45 f8 0a 00 00 00    movl   $0xa,-0x8(%ebp)
    b=5;
 80483f2:   c7 45 f4 05 00 00 00    movl   $0x5,-0xc(%ebp)

    result=sum(a, b);
 80483f9:   8b 45 f4                mov    -0xc(%ebp),%eax
 80483fc:   89 44 24 04             mov    %eax,0x4(%esp)
 8048400:   8b 45 f8                mov    -0x8(%ebp),%eax
 8048403:   89 04 24                mov    %eax,(%esp)
 8048406:   e8 b9 ff ff ff          call   80483c4 <sum>
 804840b:   89 45 f0                mov    %eax,-0x10(%ebp)

    printf("%d\n", result);
 804840e:   8b 45 f0                mov    -0x10(%ebp),%eax
 8048411:   89 44 24 04             mov    %eax,0x4(%esp)
 8048415:   c7 04 24 f0 84 04 08    movl   $0x80484f0,(%esp)
 804841c:   e8 d7 fe ff ff          call   80482f8 <printf@plt>

    return 0;
 8048421:   b8 00 00 00 00          mov    $0x0,%eax

/************************************************************/

코드가 꽤 길어 보이는데  이해하는데 어려움이 없을 겁니다.

하나씩 분석해 봅니다.

080483da <main>:  <-- main 의 시작 주소 입니다.

0x80483da <- 이부분은 코드가 저장된 주소값입니다.
위에 메모리 구조중에 0x80483da 는 CODE 세그먼트에 저장된 영역입니다.

프로그램이 실행되면서 CODE 세그먼트값을 읽습니다. 순서대로 읽죠

gdb 에서 info r 실행하면
%eip 라는 레지스터가 있습니다.
eip 레지스터에 들어있는 값이 CODE 세그먼트에 저장되어 있는 다음 실행 주소값입니다.

예를 들어
 80483da:   8d 4c 24 04             lea    0x4(%esp),%ecx  <--- 현재 여기를 실행하고 있음
 80483de:   83 e4 f0                and    $0xfffffff0,%esp

위의 상태라면 eip 값은 그 다음 주소값인 80483de 값을 가지고 있습니다.

cpu가 위의 문장을 처리하면 eip가 그 다음 주소를 가지고 있기 때문에 다음에 무엇을
실행해야 되는지 알 수 있습니다.

참고로 eip 값은 사용자가 마음대로 바꿀수 없습니다.
왜냐면 eip 주소를 바꾸면 프로그램 실행이 엉뚱한데로 이동하겠죠?
(해킹에 관심있는 분들은 아마 이쪽에 대해 잘 아실겁니다.)




 80483da:   8d 4c 24 04             lea    0x4(%esp),%ecx
 80483de:   83 e4 f0                and    $0xfffffff0,%esp
 80483e1:   ff 71 fc                pushl  -0x4(%ecx)

사실 이부분이 gcc 버전에 따라 차이가 많이 났습니다.
main 초기화 부분입니다.
그냥 넘어가도 됩니다. ( 저도 잘 몰라서.... ㅡㅡ)



 80483e4:   55                      push   %ebp
 80483e5:   89 e5                   mov    %esp,%ebp
 80483e7:   51                      push   %ecx
 80483e8:   83 ec 24                sub    $0x24,%esp
    int a, b;
    int result;

드디어 본격적으로 분석 시작이죠

 80483e4:   55                      push   %ebp
 80483e5:   89 e5                   mov    %esp,%ebp

함수 프롤로그라고 부릅니다.
함수가 시작하면 위에 있는 코드가 가장 먼저 시작한다는 말입니다.

main도 엄연히 함수입니다.

위의 두 문장은 스택을 이해하고 있어야 가능합니다.
여기서 ebp 레지스터와 esp 레지스터가 있습니다.

esp 레지스터는 스택의 맨끝에 위치하는 레지스터입니다.

예를 들어 현재 스택의 위치는 0x100 번지입니다. 여기에 20 의 값이 저장되어 있습니다.

push %ebp <-- ebp 는 스택의 기준을 가리키고 있습니다. ( 말이 이상한데...)

push <-- 스택에 값을 넣습니다. 여기서 값이란 ebp 값을 말하는 겁니다.

현재 ebp 값이 30 이 들어있으면


0x100    |      20        |
             |---------------------- |
0x9c      |      30        |   <------------------ ESP
             |                 |      

요렇게 됩니다.  즉 esp에는 0x9c가 저장되어 있겠죠?
처음에 제가 스택은 밑으로 증가된다고 말했습니다.
그리고 push를 하면 뒤에 레지스터 크기에 맞춰 값이 감소합니다.
ebp 는 레지스터 크기가 32bit 입니다 즉 4바이트 인거죠

그래서 -4바이트 감소되는 것입니다.

실제 gdb로 보면
(push %ebp 실행 전)

(gdb) p $ebp
$1 = (void *) 0xbf808558
(gdb) p $esp
$2 = (void *) 0xbf8084ec

(push %ebp 실행 후)

(gdb) p $esp
$4 = (void *) 0xbf8084e8   <---   -4 만큼 감소 했습니다.

(gdb) x $esp
0xbf8084e8:    0xbf808558 <--- ebp 값도 저장되어 있네요
 


잠깐 쉬었다 다시 올리겠습니다.