1. 취약점

     - 에러의 한 종류

     - 모든 취약점은 에러이다.

     - 모든 에러는 취약점이다. (X)


1). 메모리 변조:  버퍼(스택) 오버플로우


    - 1995년도에 처음 발표

    - smashing the stack for fun and profit

    - by Aleph One


    - 취약한 함수: scanf, gets, strcpy, ....


#include <stdio.h>

int main (int argc, char *argv[])

{


        char    buffer1[20]={0,};

        char    buffer2[20]={0,};


        if(argc > 1){

        strcpy(buffer2, argv[1]);  // vul!!!

        }


        printf("buffer1:%s\n",buffer1);

        printf("buffer2:%s\n",buffer2);


        return 0;

}


만약에 값이 20개가 넘는 글자가 들어갈 경우에 취약점이 발생한다 

buffer2[20] 이라서 남은 길이가 딴 공간에 침범한다

strcpy 문자를 복사 할때 길이를 전혀 체크하지 않는다

만약 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 비정상적인 입력값을 주어주면

Segmentation fault (core dumped) 오류가 발생해서 시스템이 멈춘다.

에러가 발생하면 취약점이 될 가능성이 있기 때문에 검사한다.


[root@korea vul]# ./target1 AAAAAAAAAAAAAAAAAAAAAAAAAAA

buffer1:AAAAAAA

buffer2:AAAAAAAAAAAAAAAAAAAAAAAAAAA


[root@korea vul]# ./target1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

buffer1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

buffer2:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Segmentation fault (core dumped)


디버거로 값을 확인해보면 

eip 값이 바뀐걸 확인할 수 있었다. GDB 디버거로 확인해보면 eip레지스터에 A의 아스키코드(41)값이 들어가있는걸 확인할수있습니다. 이것을 메모리 변조라고 한다. 

( 여기서 알 수 있는점은 입력 값의 길이를 체크하지 않는 함수를 사용했을때 해당 버퍼보다

큰 입력 값이 들어오게 된다면 다른 메모리 공간까지 덮어쓰게 된다는 점이다.

그리고 gdb에서 설정 해놓은 display 결과를 보면 알 수 있듯이 eip 레지스터의 주소 값 또한 덮어 써지게 된다는 것이다. )


이런 원리로 변조가 가능하다.


strcpy(buffer2, argv[1]);  // vul!!! 대신에

strncpy(buffer2, argv[1], 20); //를 사용하면 메모리 참조를 하지 않는다 .



gets


#include <stdio.h>


int main()

{

        char buffer1[20]={0,};

       char buffer2[20]={0.};


        gets( buffer2 );


        printf("buffer1:%s\n", buffer1);


        printf("buffer2:%s\n", buffer2);


        return 0;

}


[root@korea vul]# gdb -q target1

(gdb) set disassembly-flavor intel

(gdb) dis

disable      disassemble  display      

(gdb) display /i $eip

(gdb) r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Starting program: /root/vul/target1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

buffer1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

buffer2:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


Program received signal SIGSEGV, Segmentation fault.

0x41414141 in ?? ()

1: x/i $eip  0x41414141: Cannot access memory at address 0x41414141

Disabling display 1 to avoid infinite recursion.

0x41414141 AAAA  eip 값이 변조가 가능하다는 소리이다

변조가 되더라도 에필로그가 실행되지 않으면 오류가 발생하지 않는다.

취약점이 발생하는 위치에 따라서도 내가 변조할 수 있는 부분이 달라진다



[실습]

#include <stdio.h>


int main(int argc, char *argv[])

{

        char CAT[] = "CAT";

        char buffer[20]={0,};


        printf("WHAT:%s\n", CAT);


        if(argc>1){ strcpy(buffer,argv[1]);}


        printf("WHAT:%s\n", CAT);


        return 0;


}

             


[root@korea vul]# vi cat.c

[root@korea vul]# gcc -o cat cat.c

[root@korea vul]# ./cat

WHAT:CAT

WHAT:CAT

-소스코드를 수정하지 않고 다음과 같이 출력되도록 만들어라.


[root@korea vul]# vi cat.c

[root@korea vul]# gcc -o cat cat.c

[root@korea vul]# ./cat

WHAT:CAT

WHAT:DOG


[root@korea vul]# ./cat  AAAAAAAAAAAAAAAAAAAADOG

WHAT:CAT

WHAT:DOG



[root@korea vul]# vi cat.c

[root@korea vul]# gcc -o cat cat.c

[root@korea vul]# ./cat

WHAT:12345678

WHAT:12345678

-소스코드를 수정하지 않고 다음과 같이 출력되도록 만들어라.


[root@korea vul]# vi cat.c

[root@korea vul]# gcc -o cat cat.c

[root@korea vul]# ./cat

WHAT:12345678

WHAT:87654321


위와 같이 출력해보려고 같은 방법을 썼으나 이상한 값이 나온다.

-> 확인해보니 숫자도 문자로 인식하여 아스키 값으로 출력되는 것을 알 수 있었다.

-> 인자는 숫자 형태로 입력할 수 없다.

-> 문자열 이스케이프를 사용해야 문자열 내에서 숫자 표현이 가능해진다.

숫자를 넣고 변조 하고 싶으면 

#include <stdio.h>

int main(int argc, char *argv[])
{


        int buffer1 = 0x12345678;
        char buffer2[20]= {0,};

        printf("WHAT:%x\n",buffer1);
        if(argc > 1){ strcpy(buffer2, argv[1]);}
        printf("WHAT:%x\n",buffer1);

        return 0;
}

$(python -c 'print ("\x41\x41\x41\x41")')

`python -c 'print ("\x41\x41\x41\x41")'`


./cat $(python -c 'print "A" * 20 + "\x21\x43\x65\x87"')

! 문자열 이스케이프 -> 가능

! bash shell -> ', ", `, |, >



! bad  charachter


1. 0x00 (null 은 죽어도 입력이 안된다) : 입력할 방법이 전혀 없음

./cat $(python -c 'print "A" * 20 + "\x00\x00\x00\x00"') ( X )

-null 뒤에 다른 문자열을 넣는건 불가능하다, 마지막에 한 바이트 가능

-문자열의 끝을 널로 인식하는 경우에는 절대 불가능하다.


2. 0xff :bash 버그

-쉘 교체: bash2로


3. 0x20, 0x0a, 0x09: 화이트 스페이스


bash: ', "


./cat "$(python -c 'print "A" * 20 + "\x20\x20\x20\x20"')"

공백(0x20)과 tab(0x09) 정도는 바꿔서 사용이 가능하다.



! 쉘코드(Shellcode)


-타켓 프로세스에 메모리에 실행하고자 하는 명령들을 올려놓고 실행 


1. 기게어로 작성되어 있어야 한다.

2. 데이터 세그먼트는 사용할 수 없다.


권한을 얻고싶으면 쉘코드를 얻는게 가장 쉽다

system("/bin/sh"); -> 기계어 (bin)

--> execve --> 번호로 구분 


 - ELF파일 전체가 메모리에 있어야만 명령어를 실행할 수 있다.

- 여기선 기계어 코드만 메모리에 존재하기 때문에 실행될 수 없다.

해결책 - 세그먼트 없이 실행 가능하도록 스택형식으로 만들어야함.


라이브러리 함수의 주소가 다른 프로세스에서의 라이브러리 주소가 동일할 확률은 0에 가까움

해결책 - 시스템 콜 사용    


2) 어셈블리어로 작성

 - 직접 메모리를 제어가능

  - 기계어만 가지고 실행이 가능한 환경을 구성

  - 세그먼트 없이 프로그램을 작성

system 라이브러리 -> execve 시스템콜로 대체

세그먼트 -> 스택메모리로 대체

segment .text

global  _start


_start:


        mov     ebp,    esp

        sub     esp,    8


        push    0       ;ebp-12

        push    '//sh'  ;ebp-16

        push    '/bin'  ;ebp-20



        lea     eax,    [ebp-20]

        mov     dword [ebp-8], eax

        mov     dword [ebp-4],  0

        add     esp,    12


        mov     eax,    11

        mov     ebx,    [ebp-8]

        lea     ecx,    [ebp-8]

        mov     edx,    0

        int     80h

        add     esp,    8


        leave

        ret



3). 권한

- 권한 상승기법

- got root???

-SETUID: effective User ID

-해당 프로그램이 실행되는 동안만 소유자의 권한을 행사

-SETGID: dffective Group ID


setuid (  왜 굳이 취약점이 존재하는 프로그램 내에서 명령어를 실행하도록 할까?에 대한 대답 )

  - 권한이 없는 사용자라 할지라도 setuid가 있으면 해당 프로그램의 소유자의 권한을 행사할 수 있다.

  ! 타겟 프로그램에 취약점이 존재하고

  ! 타겟 프로그램에 setuid 권한이 존재한다면

  !! 권한 상승 가능


root: login -> id, pw -> 비교: /etc/shadow 

leviathan1@leviathan:~$ ls -l /etc/shadow

-rw-r----- 1 root shadow 1420 Nov 13 16:03 /etc/shadow


/etc/leviathan_pass

leviathan0:leviathan0


leviathan1:rioGegei8m

leviathan1@leviathan:~$ ls -l check

-r-sr-x--- 1 leviathan2 leviathan1 7608 Nov 13 16:03 check

-sr

-SETUID: effective User ID

check 라는 프로그램을 실행하는 동안에는 소유자의 권한으로 실행이된다 

보안상 각별한 주의가 필요한 파일이 SETUID가 걸려있는 파일이다


타켓 프로그램: 취약점 + SETUID 

ltrace ./check 로 해당 파일에 비밀번호를 획득하고

./check 를 실행시켜서 

ougahZi8Ta



leviathan3:


-hint: ", link 

"/etc/leviathan_pass/leviathan3



password 파일을 gdb를 활요하여 어셈블리 언어로 변경했다. 아래 코드는 전체 파일에 대한 코드이다 


Dump of assembler code for function main:

0x8048430 <main>: push   %ebp

0x8048431 <main+1>: mov    %ebp,%esp

0x8048433 <main+3>: sub    %esp,0x40c

0x8048439 <main+9>: push   0x8048580

0x804843e <main+14>: call   0x8048370 <printf>

0x8048443 <main+19>: add    %esp,4

0x8048446 <main+22>: lea    %eax,[%ebp-1024]

0x804844c <main+28>: push   %eax

0x804844d <main+29>: call   0x8048330 <gets>

0x8048452 <main+34>: add    %esp,4

0x8048455 <main+37>: lea    %eax,[%ebp-1024]

0x804845b <main+43>: push   %eax

0x804845c <main+44>: call   0x8048350 <strlen>

0x8048461 <main+49>: add    %esp,4

0x8048464 <main+52>: mov    DWORD PTR [%ebp-1036],%eax


0x804846a <main+58>: cmp    DWORD PTR [%ebp-1036],0

0x8048471 <main+65>: jle    0x8048497 <main+103>

0x8048473 <main+67>: mov    %eax,DWORD PTR [%ebp-1036]

0x8048479 <main+73>: dec    %eax

0x804847a <main+74>: lea    %edx,[%ebp-1024]

0x8048480 <main+80>: cmp    BYTE PTR [%eax+%edx],0xa

0x8048484 <main+84>: jne    0x8048497 <main+103>

0x8048486 <main+86>: mov    %eax,DWORD PTR [%ebp-1036]

0x804848c <main+92>: dec    %eax

0x804848d <main+93>: lea    %edx,[%ebp-1024]

0x8048493 <main+99>: mov    BYTE PTR [%eax+%edx],0x0

0x8048497 <main+103>: lea    %ecx,[%ebp-1024]

0x804849d <main+109>: mov    DWORD PTR [%ebp-1028],%ecx

0x80484a3 <main+115>: mov    DWORD PTR [%ebp-1032],0x80495d8


0x80484ad <main+125>: lea    %esi,[%esi]

0x80484b0 <main+128>: mov    %eax,DWORD PTR [%ebp-1028]

0x80484b6 <main+134>: cmp    BYTE PTR [%eax],0x0

0x80484b9 <main+137>: je     0x80484e0 <main+176>

0x80484bb <main+139>: mov    %eax,DWORD PTR [%ebp-1032]

0x80484c1 <main+145>: cmp    BYTE PTR [%eax],0x0

0x80484c4 <main+148>: je     0x80484e0 <main+176>

0x80484c6 <main+150>: mov    %eax,DWORD PTR [%ebp-1028]

0x80484cc <main+156>: mov    %edx,DWORD PTR [%ebp-1032]

0x80484d2 <main+162>: mov    %al,BYTE PTR [%eax]

0x80484d4 <main+164>: cmp    %al,BYTE PTR [%edx]

0x80484d6 <main+166>: je     0x80484e2 <main+178>

0x80484d8 <main+168>: jmp    0x80484e0 <main+176>

0x80484da <main+170>: lea    %esi,[%esi]

0x80484e0 <main+176>: jmp    0x80484f0 <main+192>

0x80484e2 <main+178>: inc    DWORD PTR [%ebp-1028]

0x80484e8 <main+184>: inc    DWORD PTR [%ebp-1032]

0x80484ee <main+190>: jmp    0x80484b0 <main+128>

0x80484f0 <main+192>: mov    %eax,DWORD PTR [%ebp-1028]

0x80484f6 <main+198>: cmp    BYTE PTR [%eax],0x0

0x80484f9 <main+201>: jne    0x8048515 <main+229>

0x80484fb <main+203>: mov    %eax,DWORD PTR [%ebp-1032]

0x8048501 <main+209>: cmp    BYTE PTR [%eax],0x0

0x8048504 <main+212>: jne    0x8048515 <main+229>

0x8048506 <main+214>: push   0x8048591

0x804850b <main+219>: call   0x8048370 <printf>

0x8048510 <main+224>: add    %esp,4

0x8048513 <main+227>: jmp    0x8048522 <main+242>

0x8048515 <main+229>: push   0x80485af

0x804851a <main+234>: call   0x8048370 <printf>

0x804851f <main+239>: add    %esp,4

0x8048522 <main+242>: xor    %eax,%eax

0x8048524 <main+244>: jmp    0x8048526 <main+246>

0x8048526 <main+246>: leave  

0x8048527 <main+247>: ret    

0x8048528 <main+248>: nop    

0x8048529 <main+249>: nop    

0x804852a <main+250>: nop    

0x804852b <main+251>: nop    

0x804852c <main+252>: nop    

0x804852d <main+253>: nop    

0x804852e <main+254>: nop    

0x804852f <main+255>: nop    



입력받은 코드를 분석해보면 


0x8048430 <main>: push   %ebp

0x8048431 <main+1>: mov    %ebp,%esp   

프롤로그를 표현한다.



0x8048433 <main+3>:         sub    %esp,0x40c

0x8048439 <main+9>:         push   0x8048580

0x804843e <main+14>: call   0x8048370 <printf>

0x8048443 <main+19>: add    %esp,4


sub로 저장할 위치를 확보하고 push후 printf 하는걸 보니 전역 변수 1036 바이트를 확보한후 printf 하는걸 확인할 수 있다.

0x8048580 값을 확인해 보면




 "input password: " 패스워드를 입력받는 문자열이 출력된다


지금까지 확인된 정보는 전역 변수 .지역 변수가 몇개 인지는 모르지만 총 1036 바이트의 저장공간을 확보했고 


int main(){

printf("Input password");

}

프린트 문장이 하나 출력되는걸 알 수 있다. 

0x8048446 <main+22>: lea    %eax,[%ebp-1024]

0x804844c <main+28>: push   %eax

0x804844d <main+29>: call   0x8048330 <gets>

0x8048452 <main+34>: add    %esp,4


lea로 주소를 불러오고 push 후 call로 gets 함수를 부르는걸 확인해보니 아마 1024 바이트를 사용하는 지역변수를 입력받는 부분같다

입력받은 값들이 1024 부터 저장되는걸 보니까 배열로 입력받는거 같다 


0x8048455 <main+37>: lea    %eax,[%ebp-1024]

0x804845b <main+43>: push   %eax

0x804845c <main+44>: call   0x8048350 <strlen>

0x8048461 <main+49>: add    %esp,4

0x8048464 <main+52>: mov    DWORD PTR [%ebp-1036],%eax

ebp-1024의 위치 주소를 가지고오고 strlen 을 하는거 보니 입력받은 배열의 길이를 ebp-1036에 저장한다

지금까지 C언어로 확인하면 크기가 1024인 배열이 하나 있는거 같고 그 배열의 길이를 저장하는 int형 변수가 있다. 


int main(){

?[bufffer] =1024;  //[ebp-1024]

?...                   //[ebp 1024~1036]

int length= //[ebp-1036]

int length=strlen(buffer);

printf("Input password");

}



0x804846a <main+58>: cmp    DWORD PTR [%ebp-1036],0

0x8048471 <main+65>: jle    0x8048497 <main+103>


0x8048473 <main+67>: mov    %eax,DWORD PTR [%ebp-1036]

0x8048479 <main+73>: dec    %eax

0x804847a <main+74>: lea    %edx,[%ebp-1024]

0x8048480 <main+80>: cmp    BYTE PTR [%eax+%edx],0xa

0x8048484 <main+84>: jne    0x8048497 <main+103>


0x8048486 <main+86>: mov    %eax,DWORD PTR [%ebp-1036]

0x804848c <main+92>: dec    %eax

0x804848d <main+93>: lea    %edx,[%ebp-1024]


0x8048493 <main+99>: mov    BYTE PTR [%eax+%edx],0x0


0x8048497 <main+103>: lea    %ecx,[%ebp-1024]

0x804849d <main+109>: mov    DWORD PTR [%ebp-1028],%ecx

0x80484a3 <main+115>: mov    DWORD PTR [%ebp-1032],0x80495d8


두개의 조건일때 같은 곳으로 지목하는걸 보니까 다중 조건문일 가능성이 크다/


조건 && 조건  인걸 확인 할 수 있다.

0x804846a <main+58>: cmp    DWORD PTR [%ebp-1036],0

0x8048480 <main+80>: cmp    BYTE PTR [%eax+%edx],0xa


if (length > 0 && buffer[length] == '\n'){

length--;

buffer[length]=0;

}

뉴라인을 제거하는 코드 같다.


0x8048497 <main+103> 라인을 보면 

[ebp-1024] 의 값을 ebp-1028에 저장하고 0x80495d8 에 있는 값을 [%ebp-1032] 여기에 저장한다.

배열로 입력받은 값을 ebp-1028에 저장하고 

0x80495d8의 값을   ebp-1032에 저장한다.

여기서 얻은 내용으로 C언어를 작성하면 


pwd="th3p4ssw0rd"

int main(){

char [bufffer] =1024;  //[ebp-1024]

char *p                   //[ebp-1028]

char *q                  //[ebp -1032]

int length= //[ebp-1036]

printf("Input password");

gets(buffer);

int length=strlen(buffer);

if (length > 0 && buffer[length] == '\n'){

length--;

buffer[length]=0;

}

p=buffer;

q=pwd ;


}

0x80484ad <main+125>: lea    %esi,[%esi]

0x80484b0 <main+128>: mov    %eax,DWORD PTR [%ebp-1028]  // p의 값을 eax에 저장

0x80484b6 <main+134>: cmp    BYTE PTR [%eax],0x0   //  eax에 저장된 값이 0이면 0x80484e0 이동

0x80484b9 <main+137>: je     0x80484e0 <main+176>

0x80484bb <main+139>: mov    %eax,DWORD PTR [%ebp-1032] //q의 값을 eax 에 저장 

0x80484c1 <main+145>: cmp    BYTE PTR [%eax],0x0 // 0이면  0x80484e0 이동

0x80484c4 <main+148>: je     0x80484e0 <main+176>


0x80484c6 <main+150>: mov    %eax,DWORD PTR [%ebp-1028]     //eax에 p의 값을 저장

0x80484cc <main+156>: mov    %edx,DWORD PTR [%ebp-1032]    //edx에 q의 값을 저장 

0x80484d2 <main+162>: mov    %al,BYTE PTR [%eax]                   //eax값을 al로 저장 

0x80484d4 <main+164>: cmp    %al,BYTE PTR [%edx]                   //즉 p의 값과 q의 값을 비교하고  

0x80484d6 <main+166>: je     0x80484e2 <main+178>                //같으면 0x80484e2 이동 

0x80484d8 <main+168>: jmp    0x80484e0 <main+176>              // 다르면 0x80484e0이동 



0x80484da <main+170>: lea    %esi,[%esi]

0x80484e0 <main+176>: jmp    0x80484f0 <main+192>    //jmp 192로 이동한다

0x80484e2 <main+178>: inc    DWORD PTR [%ebp-1028]  //p값을 1증가

0x80484e8 <main+184>: inc    DWORD PTR [%ebp-1032]  //q값을 1증가 

0x80484ee <main+190>: jmp    0x80484b0 <main+128>   // 반복문 실행


while(*p != 0 && *q ! = 0 && *p=*q ){

p++;

q++;

}


0x80484f0 <main+192>: mov    %eax,DWORD PTR [%ebp-1028]   // p값을 eax에 대입

0x80484f6 <main+198>: cmp    BYTE PTR [%eax],0x0                  //값이 0이 아니면 

0x80484f9 <main+201>: jne    0x8048515 <main+229>              //229로 이동 

0x80484fb <main+203>: mov    %eax,DWORD PTR [%ebp-1032]  //q값을 eax에 대입하고 

0x8048501 <main+209>: cmp    BYTE PTR [%eax],0x0                //값이 0이 아니면

0x8048504 <main+212>: jne    0x8048515 <main+229>            //역시 229로 이동한다

p,q의 값이 0을 가리키고 있으면 성공하는 문장이 출력되고 

0x8048506 <main+214>: push   0x8048591  

           

0x804850b <main+219>: call   0x8048370 <printf>

0x8048510 <main+224>: add    %esp,4

0x8048513 <main+227>: jmp    0x8048522 <main+242>

0x8048515 <main+229>: push   0x80485af            

아니면 실패하는 문장을 출력하고 프로그램이 종료된다

0x804851a <main+234>: call   0x8048370 <printf>

0x804851f <main+239>: add    %esp,4

0x8048522 <main+242>: xor    %eax,%eax  //eax를 초기화 하고 

0x8048524 <main+244>: jmp    0x8048526 <main+246>

0x8048526 <main+246>: leave  

0x8048527 <main+247>: ret    


if( *p == 0 && *q == 0 )

{

  printf("Congratulations! You got it!\n"

} else if

  {

    printf("Oops! wrong password! : -P\n"

  }


이제 내용을 종합해 보면

#include <stdio.h>

char pwd[]="th3p4ssw0rd";

//char *pwd ="th3p4ssw0rd";

int main()

{

char buffer[1024];  //[ebp-1024]

char *p;                   //[ebp-1028]

char *q;                  //[ebp -1032]

int length; //[ebp-1036]

printf("Input password:");

gets(buffer);

length=strlen(buffer);

if (length > 0 && buffer[length] == '\n'){

length--;

buffer[length]=0;

}

p=buffer;

q=pwd ;

while(*p != 0 && *q != 0 && *p==*q ){

p++;

q++;

}

if (*p==0 && *q==0){

        printf("Congratulations! You got it\n");

}

else{

        printf("Oops! wrong password! :-p\n");

}

return 0;

}




1. 디버거 사용법: GDB

 

 - bug: 프로그램에 존재하는 잠재적인 오류

 - de-bugging: 버그를 찾는 행위

 - de-bugger: 버그를 찾는걸 도와주는 도구

 

- shell로는 실행중인 메모리에 접근을 못하는데 디버거를 쓰면 접근이 가능하다

 - 대표적으로 IDA

   

가장 일반적인 것은

#>gdb ./hello2_

#>run      //#>file ./hello2  로 바꿔 쓸 수 있다.

#>quit


Program received signal SIGSEGV, Segmentation fault.

0x8048096 in ?? ()

버그가 나타난 곳을 알려준다


gdb 안에서는 절대 명령어만 사용된다



2. 자주 사용하는 GDB 명령어

-명령어 축약: run-> r 

-명령어 완성 : r<tab>, ru<tab>

*프로그램 실행: run

*GDB 종료: quit



*디스어셈블 : disassemble <주소>

  - 특정 주소에 있는 값을 자동으로 어셈블로 해석한다.

C프로그램을 분석할때는 주로 main으로 걸고 

어셈블리어로 구성된 파일을 분석할때는 _start 로 걸어서 분석한다



문법 변환: set disassembly-flavor intel or at

 - gdb에서는 intel방식이 아닌 at&t방식을 지원한다.

 - at&t방식 같은경우 intel과는 다르게 src와 dst가 바뀌어서 표현되는데

 - intel방식으로 배워온 입장에서 보기에 햇갈릴수도 있다. 그래서 이 기능으로 intel문법에 맞게 변환시켜준다.

기본값(default)은 xw이다. 16진수, 4바이트 형태


메모리 or 레지스터 값 확인


*레지스터값 확인: info

          ex) info register

          ex) info register eax ebx ecx


*메모리 값 확인: x/nfu 주소

 - 메모리 덤프에는 x/출력갯수/출력형태/출력단위 덤프할메모리주소

 - 기본형태 : (gdb) x/1xh

 - 출력형태 : 10진수(ㅇ), 8진수(o), 16진수(x), 문자열(s), 명령어(i)

 - 출력단위: 1바이트(b), 2바이트(h), 4바이트(w)


0xbffffb58                     0x400349cb                     0x00000001

saved ebp,                      eip                                    argc

의미한다 



ex) x/20xw = 4바이트 단위(w)로 짜른 메모리값을 16진수(x) 형태로 20개 출력

커널이 사용하는 메모리 말고 3기가는 어디든 확인할 수 있다

디버거라고 해도 커널영역은 사용이 불가능하다.



cpu에 따라 메모리에 작성되는 순서가 다르다


byte order 


little- Endian: Intel (뒤집에서 저장된다)     0x12 0x34 0x56 0x78

실제로 메모리에서는    0x78  0x56  0x34 0x12  


파일 맨 끝이 뒤집혀서 저장되는 것이 아니라 4바이트 기준으로 뒤집혀 들어간다



Big - Endian: Network,  (일반적으로 저장된다 )  0x12 0x34 0x56 0x78

실제로 메모리에서는 0x12 0x34 0x56 0x78


프로세스 실행 흐름 제어


프로세스 실행 멈춤: break points //멈추는 지점을 확인할 수 있다 (대상은 명령어) (s/w 타입)

(gdb) break *0x8048085   


info breakpoints

delete

disable/ enable


프로세스 다시 실행: continue

프로세스 한단계씩 추적: nexti(ni), stepi(si)

악성코드 동적분석-실행을 시켜가면서 분석을 하는것

정적 분석-실행을 하지 않고 분석하는 것 



명령어 등록 

display/i $eip



C-> ASM( 기계어 )


1. 시스템 프로그래밍

 

    - 윈도우즈: win API(Advanced Programing Interface)

    - 리눅스: system call



shell (user)

----------------

커널 

----------------

H/w (Device)

사용자가 커널을 통해 장치에 접근한다

인터페이스는 함수형태로 제공 

세개의 개념을 통틀어서 운영체제



! ltrace Vs. strace

 ltrace: 라이브러리 추적 명령어




 strace: 시스템콜 추적 명령어




리눅스 시스템 콜 같은경우 win api와는 다르게 웹상에 공개되어있어 쉽게 찾아볼 수 있다.

주로 이용하는 시스템 콜 참조 사이트


https://syscalls.kernelgrok.com/


write 함수를 살펴보면




2. write system call


1). C 언어

-systemcall과 동일한 래퍼 함수 제공

-fd: 0,1,2


<파일 디스크립터 번호>

할당되있는 파일디스크립터 번호

0 : 표준입력(키보드)

1 : 표준출력(모니터)

2 : 표준에러

(* 3번부터는 실행파일에 파일핸들을 준다.)


printf 대신 write 를 사용한다

#include <stdio.h>

int main()

{

        write(1,"Hello,world\n",13);

        return 0;

}



2). 어셈블리


- 함수 호출을 통한 시스템콜 함수는 사용하지 않는다.

- 인터럽트를 통한 시스템콜 함수를 사용


-하드웨어 인터럽트 (물리적인 인터럽트)

-소프트웨어 인터럽트 

WRITE 함수



_start:

        mov     eax,    0100b //eax 의 역할은 시스템콜의 번호를 저장한다 wirte 시스템콜이 4번이다. 

        mov     ebx,    1d      // fd =1 표준 출력을 의미한다

        mov     ecx,    string  //const char __user *buf

        mov     edx,    20o    // 최대 길이 

        int     80h //int 가 인터럽트고 80은 시스템콜을 의미한다


        차례 대로 쓰는 인자를 레지스터에 넣고 시스템콜을 한다  급한경우는 edi ,esi 까지 사용이 가능하다

        만약 인자가 3개가 넘어가면 스택을 사용한다.




read 함수



       ;read(0,path,1024);

        mov     eax,    3 ;call number 

        mov     ebx,    0 ;fd 값

        lea     ecx,    [ebp-1024] ;user *buf 사용하고자 하는주소

        mov     edx,    1024 ;최대 길이 

        int     80h





OPEN 함수


     ;open

        mov     eax,    5  ;시스템 콜 번호 

        lea     ebx,    [ebp-1024] ; open할 파일 이름

        mov     ecx,    0  ;flags

        mov     edx,    0 ;int mode

        int     80h



execve 함수



[실습]



1). 디렉터리 생성 :makedir


-read, write, mkdir


#> ./mkdir

path: path

success or fail


C언어로 연습


int main()

{

        char path[1024] = {0,};

        int end=0;


        read(0,path,1024);

        while( path[end] != '\n' ){

                end++;

        }


        path[end]=0;


        printf("%s",path);



        return 0;


}



  - read 는 \n까지 읽어들인다

  - read는 읽어들인 문자열의 길이를 반환

  - 메모리는 지역변수만 사용



C언어 


int main()

{

        char path[1024] = {0,}; // [ebp-1024]

        int end=0; // [ebp-1028]

        int result = 0; // [ebp-1032]


        write(1,"input:",7);

        read(0,path,1024);


        while( path[end] != '\n' ){  //마지막 라인을 찾아준다 

                end++;

        }


        path[end]=0;  // 뉴라인을 삭제 


        result=mkdir(path, 0755); // sys_mkdir 0x27(콜번호) const char __user *pathname int mode(권한)

     

       if (result < 0){

                write(1,"failed\n",7);

        } else {

                write(1, "success\n",8);

        }


        return 0;


}



 어셈블리어                                                                                                                                        

segment .data

fail    db      'failed',10,00

success db      'success',10,00

input   db      'input:',00




segment         .text

global  _start


_start:

        push    ebp  ;프롤로그 

        mov     ebp,    esp

        sub     esp,    1032 ;배열 1024 + 변수 2개 (8)   1032


        mov     ecx,    256  ; 배열 초기화 1024/4 256번 반복한다 

        mov     eax,    0

        lea     edi,    [ebp-1024]

rep     stosb                                   ;path 초기화



        mov     dword [ebp-1028],       0       ;end 초기화

        mov     dword [ebp-1032],       0       ;result 초기화


      

  ;write(1,"input:",7);


        mov     eax,    4 ;call number

        mov     ebx,    1 ; 표준 출력

        mov     ecx,    input ; const char __user *buf

        mov     edx,    7 ; 최대 길이

        int     80h ; 시스템콜 


        ;read(0,path,1024);

        mov     eax,    3 ;call number 

        mov     ebx,    0 ;표준 입력 0

        lea     ecx,    [ebp-1024] ;const char __user *buf

        mov     edx,    1024 ;최대 길이 

        int     80h ; 시스템콜 


while:


   lea     eax,    [ebp-1024] ;path start address

        mov     ebx,    [ebp-1028] ;end memory

        cmp     byte [eax+ebx*1],       10 ;new 라인 인지 비교한다 

        je      end ;뉴 라인이면 end로 jmp 하고

        inc     dword [ebp-1028] ;end 값을 하나 늘려서 while 문을 반복한다

        jmp     while


end:

        ;path[end]=0;

        lea     eax,    [ebp-1024]  ;path 시작 위치를 eax에 두고

        mov     ebx,    [ebp-1028] ; [ebp -1028] end 를 ebx에 저장한다

        mov     byte [eax +ebx *1],     0 [eax+ebx *1] 에 0을 저장한다  


        ;result=mkdir(path, 0755);

        mov     eax,    27h ;mkdir 콜 번호

        lea     ebx,    [ebp-1024] // 시작 위치

        mov     ecx,    0755o // 권한 설정 

        int     80h ; 시스템콜 


        mov     dword [ebp-1032],       eax  ; 값을 1032 result 에 저장한다 



      ;if (result < 0){

                ;write(1,"failed\n",7);

        ;} else {

              ;write(1, "success\n",8);

        ;}


if:

        cmp     dword [ebp-1032],       0 ;result 값을 비교하고 

        jge     .else ;크거나 같으면 .else로 이동한다 


        mov     eax,    4 ;write 시스템 콜 번호

        mov     ebx,    1 ;fd 표준 입출력 

        mov     ecx,    fail ; fail 출력 

        mov     edx,    7 ;최대 길이 

        int     80h ; 시스템콜 

        jmp     .end ;분기문



.else:

        mov     eax,    4

        mov     ebx,    1

        mov     ecx,    success

        mov     edx,    8

        int     80h


.end:

        xor     eax,    eax

        leave

        ret



2). 파일 입/출력: mycat

- open, read, wirte


#> ./mycat

input: /etc/passwd

......

read를 한번 쓸때 마다 얼만큼 썼는지 리턴이 될것이다.

open 할때 flag 0번 

  mode 는 0으로 하면된다


C언어 

int main()


{

  char path[1024] = {0,};

  char buffer[1024] = {0,};

  int size = 0;

  int fd = 0;

  int end = 0;



  write( 1, "input: ", 7 );

  read( 0, path, 1024 );


  while( path[end] != '\n' ) {

    end++;

  }

  

path[end] = 0;


  fd = open( path, 0, 0 );

  size = read( fd, buffer, 1024 );



  while(size == 0) {

    write( 1, buffer, 1024 );

    size = read( fd, buffer, 1024 ); 

}


 어셈블리어 

segment .data

input   db      'input: ', 00


segment .text

global  _start


_start:

        push    ebp                ;프롤로그

        mov     ebp,    esp        

        sub     esp,    2060      ;1024+1024+4+4+4 


        mov     ecx,    512       ;2048 을 한번에 초기화 한다

        mov     eax,    0          ; eax에 0을 넣어주고

        lea       edi,    [ebp-2048] ; 배열 시작 위치 

        rep     stosd ; 반복해서 초기화 한다 


        mov     dword [ebp-2052],       0       ; size

        mov     dword [ebp-2056],       0       ; fd

        mov     dword [ebp-2060],       0       ; end


        ;write

        mov     eax,    4        ;시스템콜 번호    

        mov     ebx,    1        ;파일 출력

        mov     ecx,    input   ; *buf

        mov     edx,    7        ;최대 사이즈

        int     80h                ;시스템콜


        ;read

        mov     eax,    3        ;시스템 콜 번호

        mov     ebx,    0        ;파일 입력

        lea     ecx,    [ebp-1024]     ;path 시작 주소

        mov     edx,    1024        ;최대 길이

   int     80h                   ;시스템 콜


while1:


        lea     eax,    [ebp-1024]        ;path 시작 주소 위치

        mov     ebx,    [ebp-2060]      ;end  값 ebx에 저장 

        cmp     byte [eax + ebx * 1],   10 ; eax+ end *1이 뉴라인 인지 비교한다 

        je      .end ; 뉴라인이면 .end로 이동 

        inc     dword [ebp-2060] ; end값을 1씩 증가 해서 while1 반복문 처리한다

        jmp     while1




.end:

;path[end] = 0;


        lea     eax,    [ebp-1024]  ;path 시작위치 주소를 가지고오고

        mov     ebx,    [ebp-2060]  ; end가 뉴라인인 값을 가지고 오고 ebx에 저장한다  

        mov     byte [eax + ebx *1],    0  ; eax+end가 뉴라인 *1 ,0을 대입해준다 



        ;open

        mov     eax,    5                ;시스템 콜번호

        lea     ebx,    [ebp-1024]    ;open할 파일 이름

        mov     ecx,    0                ;flag

        mov     edx,    0                ;open 0

        int     80h                        ; 시스템콜


        mov     dword [ebp-2056],       eax     eax를 fd에 저장 한다 



    ;size = read( fd, buffer, 1024 );

        mov     eax,    3                 ;시스템 콜 번호

        mov     ebx,    [ebp-2056]    ;위에 저장한 fd 번호 [ebp-2056]

        lea     ecx,    [ebp-2048]      ;시작 위치 주소를 가져온다 buffer의 시작 주소

        mov     edx,    1024            ;최대 길이         

        int     80h                         ; 시스템 콜


        mov     dword [ebp-2052],       eax      ;size 에 대입해준다


while2:


        cmp     dword [ebp-2052],       0    ;;size 가 0 즉 남은 내용이 없으면 end로 끝낸다

        je      end ;에필로그 하고 끝낼려고 


        ;write

        mov     eax,    4                ;만약 size가 남았으면 write 해준다 콜번호

        mov     ebx,    1                ;fd 1

        lea     ecx,    [ebp-2048]     ;시작 위치

        mov     edx,    1024           ; 최대 길이

        int     80h                        ; 시스템 콜


        ;read

        mov     eax,    3

        mov     ebx,    [ebp-2056]  ;fd 번호 

        lea     ecx,    [ebp-2048]

        mov     edx,    1024

        int     80h


        mov     dword [ebp-2052],       eax        ;size에 대입해준다

        jmp     while2


end:


        xor     eax,    eax

        leave

   ret



3). 쉘: myshell

- read, execve

-명령어를 입력-> 실행

-execve, read

int execve (const char *filename, char *const argv [], char *const envp[]);


1. 실행할 명령어의 종류

2. 아규먼트(포인터 배열)

3. 환경 변수에 대한 포인터


#include <stdio.h>

int main()
{


        char *argv[2]= {0,}; //[ebp-8]
        char buffer[1024] ={0,};// [ebp-1032]
        int  end =0; // [ebp-1036]

        write(1, "input:", 7);
        read( 0, buffer, 1024);

        while( buffer[end] != '\n') {
         end ++;
        }

        //while (*(buffer+end) != '\n') {
end ++;
        }


        buffer[end] = 0;

        argv[0] = buffer;   // [ebp-8]
        argv[1] = NULL;    // [ebp-4]

        execve( buffer, argv, NULL);

        return 0;

}


1.변수 초기화

        char *argv[2]= {0,}; //[ebp-8]
        char buffer[1024] ={0,};// [ebp-1032]
        int  end =0; // [ebp-1036]


_start:
        push    ebp     
        mov     ebp,    esp     
        sub     esp,    1036

        mov     dword [ebp-4],  0
        mov     dword [epb-8],  0

        mov     ecx,    256
        xor     eax,    eax
        lea     edi,    [ebp-1032]
rep     stosb

        mov     dword [ebp-1036],       0

        
2. write, read

  write(1, "input:", 7);
        read( 0, buffer, 1024);

  
        mov     eax,    4
        mov     ebx,    1
        mov     ecx,    prompt  
        mov     edx,    7
        int     80h


        mov     eax,    3
        mov     ebx,    0
        lea     ecx,    [ebp-1032]
        mov     edx,    1024
        int     80h


3, 뉴라인 제거 
        while( buffer[end] != '\n') {
         end ++;
        }

        //while (*(buffer+end) != '\n') {
end ++;
        }
   buffer[end] = 0;


loop:   

        lea     eax,    [ebp-1032]      ;buffer
        mov     ebx,    [ebp-1036]      ;end
        cmp     byte [eax + ebx * 1], 10
        je      .end
        inc     dword [ebp-1036]
        jmp     loop
.end:   
        lea     eax,    [ebp-1032]
        mov     ebx,    [ebp-1036]
        mov     byte [eax + ebx *1], 0


4.

lea     eax,    [ebp-1032]
        mov     dword [ebp-8],  eax
        mov     dword [ebp-4],  0

        mov     eax,    11
        lea     ebx,    [ebp-1032]
        lea     ecx,    [ebp-8]
        mov     edx,    0
        int     80h

        xor     eax,    eax

        leave
        ret


main 함수의 인자



-int main();

-int main( int argc, char *argv[ ] );


주소냐 메모리냐 구분만 잘해주면 된다.


  #> ./a.out hello

  hello

      * 위 내용은 hello라는 인자를 받아서 그 인자의 내용인 hello를 출력하는 프로그램이다.


  - 포인터 배열 사용

  - 포인터 배열: 배열인데 주소를 원소로 하는

  - 배열 포인터: 포인터인데 배열을 나타내는



* int argc 인자의 경우 argument의 갯수를 나타낸다.

* 실행 프로그램의 이름도 argument로 친다. ( 첫번째 인자 )

* char *argv[]의 시작 주소를 가져올때는 [ebp+12] 위치에서 가져온다.



먼저 C 언어로 확인해 보면


1).인자가 없는 경우


int main( int argc, char *argv[] )

{

   printf("argc: %d\n", argc);  // 인자의 개수, 최소는 arg1, null 이다

   printf("argv: 0x%08x\n", argv);  // argv의 주소

   printf("*argv: 0x%08x\n", *argv);  // 주소의 메모리 호출 거기에도 주소가 들어있음.   [argv]

   printf("*argv[]: %c\n", **argv);  // 메모리의 첫번째 문자  [[argv]] 

   printf("*argv[]: %s\n", argv[0]); // 첫번째 인자의 메모리. 문자열을 출력한다

   printf("%s\n", argv[1]);  // 두번째 인자의 문자열을 호출

   return 0;

}






어셈블리


extern printf


segment         .data

prompt_hex      db      'address:0x%08x',10,00

prompt_chr      db      'memory: %c',10,00

prompt_int      db      'argc: %d',10,00


segment         .text

global  main


main:


        push    ebp

        mov     ebp,    esp


        push    dword [ebp+8]

        push    prompt_int

        call    printf

        add     esp,    8



        mov     eax,    [ebp+12]

        push    eax

        push    prompt_hex

        call    printf

        add     esp,    8


        mov     eax,    [ebp+12]

        mov     ebx,    [eax]

        push    ebx

        push    prompt_hex

        call    printf

        add     esp,    8


        mov     eax,    [ebp+12]

        mov     ebx,    [eax]

        mov     ecx,    [ebx]

        push    ecx

        push    prompt_chr

        call    printf

        add     esp,    8


        xor     eax,    eax


        leave

        ret



문자를 넘겨줄때는 주소를 넘겨줘야 %s로 온전히 출력이 가능하다



   push    ebp

        mov     ebp,    esp


        push    dword [ebp+8]

        push    prompt_int

        call    printf

        add     esp,    8


[ebp + 8] 에는 argc 값이 들어 있다. 

따라서 printf 하면 argc 값이 들어가 있을것이다



   mov     eax,    [ebp+12]

        push    eax

        push    prompt_hex

        call    printf

        add     esp,    8


[ebp+12] 에는 argv 의 주소 값이 들어가 있다

따라서 printf 하면 argv의 주소 값이 출력된다




   mov     eax,    [ebp+12]

        mov     ebx,    [eax]

        push    ebx

        push    prompt_hex

        call    printf

        add     esp,    8



[ebp+12] 에 한번더 [] 를 씌우면 그 주소가 나타내는 포인터 배열이 한번더 출력된다  그 값은 주소 값이다




mov     eax,    [ebp+12]

        mov     ebx,    [eax]

        mov     ecx,    [ebx]

        push    ecx

        push    prompt_chr

        call    printf

        add     esp,    8



[[[ebp+12]]]  이렇게 되면 이제 메모리 값이 출력된다

그 값은 인자의 첫번째 글자 일것이다.


 









ㅇ예제는 저번에 사용했던 reverse.asm 을 사용하고


1. reverse(스택)

-문자 배열


C언어


int main()

{

char buffer[1024]={0,};

int len = 0;

printf("input:");

gets(buffer);

while (buffer[len] != 0) {

len++;

}

len --;

while(len>=0){

printf("c",buffer[len]);

len--;

}

printf("\n");

return 0;

}


어셈블리 언어


extern printf

extern gets



segment         .data

prompt1         db      'input:',00

prompt2         db      '%c',00

prompt3         db      10,00

prompt4         db      '%s',10,00


segment         .text

global  main


main:

        push    ebp

        mov     ebp,    esp

        sub     esp,    1028


        mov    ecx,    256

        xor     eax,    eax

        lea     edi,    [ebp-1024]

        rep    stosd


        mov     dword [ebp-1028],       0


        push    prompt1

        call      printf

        add     esp,    4


        lea     eax,    [ebp-1024]

        ;mov    eax,    ebp

        ;sub    eax,    1024

        push    eax

        call    gets

        add     esp,    4


while1:

        mov     ebx,    dword [ebp-1028]

        cmp     byte [ebp-1024 +  ebx * 1],     0

        je      .end

        inc     dword [ebp-1028]

        jmp     while1


.end:

        dec     dword [ebp-1028]


while2:

        mov     ebx,    dword [ebp-1028]

        cmp     ebx,    0

        jl      .end


        movzx   eax,    byte [ebp-1024 + ebx * 1]

        push    eax

        push    prompt2

        call    printf

        add     esp,    8


        dec     dword [ebp-1028]

        jmp     while2


.end

        push    prompt3

        call    printf

        add     esp,    4


        xor     eax,    eax


        leave

        ret


stack 에서 변수 공간을 할당할때는 선언된 순서대로 잡아준다

배열의 시작주소는 지정된 크기중 가장 낮은 주소에 위치한다.


처음 부터 확인해 보면 

        push    ebp

        mov     ebp,    esp

        sub     esp,    1028


프롤로그 함수와 변수 2개의 공간을 확보한다.


그림으로 확인해 보면 


len 시작 위치는 dword [ebp-1028]

1024 buffer buffer 시작위치는 byte [ebp-1024] 

saved ebp

saved eip



배열의 시작주소는 지정된 크기중 가장 낮은 주소에 위치한다.


배열이 시작하는 위치를 알아야 불러오는게 가능하다

byte [ebp-1024]

byte [ebp-1023]

byte [ebp-1022]

byte [ebp-1021]





지역변수는 꼭 초기화로 해주어야 한다.

stack은 여러 프로그램이 사용하기 때문에 꼭 초기화 해주어야한다.

 

배열의 크기가 작을때는 

 mov     dword [ebp-1024],       0

 mov     dword [ebp-1020],       0

 ...

이런식으로 초기화 해준다.


       mov    ecx,    256

        xor     eax,    eax

        lea     edi,    [ebp-1024]

        rep    stosd



ecx 카운터

edi 목적지 주소


한칸에 4바이트씩 구성되어 있기 때문에 256 번 반복을 해서 ecx에 256을 대입하고

eax 를 xor을 이용해서 초기화 시켜준다


lea edi, [ebp-1024]

배열의 시작 주소인 [ebp-1024] 를 edi에 저장한다

edi는 목적지 주소이다

rep stosd 반복한다



그렇치 않으면 ESI, EDI:STOS, LORD, REP ... 레지스터를 이용해서 초기화 한다.

extern printf

extern gets



초기화 하는 방법 

char buffer[1024]={0,};


main:

        push    ebp

        mov     ebp,    esp

        sub     esp,    1028


        mov     ecx,    1024

        xor     eax,    eax

        mov     al,     65

        lea     edi,    [ebp-1024]

        rep     stosb


        lea     eax,    [ebp-1024]

        push    eax

        push    prompt4

        call    printf


        add     esp,    8

        leave

        ret



 ;mov    edi,    [ebp-1024] //메모리의 값을 가지고온다.

 lea     edi,    [ebp-1024] //[]있다고 메모리가 아닌 주소의 값을 가지고온다.



mov     dword [ebp-1028],       0

len 변수를 0으로 초기화 시켜준다

        push    prompt1
        call      printf
        add     esp,    4

입력 받는 문장을 출력하고 add로 esp를 돌려준다


lea     eax,    [ebp-1024]
        ;mov    eax,    ebp
        ;sub    eax,    1024
        push    eax
        call    gets
        add     esp,    4

이제 배열에 입력받은 값을 저장해야하는데 
lea로 배열 처음 시작 주소인 [ebp-1024] 로 이동해도 좋고 
아니면 mov로 eax, ebp  기준점으로 이동한후 sub로 1024를 빼주어도 시작 주소이다
call gets 함수를 부르고  add esp, 4 해준다


// 여기 까지가 C 언어 에서 
char buffer[1024]={0,};
int len = 0;
printf("input:");
gets(buffer);

변수 값을 초기화 해주고 배열buffer 에 입력받아 저장해 주었다.

while (buffer[len] != 0) {
len++;
}
len --;

반복문 처리를 해주면

while1:
        mov     ebx,    dword [ebp-1028]
        cmp     byte [ebp-1024 +  ebx * 1],     0
        je      .end
        inc     dword [ebp-1028]
        jmp     while1

.end:
        dec     dword [ebp-1028]


len의 주소인 [ebp-1028] 의 값을 ebx에 저장하고
ebx=0이 저장된다
배열에 abc가 저장되었다고 생각하면 

cmp     byte [ebp-1024 +  ebx * 1],     0

cmp byte [ebp-1024 배열의 시작 주소 + 0 ], 0 
이렇게 되면 배열의 시작 주소와 0을 비교해서 같으면 .end 로 점프한다
배열의 시작이 a 니까 0이 아니라서  아래 문장이 실행된다

        inc     dword [ebp-1028]
        jmp     while1

ebp-1028 즉 len 이 1 증가하고 무조건 분기로 while 로 이동한다

위의 과정을 그림으로 표현하면  



이렇게 표시가 될것이다 

이제 len의 값이 하나씩 늘어서 만약 4가 되면 cmp 비교 값에 의해 .end 로 이동하고 반복문은 종료가 될것이다 


.end:
        dec     dword [ebp-1028]

.end 에서는 len 의 값을 1 감소 시켰다 0나온 값보다 하나 작아야 마지막 값을 가리키는 배열 개수가 출력되기 때문이다

while(len>=0){
printf("c",buffer[len]);
len--;
}

두번째 반복문을 처리해주면 

while2:
        mov     ebx,    dword [ebp-1028]
        cmp     ebx,    0
        jl      .end

        movzx   eax,    byte [ebp-1024 + ebx * 1]
        push    eax
        push    prompt2
        call    printf
        add     esp,    8

        dec     dword [ebp-1028]
        jmp     while2

.end
        push    prompt3
        call    printf
        add     esp,    4



mov     ebx,    dword [ebp-1028]
마지막 배열값인 len 을 ebx에 저장하고
cmp ebx, 0 
jl     .end

len 의 값을 비교해서 0보다 작으면 ,end 로 이동하고  그전에는 이제 배열에 있는 값을 출력해야한다
movzx   eax,    byte [ebp-1024 + ebx * 1]

abc를 대입했다고 가정했으니까 len은 3이 될것이다 ebx에 3을 대입하고 movzx를 수행하면 eax에는 배열 마지막 값인 c가 저장될 것이다.

배열의 값을 끝에서 부터 1개씩 출력한다. 
그리고 esp를 push 한 만큼 돌려주고 len 값을 하나씩 낮춰서 반복문을 진행하고 끝난다



        xor     eax,    eax

        leave
        ret

에필로그 해주고 끝난다 

형식 : MOVZX [OPER1] [OPER2]
의미 : OPER2의 내용을 OPER1에 복사한다. 이때, 확장되고 채워지지 않는 나머지 비트(공간)를 '0'으로 채운다.

위 MOV 같은 경우는 OPER1과 OPER2의 사이즈가 동일하여야지 가능하다. 하지만 이 명령어는 두 OPER 간의 사이즈가 다를 경우 사용된다. 기본적으로, 복사하는 곳의 사이즈가 OPER1의 사이즈가 OPER2의 사이즈보다 커야한다. 때문에, MOVZ'X'의 'X'가 확장의 의미이다.



2. 배열의 총합(스택)

-숫자 배열

-int sum(int *array, int len);



1), C 언어

 

           [ebp+8]    [ebp+12]

int sum(int *array, int len)

{

int sum=0;      [ebp-4]

len --;

for( ; len > 0; len--){

sum = sum + array[len];

}

return sum;

}


int main( )

{


int array[ ]={10,20,30,40,50};

int result = 0;

result = sum( array,5 );

printf("sum:%d\n",result);


return 0;


}



2). 어셈블리


extern printf


segment         .data

prompt          db      'sum:%d',10,00



segment         .text

global  main


sum:

        push    ebp

        mov     ebp,    esp

        sub     esp,    4


        mov     dword [ebp-4],  0

        dec     dword [ebp+12]

for:

        mov     ebx,    dword [ebp+12]

        cmp     ebx,    0

        jl      end

        mov     edx,    dword [ebp+8]

        mov     eax,    [ edx + ebx *4 ]

        add     dword [ebp-4],eax

        dec     dword [ebp+12]

        jmp     for


end:


        mov     eax,    dword [ebp-4]

        leave

        ret


main:

        push    ebp

        mov     ebp,    esp

        sub     esp,    24


        mov     dword [ebp-4], 10

        mov     dword [ebp-8], 20

        mov     dword [ebp-12], 30

        mov     dword [ebp-16], 40

        mov     dword [ebp-20], 50

        mov     dword [ebp-24], 0


        push 5

        lea     eax,    [ebp-20]

        push    eax

        call    sum

        add     esp,    8


        mov     dword [ebp-24],eax

        push    dword [ebp-24]

        push    prompt

        call    printf

        add     esp,    8


        xor     eax,    eax



        leave

        ret



이것도 하나하나 분석해보면 


main:

        push    ebp

        mov     ebp,    esp

        sub     esp,    24


배열 20바이트에 result 4 바이트해서 총 24 바이트 값을 확보한다

배열이 초기화가 되어 있는 경우에는 반복문으로 입력하는게 아니라 하나하나 입력해준다 

 


   mov     dword [ebp-4], 10

        mov     dword [ebp-8], 20

        mov     dword [ebp-12], 30

        mov     dword [ebp-16], 40

        mov     dword [ebp-20], 50

        mov     dword [ebp-24], 0


배열의 시작 위치는 ebp-20 이고 

시작부터 반대로 대입시켜 준다 50,40,30,20,10 순서대로 초기화 시켜주고

dword [ebp-24] result 값을 0으로 초기화 시켜준다


이제 sum 함수를 출력하는데  

result = sum( array,5 );


    push 5

        lea     eax,    [ebp-20]

        push    eax


뒤에서 부터 입력하고 call 해주어야 한다 

우선 숫자 값인 5를 push 하고 array의 시작 주소인 [ebp-20] 를 eax에 넣고 push 시켜준다.


        call    sum

        add     esp,    8

이제 sum 함수를 호출 시켜주고 push push 에 대한 esp 메모리를 회수한다

call 함수는 


push eip +jmp sum = call sum

복귀 주소인 eip를 저장하고 jmp sum 하는 의미를 가지고 있다





   

sum:

        push    ebp

        mov     ebp,    esp

        sub     esp,    4


        mov     dword [ebp-4],  0

        dec     dword [ebp+12]

for:

        mov     ebx,    dword [ebp+12]

        cmp     ebx,    0

        jl      end

        mov     edx,    dword [ebp+8]

        mov     eax,    [ edx + ebx *4 ]

        add     dword [ebp-4],eax

        dec     dword [ebp+12]

        jmp     for


end:


        mov     eax,    dword [ebp-4]

        leave

        ret



sum 함수로 와서 보면 

int sum=0; 


지역 변수가 있으니까 프롤로그 하고 sub 4 로 공간을 확보 했다


 mov     dword [ebp-4],  0

 dec     dword [ebp+12]


sum 변수 값을 초기화 시켜주고 






push 5 값인 ebp+12 를 1 감소 시켜준다. 



mov   ebx,    dword [ebp+12]

        cmp     ebx,    0

        jl      end

        mov     edx,    dword [ebp+8]

        mov     eax,    [ edx + ebx *4 ]

        add     dword [ebp-4],eax

        dec     dword [ebp+12]

        jmp     for




이제 위에 했던거 처럼 반복문을 수행하고 main으로 리턴 시켜준다 












3. 함수

-함수(function)이라는 개념이 없다.

-스택 메모리의 이해


// sub routine

// procedure -> 절차(x)


int sum( int a, int b)


{

return a+b

}


int main()

{

int result =0;

result =sum(10,20);

printf("sum:%d\n",result);


return 0;

}



지역 변수는 함수가 호출될때 생성되고 끝나면 메모리에서 없어진다

스택메모리를 이용해서 함수를 표시한다.



1. 실행중인 프로세스의 전체 메모리 구조

-basic3.c

#include <stdio.h>


int main()

{


        printf("hello, world\n");

        sleep(100000);


        return 0;

}


실행중일때 메모리 구성이 어떻게 되는지 확인해보면

1.ctrl + z (중지가 아니라 백그라운드로 넘긴다)

2.jobs


3.ps -ef

  1)프로세스 아이디를 확인한다 802번


4.cd /proc/

  1) 802번 디렉토리로 접속하면 정보를 확인할 수 있다


5.cd 802

  1).maps <-실행파일 메모리 공간을 확인할 수 있다.


 




low <-------------------------------------------------------------------> high

0x00000000~0x08048000             사용불가

0x08048000~0x08049000             text segment  (4k) 실행 권한이 r-xp

0x08049000~0x0804aFFF             data segment (.data, .bss, heap 메모리 영역이 포함되어 있음) (4k) 실행권한이 rw-p

                                      ----------- 데이터메모리영역 (a.out)


0x0804aFFF~0x40000000 예약된 메모리 (heap)을 위한


0x40000000~0x40013000 lib

0x40013000~0x40014000

                                        -----------  공유라이브러리 


0x4001c000~0x40109000 libc

0x40109000~0x4010d000

                                        -----------  공유라이브러리

 

0x4010d000~0xbfffe000 예약된 메모리 (stack)을 위한


0xbfffe000~0xbfffffff                  stack(static) 메모리 (지역, 환경변수, 함수가 들어있음)


c0000000 부터는 커널이 사용 (사용할 수 없음)


! heap메모리는 할당할수록 크기가 커지지만 커널은 할당할수록 stack쪽으로 내려온다.


! 중간비어있는 부분은 예약된 메모리


! stack메모리는 함수가 실행될때 크기를 잡으므로 그때그때 필요한만큼 할당해서 쓰므로

  모든 메모리에서 스택을 쓸 경우 stack에서 가져다가 쓴다. (rwx) 모두가 공통으로 사용하는 메모리이다.

  이부분에서 취약점이 발생하는데 stack은 모든 권한을 가지고있다.


메모리 보호기법은 스택의 실행 권한을 뺐는것이다.


stack

LIFO(last in first out)

주소가 낮아지면 낮아질수록 사용량이 많다

push로 스택을 쌓을수 있다.

한번 푸쉬할때 마다 기본적으로 4byte 씩 정렬된다.

pop 로 스택을 가져올수 있다.(맨위에 있는것만 pop할 수 있다)

가장 윗쪽에 있는 부분을 top  위치는 push와 pop에 의해 유동적으로 변한다.



esp 레지스터가 stack pionter <- top의 주소를 나타낸다 유동적으로 계속 변한다. 정렬단위 4 byte

ebp 레지스터 base pointer 한번 설정되면 값이 바뀌지 않는 레지스터(기준점을 잡을때 사용)

스택상에서 주소를 값으로 갖는다.


stack.asm


extern printf



segment         .data

prompt_hex      db      'esp:0x%08x',10,00



segment         .text

global  main


main:

        push    esp

        push    prompt_hex

        call    printf


        push    esp

        push    prompt_hex

        call    printf



결과값 

esp:0xbffffb3c

esp:0xbffffb34

8바이트 차이가 나는데 


push esp , push prompt_hex  두번 push를 하니까 4byte씩 이동한다.


l0xbffffb34l

l    4byte l push prompt_hex              

l    4byte l push esp

l0xbffffb3c  l

l          l push prompt_hex

l         l push esp

l         l      

l0xbfffffff    l



[실습 ] -jmp 를 이용해서 함수 구현


int result =0;

int a,b;



int sum( )

{

return a+b

}


int main()

{

result =sum(10,20);

printf("sum:%d\n",result);


return 0;

}


extern printf


segment         .data

prompt  db      'sum:%d',10,00



segment         .bss

result  resd    1

a       resd    1

b       resd    1



segment         .text

global main



sum:

        mov     eax,    dword [a]

        add     eax,    dword [b]

        mov     dword [result],eax

        jmp     return


main:

        mov     dword [a],      10 ;명령어의 주소가 eip에 들어가 있다. (cpu가 참조)

        mov     dword [b],      20 ;어떤 명령어가 실행할지는 eip를 보고 정한다.

        jmp     sum ;


         call     sum

return:

        push    dword [result] ;jmp sum 을 실행할때 eip 값이 여기 있는데 이걸 stack에 저장해서 사용한다

        push    prompt

        call    printf


이렇게 표현하면 프로그램이 엉망이 되기때문에 쓰면 안된다.






[실습 ] -stack 함수 구현



extern printf


segment         .data

prompt  db      'sum:%d',10,00



segment         .bss

result  resd    1

a       resd    1

b       resd    1



segment         .text

global main



sum:

        mov     eax,    dword [a]

        add     eax,    dword [b]

        mov     dword [result],eax

        ;pop      eip

         ret

main:

        mov     dword [a],      10 ;명령어의 주소가 eip에 들어가 있다. (cpu가 참조)

        mov     dword [b],      20 ;어떤 명령어가 실행할지는 eip를 보고 정한다.

        ;push    eip

        ;jmp     sum

        call     sum


        push    dword [result] ;jmp sum 을 실행할때 eip 값이 여기 있는데 이걸 stack에 저장해서 사용한다

        push    prompt

        call    printf


eip가 다음 수행할 명령어의 주소를 저장해두는 곳인데  만약 jmp sum 하기전에 eip 값을

스택에 저장하면 eip에는 push dword [result] 의 주소가 들어가 있다

jmp sum을 하고나서는 값을 더해주고 다시 push dword [result]로 와야하는데 이때 레이블을 안쓰고도

eip 주소를 이용해서 pop 해주면 돌아올수 있다.


nasm 에서는 eip를 못써서


push eip +jmp sum = call sum

pop eip =ret


이런식으로 사용한다.



Stack 메모리를 static 메모리 라고도 한다.



[실습 ] -stack 이용해서 함수 구현(지역변수 사용)


int sum(int a,int b )

{

return a+b

}


int main()

{

int result =0;

result =sum(10,20);

printf("sum:%d\n",result);


return 0;

}


extern printf


segment         .data

prompt  db      'sum:%d',10,00



segment         .bss



segment         .text

global main



sum:

        push    ebp

        mov     ebp,    esp


        mov     ebx,    dword [ebp+8]

        add     ebx,    dword [ebp+12]


        mov     eax,    ebx


        ;function epilogue

        ;mov    esp,    ebp

        ;pop    ebp

        leave

        ret


main:

        ;function prologue

        push    ebp

        mov     ebp,    esp

        sub     esp,    4


        push    20

        push    10

        call    sum

        add     esp,    8


        mov     dword [ebp-4],eax


        push    dword [ebp-4]

        push    prompt

        call    printf


        ;mov    esp,    ebp

        ;pop    ebp

        leave

        ret




1. main 

 push    ebp

 mov     ebp,    esp

 sub     esp,    4


main에서 ebp를 push하고

ebp에 esp를 push 하고

esp-4 를 해서 지역변수 result 값이 들어가는 위치를 확보한다


그림으로 표시해보면 

 

result  //esp -4를 해서 위치를 확보한다.

push ebp //esp 위치  

push eip  //main 호출하면 설정된다



2. push    20

   push    10

   call    sum

 

 push eip //push eip

 push 10 //push 10

 push 20 //push 20

 result

 push ebp

 push eip



call sum 의 의미는 push eip +jmp sum 이다



3.jmp sum 에 의해서 

   push    ebp

        mov     ebp,    esp


        mov     ebx,    dword [ebp+8]

        add     ebx,    dword [ebp+12]


        mov     eax,    ebx


sum 함수의 기준점을 설정해주기 위해서

push ebp

mov ebp, esp 를 해준다



 push ebp  <- esp  설정 

 push eip //push eip

 push 10 //push 10

 push 20 //push 20

 result

 push ebp

 push eip




4. mov     ebx,    dword [ebp+8]

   add     ebx,    dword [ebp+12]

   

   mov     eax,    ebx


   ;function epilogue

   ;mov    esp,    ebp

   ;pop    ebp

    leave

    ret



기준점 [ebp+8](push 10)  ebx에 대입해준다

add   ebx + [ebp+12](push 20)  ebx 에 30이 저장된다

mov eax,ebx eax에 30을 대입해준다. 

eax는 따로 지정안하면 리턴값을 저장하는 용도로 사용한다.

이제 함수 epilogue 하면서 main 함수로 돌아 간다










[ebp +12]

ㅡㅡㅡㅡㅡ

[ebp+8]

ㅡㅡㅡㅡㅡ

Saved ebp

ㅡㅡㅡㅡㅡ

Saved eip

ㅡㅡㅡㅡㅡ

10

ㅡㅡㅡㅡㅡ

20

ㅡㅡㅡㅡㅡ

result   <-[ebp -4]

ㅡㅡㅡㅡㅡ 

Saved ebp  <-ebp 

ㅡㅡㅡㅡㅡ

Saved eip    

ㅡㅡㅡㅡㅡ


1.Saved eip는 이미 실행된 상태에서 main 함수가 실행될것이다

2.push ebp

  mov  ebp,esp

  ebp의 값을 






ebp 라는 기준점을 세워둔다 

ebp를 우선 가지고와서 사용하고 나중에는 원래있던 곳으로 보내야하니까 ebp를 우선 저장해놓고

ebp를 기준으로 4바이트씩 이동해서 

함수 끝에 eax는 return을 의미한다


꼭 회수는 해야한다.











반복문 : for , while


1.while

int cnt =10;


int main()

{

   while(cnt>0){

      printf("%d\n",cnt);

      cnt --;        // inc(++), dec(--)

      }

   return 0;

}


2.for문

int cnt =10;


int main()

{

   for( ;  cnt > 0 ; cnt --){

      printf("%d\n",cnt);

      cnt --;        // inc(++), dec(--)

      }

   return 0;

}


while 이랑 for문은 문장 구조만 다르지 실행되는 방식은 똑같다


extern printf


segment         .data

prompt_int      db      '%d',10,00

cnt                dd      10


segment         .text


global main


main:


while:

        cmp     dword [cnt], 0

        jle     end


        push    dword [cnt]

        push    prompt_int

        call    printf


        dec     dword [cnt]

        jmp     while


end:


[실습]


  -문자열을 입력받아서 거꾸로 출력하는 어셈블리 프로그램을 작성

-scanf, gets, fgets, ...


1. 알고리즘 

문자열을 배열로 입력받아서 반복문으로 출력한다


C코드 


char buffer[1024];

int length = 0;

int i = 0;


int main() {

  gets ( buffer );

  while( buffer[length] != '\0' ) {

    len++;

  }

  len--;

  for( i = length; i >= 0; i-- ) {

    printf("%c", buffer[i]);

  }

  printf("\n");

  return 0;

}



만약 abc를 입력받는다고 생각하면

1.buffer에 abc를 저장한다

buffer의 값이 null 값이 나올때 까지 len을 더해준다

0,1,2,null  

a,b,c,

그럼 len이 3이 된다

len을 -- 해서 1을 빼주고 

2에서

(i=2 이고 i 가 0보다 크거나 같을때 ,i --)

i가 2일때 printf buffer[2] -> c

i가 1일때 printf buffer[1] -> b

i가 0일때 printf buffer[0] -> a





extern printf

extern scanf


segment .data


string1 dd '%c',10,00

string2 dd 'input:',00

input   dd '%s',10,00

newline dd  '',10,00

length  dd      0

i       dd      0


segment .bss

buffer resb 1024


segment .text

global main



main:

        push string2

        call printf

        push buffer

        push input

        call scanf


while:

        mov    eax,dword [length]

        cmp    dword [buffer+eax], 0

        je       then

        inc      dword [length]

        jmp     while


then:

        dec      dword [length]

        mov     ebx,dword [length]

        mov     dword [i],ebx


then1:

        cmp     ebx ,0

        jl         end

        mov     edx, dword [buffer+ebx]

        push    edx

        push    string1

        call      printf

        dec      ebx

        jmp     then1


end:

        push newline

        call printf



실행 결과




[실습]

-정수 5개를 배열로 입력받아서 배열의 총합을 출력하는 프로그램을 작성


 #include <stdio.h>


int main()

{

 int i, arr[5],sum;

 printf("input:");

 for(i=0; i<=4; i++) {

  scanf("%d", &arr[i]);

 }

 sum=arr[0];


  for(i=1; i<5; i++){

        sum += arr[i];

     }


        printf("sum:%d",sum);

        return 0;

}





extern printf

extern scanf


segment .data

input   db      '%d', 00

output  db      '%d', 10, 00


segment .bss

arr  resd    10

i     resd    1

sum  resd    1


segment .text

global  main


main:

        mov     eax,            0

        mov     dword [sum], eax

        mov     dword [i],    eax


while:

        mov     eax,    dword [i]

        cmp     eax,    5

        je      then


        mov     edx,    arr

        mov     ecx,    dword [i]

        imul    ecx,    4

        add     edx,    ecx


        push    edx

        push    input

        call    scanf


        inc     dword [i]

        jmp     while



then:


        mov     eax,    0

        mov     dword [i],    eax




while1:


        mov     eax,    dword [i]

        cmp     eax,    5

        je      end

        mov     ecx,    dword [i]

        imul    ecx,    4

        mov     edx,    0

        mov     edx,    dword [arr + ecx]

        add     dword [sum], edx

        inc     dword [i]


        jmp     while1


end:

        push    dword [sum]

        push    output

        call    printf




형변환


1). 큰-> 작은

     int a = 10

     short b = a



2). 작은 -> 큰

     short a = 10

     int b = a

     

     movzx//부호가 없는경우 확장 

     movsx//부호가 있는 경우 확장 


음수 표현법

0000 0001

1111 1110(1의 보수)

1111 1111(2의 보수) = -1이다.


어셈블리는 C와 다르게 작은값에서 큰값으로 넘어갈때 형변환이 필요하다


extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      9876


segment         .bss

result          resd    1


segment         .text

global main


main:

        mov     dx,     10

        movzx   eax,    dx




관계 연산: >, <, =, <=, >=, !=

-cmp(compare)

-cmp vleft, vright ->eflags register

-sub  vleft, vright ,


! 메모리나 레지스터의 값을 변경하지 않는다.

! 플래그 레지스터에 반영


1). vleft가 큰 경우:양의 정수

-SF: 0, ZF: 0 -부호도 발생하지 않고 제로도 아니니까 둘다 00


2). vright가 큰경우:음의 정수

-SF: 1, ZF:0  -부호가 발생하고 제로는 아니니까 SF만 1 ZF 는 0

3). 같은 경우: 0

-SF: 0, ZF: 1 -부호가 발생하지 않고 제로니까 SF:0 ZF:1

ex)

extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      9876


segment         .bss

result          resd    1


segment         .text

global main


main:

        mov     eax,    20

        cmp     eax,    20



        pushfd

        push    prompt_int

        call    printf


SF,ZF를 확인 할 수 있는 방법은 pushfd 를 확인하고 계산기에서 자릿수를 확인한다


EFLAGS Register

Sign Flag(SF) -8번

Zero Flag(ZF) -7번



제어문: if, case (기계어 에서는 차이가 없다)


1). 무조건 분기:jmp


 -jmp 주소

 -jmp offset


ex)

extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

hello1          db      'first step', 10, 00

hello2          db      'second step',10, 00



segment         .bss

result          resd    1


segment         .text

global main


main:

        jmp  second

        push hello1

        call printf



second:

        push hello2

        call printf

 


2). 조건 분기


조건분기 종류를 살펴보면
 - je (jmp equal) = jz
 - jne (jmp not equal) = jnz
 - jl (jmp less)
 - jg (jmp greater)
 - jnl
 - jng
 - jle
 - jge
 - ja (jmp above) 초과
 - jb (jmp below) 미만
 - jna
 - jnb
 ...



   if (a < 10) {

printf("less then 10\n");

   }


ex)

extern printf

extern scanf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

string          db      'less then', 10, 00

input           db      '%d',00


segment         .bss

buffer         resd    1


segment         .text

global main


main:

        push    buffer

        push    input

        call    scanf


        cmp     dword [buffer],10 ;입력받은 결과와 10을 비교하고

        jl      if ;입력 받은 결과가 작으면 if 블록으로 이동하고

        jmp     end ;입력받은 결과가 크면 end로 무조건 분기한다


if:     push    string  

        call    printf


end:


ex)-같은 결과인데 좀더 합리적으로 표현해보면


extern printf

extern scanf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

string          db      'less then', 10, 00

input           db      '%d',00


segment         .bss

buffer         resd    1


segment         .text

global main


main:

        push    buffer

        push    input

        call    scanf


        cmp     dword [buffer],10

        jge     end ;크거나 같으면 end로 간다 


        push    string ;작으면 string이 출력된다. 

        call    printf


end:


조건 분기 if else 같이 사용해서


   if (a < 10) {

printf("less then 10\n");

   }else {

printf("bigger then 10\n");

  }


extern printf

extern scanf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

string1         db      'less then', 10, 00

string2          db     'bigger then', 10, 00

input           db      '%d',00


segment         .bss

buffer         resd    1


segment         .text

global main


main:

        push    buffer

        push    input

        call    scanf


        cmp     dword [buffer],10 ; cmp dword [buffer], 9

        jge     else                   ;  jg    else


then:

        push    string1

        call    printf

        jmp     end


else:

        push    string2

        call    printf


end:


3). 다중 조건


if( a< 10 && b > 1 && c == 5) {

printf("correct\n");

}

알고리즘


1.첫번째가 거짓이면 종료

2.첫번째가 참이면 두번째 조건까지 비교 거짓이면 종료

3.첫번째 두번째 조검이 참이면 세번째 조건까지 비교하고 거짓이면 종료


extern printf

extern scanf


segment         .data

prompt_int      db      'correct',10,00

prompt_int_int  db      '%d %d %d',10,10

input           db      '%d',00

string1         db      'a:%d',10,00

string2         db      'b:%d',10,00

string3         db      'c:%d',10,00



segment         .bss

a         resd    1

b         resd    1

c         resd    1


segment         .text

global main


main:

        push    a

        push    input

        call    scanf


        push    b

        push    input

        call    scanf


        push    c

        push    input

        call    scanf


        cmp     dword [a],10

        jl      then

        jmp     end


then:

        cmp     dword [b] ,1

        jg      else

        jmp     end


else:

        cmp     dword [c],5

        je      else1

        jmp     end


else1:

        push    prompt_int

        call    printf


end:



다중 조건 2

if( a< 10 || b > 1 || c == 5) {

printf("correct\n");

}


extern printf

extern scanf


segment         .data

prompt_int      db      'correct',10,00

prompt_int_int  db      '%d %d %d',10,10

input           db      '%d',00

string1         db      'a:%d',10,00

string2         db      'b:%d',10,00

string3         db      'c:%d',10,00



segment         .bss

a         resd    1

b         resd    1

c         resd    1


segment         .text

global main


main:

        push    a

        push    input

        call    scanf


        push    b

        push    input

        call    scanf


        push    c

        push    input

        call    scanf


        cmp     dword [a],10

        jl      else1

then:

        cmp     dword [b],1

        jg      else1


else:

        cmp     dword [c],5

        je      else1

        jmp     end

else1:

        push    prompt_int

        call    printf


end:




다중 조건 3

if( (a< 10 && b > 1) || c == 5) {

printf("correct\n");

}


알고리즘 


1.A가 참이면 correct

   1). a가 거짓이면 end

   2). a가 참이고 b도 참이면  A가 참


2.A도 거짓 B도 거짓이면 end


else1은 A가 참일때 분기 하는곳 

else 는 A가 거짓일때 분기하는 곳

else1 컬렉트를 출력하는 경우

end A,B 둘다 거짓일때


extern printf

extern scanf


segment         .data

prompt_int      db      'correct',10,00

prompt_int_int  db      '%d %d %d',10,10

input           db      '%d',00

string1         db      'a:%d',10,00

string2         db      'b:%d',10,00

string3         db      'c:%d',10,00



segment         .bss

a         resd    1

b         resd    1

c         resd    1


segment         .text

global main


main:

        push    a

        push    input

        call    scanf


        push    b

        push    input

        call    scanf


        push    c

        push    input

        call    scanf


        cmp     dword [a] ,10

        jl      then

        jmp     else


then:

        cmp     dword [b] ,1

        jg      else1

        jmp     else


else:

        cmp     dword [c],5

        je      else1

        jmp     end


else1:

        push    prompt_int

        call    printf



end:


 









!단위 (암기좀 하자)


byte unit C

1 (b)yte char

2 (w)ord short

4 (d)word int, float, pointer, ...

8 (q)word long long,double..

10 (t)enbyte



1. 연산자


1). 사칙연산: +,-,*,/


[주소+] ->주소 표현 내에서만 사용이 가능하다 


1-1). add 더하기

기본 사용법 



ModRM:reg (r, w) ModRM:r/m (r)

값을 둘다 읽어서 더한값을 r 앞에 쓴다

올림수 처리를 생각해 줘야한다


ex)

extern printf


segment         .data

prompt_int  db  '%d',10,00

a           dd   10

b           dd   20


segment         .bss


segment         .text


global main


main:

        mov     eax,    dword [a] //메모리값[a]를 eax에 저장한다

        add     eax,    dword [b] //메모리 값 [b]와 eax를 더해준다 


        push    eax

        push    prompt_int

        call    printf



덧셈에 대한 결과값 



1-2). sub 뺄셈


덧셈과 같은 방식으로 계산 해주면 된다


extern printf


segment         .data

prompt_int  db  '%d',10,00

a           dd   10

b           dd   20


segment         .bss


segment         .text


global main


main:

        mov     eax,    dword [a]

        sub     eax,    dword [b]


        push    eax

        push    prompt_int

        call    printf



1-3).mul 곱하기

피 연산자의 비트에 따라서 계산하는 방식이 달라진다

레지스터 구조와 mul 계산 방식 두가지를 참고해야하는데

mul 계산 방식은 각 비트에 맞게 아래 설명이 잘나와있다.



레지스터 구조를 한번더 확인해보면


16비트 일때는 AH,AL로 나눠서 쓰는걸 볼수있다.


피연산자가 8비트 일때는 

AX<-AL * r/m8 

extern printf


AL에 메모리,레지스터 피연산자 8비트를 곱해서 AX(16비트) 에 저장한다



segment         .data

prompt_int  db  '%d',10,00

a           db   2



segment         .bss


segment         .text


global main


main:


        mov     eax, 0 

        mov     al,  5  

        mul     byte [a]


        push    eax

        push    prompt_int

        call     printf


정확하게 확인할려면 eax를 0으로 초기화하고 한다


피연산자가 16비트 인 경우에는

Unsigned multiply (DX:AX ← AX ∗ r/m16).

AX에 16비트를 곱하면 DX와 AX에 값이 나누어 저장된다. 


9876 * 4500=4442000

  =0000 0010 1010 0110 0010 0001 1001 0000


dx:678        0000 00010 1010 0110

ax:8592       0010 0001 1001 0000



extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      9876


segment         .bss

result          resd    1


segment         .text


global main


main:

        mov     eax,0

        mov     edx,0


        mov     ax, 4500

        mul     word [a]


        push    eax

        push    edx

        push    prompt_int_int

        call    printf



곱셈에 대한 결과값



imul(부호가 있는)

피연산자가 2개인 경우에는 add랑 동일 

ModRM:reg (r, w) ModRM:r/m (r)


extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dd      9876




segment         .bss


segment         .text


global main


main:


        mov     eax, 10

        imul    eax, dword [a]


        push    eax

        push    prompt_int

        call    printf

               


피연산자가 3개인 경우에는 


ModRM:reg (r, w) ModRM:r/m (r) imm8/16/32


extern printf

segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dd      9876


segment         .bss


segment         .text


global main


main:


        mov     eax, 0

        imul    eax, dword [a], 10


        push    eax

        push    prompt_int

        call    printf

             


1-3).Div 나누기               

div -> 4바이트 / 4바이트 = 2바이트


피연산자가 8비트 나눗셈

Unsigned divide AX by r/m8, with result

stored in AL ← Quotient, AH ← Remainder.

AX에 있는 값을 r/m8 비트로 나눠준다 

몫은 AL 나머지는 AH에 저장한다


Unsigned divide AX by r/m8, with result

stored in AL ← Quotient, AH ← Remainder.


extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               db      7




segment         .bss


segment         .text


global main


main:


        mov     eax, 0

        mov     ax, 10

        cwd  ;convert word to byte

        ;cdw ;convert double word to word

        ;cdq ;convert double word to quad word

        div     byte [a]


        push    eax

        push    prompt_int

        call    printf



16비트 나눗셈은

Unsigned divide DX:AX by r/m16, with result

stored in AX ← Quotient, DX ← Remainder.


  44442000 / 7412

=0000 0010 1010 0110 0010 0001 1001 0000

=dx:678, ax:8592

extern printf

segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      7412


segment         .bss

segment         .text

global main


main:

        mov     eax, 0

        mov     edx, 0

        mov     dx, 678

        mov     ax, 8592

        cdw ;convert double word to word

        div     word [a]


        push    eax

        push    edx

        push    prompt_int_int

        call    printf


나눗셈 결과





idiv



2). 비교연산


3). 논리연산 : and, or, xor 


1 and 0:0

1 or   0:1

xor 값을 초기화 할때 많이 쓴다

xor eax, eax

0으로 초기화 된다


4). 비트 연산: shift


-비트를 왼쪽/오른쪽으로 이동

곱셈

9876 * 4500=4442000

  =0000 0010 1010 0110 0010 0001 1001 0000


dx:678        0000 0010 1010 0110

ax:8592       0010 0001 1001 0000


만약 dx를 왼쪽으로 16비트 이동하고

dx:0000 0010 1010 0110 0000 0000 0000 0000

ax:0000 0000 0000 0000 0010 0001 1001 0000

+ 더해주면


4442000:0000 0010 1010 0110 0010 0001 1001 0000 값이 출력된다


ax와 그 값을 합치면 곱셈을 하지 않아도 결과 값이 출력된다


명령어는 shl,shr


[root@korea /root]# vi basic.asm

extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      9876


segment         .bss

result          resd    1


segment         .text


global main


main:

        mov     eax,0

        mov     edx,0


        mov     ax,     4500

        mul     word [a]


        mov     dword [result], edx

        shl     dword [result], 16

        add     dword [result], eax


        push    dword [result]

        push    prompt_int

        call    printf



비트 연산을 할때는 부호를 생각해서 연산을 해주어야 한다.


extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      9876


segment         .bss

result          resd    1


segment         .text

global main


main:

        xor     edx,    edx

        mov     dl,     -2

        shl     dl,     2

        shl     dl,     2


        push    edx

        push    prompt_int

        call    printf




결과는 맞지않는 값이 출력된다


산술 시프트인 sar sal 을 이용해서 값을 구하면 정확한 값이 출력된다




extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      9876


segment         .bss

result          resd    1


segment         .text

global main


main:

        xor     edx,    edx


        mov     edx,    -2

        sal     edx,    2

        sar     edx,    2


        push    edx

        push    prompt_int

        call    printf




나눗셈

  44442000 / 7412

=0000 0010 1010 0110 0010 0001 1001 0000

  0000 0000 0000 0000 1111 1111 1111 1111 and

=dx:678, ax:8592


extern printf


segment         .data

prompt_int      db      '%d',10,00

prompt_int_int  db      '%d %d',10,10

a               dw      9876


segment         .bss

result          resd    1


segment         .text


global main


main:

        mov     eax,0

        mov     edx,0


        mov     ax,     4500

        mul     word [a]


        mov     dword [result], edx

        shl     dword [result], 16

        add     dword [result], eax


        push    dword [result]

        push    prompt_int

        call    printf


        mov     edx,    dword [result]

        shr     edx,    16


        mov     eax,    dword[result]

        and     eax,    00000000000000001111111111111111b

        ;and     eax,    0000ffffh

        ;and     eax,    65535

        cdw

        div     word [a]


        push    edx

        push    eax

        push    prompt_int_int

        call    printf



[실습]

-두 수를 입력 받아서 사칙연산 결과를 출력하는 

어셈블리 프로그램 작성


-! 나눗셈은 몫과 나머지를 출력


   - 최대로 받을수 있는 수는 int 형이니까 레지스터는 전부 eax,edx 등을 사용해서 계산하였다.


  - 위에 기본을 배우려고 8비트 일 때16비트일 때 나누어서 생각했지만 일반적으로 계산할 때는 편의성을 위해 eax를 사용해서 32비트로 계산한다. 요즘 cpu가 이 정도 잡는다고 성능이 떨어지거나 하지 않기 때문이다




extern printf

extern scanf


segment .data

string1 db 'a+b:%d',10,00

string2 db 'a-b:%d',10,00

string3 db 'a*b:%d',10,00

string4 db 'a/b:%d %d',10,00

input dd '%d',00


segment .bss

a resd  4

b resd  4


segment .text

global main



main:

        push a

        push input

        call scanf


        push b

        push input

        call scanf


        mov eax,0

        mov eax, dword [a]

        add eax, dword [b]

        push eax

        push string1

        call printf


        mov eax,0

        mov eax,dword [a]

        sub eax,dword [b]

        push eax

        push string2

        call printf


        mov eax,0

        mov eax,dword [a]

        mul dword [b]

        push eax

        push string3

        call printf


        mov eax,0

        mov edx,0

        mov eax,dword [a]

        div dword [b]


        push edx

        push eax

        push string4

        call printf









! 레이블 Vs 변수.


1.주소 Vs 메모리


-포인터:메모리에 대한 직접 접근이 가능

-변수를 선언해서 사용하지 않는다.

-메모리를 직접 사용한다


1). 주소


-메모리상의 위치

-파일 오프셋과 동일한 의미

-정수


2).메모리


-데이터가 들어있는 실제 메모리

-메모리를 나타낼려면 []를 사용해서 씌어주면 된다

-[apple] 이렇게면 주소에 해당하는 메모리 를 가지고온다 

-피 연산자가 메모리로 오는 경우에는 크기를 꼭 적어주어야 한다

ex)push dword [apple]




실습//진짜 중요// 개념을 확실히 하고 넘어가야한다


C언어 -> 어셈블리어 실습

주소와 메모리의 차이를 이해하고 넘어가야한다.



C코드

int a;

int b;


int main()

{

    scanf("%d",&a); ->주소

    scanf("%d",&b); ->주소


    printf("a:%d\n",a); ->메모리

    printf("b:%d\n",b); ->메모리


    return 0;

}


어셈블리 코드

extern printf

extern scanf


segment .data

string1 db 'a:%d',10,00

string2 db 'b:%d',10,00

avg db '%d %d',00


segment .bss

a resd  1

b resd  1



segment .text

global main



main:

        push b //주소 b 넣고

        push a // 주소 a 넣고

        push avg //주소 a b 입력받아서 넣을 string 문자 넣고

        call scanf // scanf 해준다


        push dword [a] //a주소에 있는 메모리값 가지고오고

        push string1 // 문자열 가지고오고

        call printf // 프린트해준다


        push dword [b] //b 주소에 있는 메모리값 가지고오고

        push string2//문자열 

        call printf// 프린트해준다





INTEL IA32 Architecture


-instruction set

-register


1.Register


CPU가 사용하는

-아주 빠른 기억장소

-고속의 기억 장치

-용도별로 여러개의 레지스터가 존재



1). 범용 레지스터:EAX, EBX, ECX, EDX (각각의 레지스터의 크기는 32bit)

EAX(Extended Accumulator Resgister) //사칙 계산 할때

EBX(Extended Base Resgister)

ECX(Extended Counter Resgister)//반복되는 횟수 (루프에서) 넣을때
EDX(Extended Data Resgister)

EBX,EDX-> 보조적인 형태로 사용

꼭 맞는 용도에서만 사용하는건 아니다. 쉽게 말하면 아무때나 사용이 가능하다


2). 포인터 레지스터:ESI, EDI, EBP, ESP, EIP ->레지스터 안에 값들을 전부 주소로 인식한다.


ESI(Extended Source Index)

EDI(Extended Dst. Index)


EBP(Extended Base Pointer)

ESP(Extended Stack Pointer)
EIP(Extended Instruction Pointer)

3). 플래그 레지스터:EFLAGS

-프로세스에 대한 상태 정보를 나타낸다.





4). 세그먼트 레지스터:CS, DS, SS, ES, ....(각각의 레지스터 크기는 16bit)



입출력이 빠르다

레지스터 >> 메모리(주 기억 장치 ) >> 보조 기억장치(디스크,USB,..)



크기를 잘 알아야 한다 



용량을 나누어 사용할 수 있는데 EAX는 32비트를 다 사용한다는 소리 이고 AX는 16비트를 사용한다는 소리인데 

AX는 또 AH,AL로 8비트씩 나누어 사용할 수 있다는걸 표현한다



! 앞으로 프로그램을 만들면서는 두가지의 기억장치를 사용해야 한다 (레지스터,메모리)



2. 명령어


1). 데이터 복사: MOV

ex)

-int a=1

-두 피연산자가 모두 메모리가 올 수 없다.


88 /r       MOV r/m8,r8 (첫번째 피연산자는 r레지스터/m메모리 둘중 하나가 올수 있다,두번째 피 연산자는  레지스터를 사용해야한다)

표를 보면 이런식으로 쭉 있는데 첫번째 두번째 연산자가 둘다 M 메모리가 올 수 없다.


imm-> 상수 표현법 




연습 


extern printf


segment         .data

prompt_int      '%d',10,00


segment         .bss


segment         .text


global main


main:


        mov  eax, 100   //첫번째 피연산자는 eax 레지스터 두번째 피연산자는 100 상수

        push eax //eax에 넣어주고 

        push prompt_int //문자열을 불러온다

        call printf //출력해준다






실습-위에서 했던 출력문을  a,b 가 바뀌어서 출력되도록 바꾸기


#> ./test

10

20

a: 10

b: 20



#> ./test

10

20

a:20

b:10



mov의 기본 설정을 이용해서 코딩을 해야하는데  mov는 메모리,메모리 롤 저장할 수 가 없다.

따라서 레지스터를 2개 이용해서 값을 저장하고 옮겨주어야 한다



extern printf

extern scanf


segment .data

string1 db 'a:%d',10,00

string2 db 'b:%d',10,00

input db '%d',00


segment .bss

a resd  1

b resd  1

c resd  1


segment .text

global main



main:

        push a

        push input

        call scanf


        push b

        push input

        call scanf


        mov ebx,dword [b]

        mov eax,dword [a]

        mov dword [b],eax

        mov dword [a],ebx


        push dword [a]

        push string1

        call printf


        push dword [b]

        push string2

        call printf






segment .data

string  db      'hello,world!!!',10,00


#>cat hello2.asm

segment .text

global _start

_start:

mov eax, 4

mov ebx, 1

mov ecx, string

mov edx, 16

int 0x80





어셈블리로 컴파일한 hello2 elf 헤더 내용을 살펴보면 


#>objdump -x hello2

hello2:     file format elf32-i386

hello2

architecture: i386, flags 0x00000112:

EXEC_P, HAS_SYMS, D_PAGED

start address 0x08048080//실행파일이 메모리에 올라가면 가상 메모리에서 시작 위치를 나타낸다.처음 어느정도의 메모리는 사용하지 않는다(메모리상의 주소)


Program Header:

    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12

         filesz 0x00000096 memsz 0x00000096 flags r-x   0804800부터 150바이트를 차지한다 총 2의 12승 4K의 공간을 차지하는데 150바이트 빼고는 나머지는 패딩으로 채워진다 (flags r-x 권한을 보면 여기가 text인걸 알수있다.)


    LOAD off    0x00000098 vaddr 0x08049098 paddr 0x08049098 align 2**12 파일상에서 옵셋은 98 가상에서는 9098이다. 위의 4800 보다 4K 떨어져 있는 위치에 존재한다. 기본 정렬 단위는 4K  (flags rw- 권한을 보면 읽고 쓸수 있다) 

         filesz 0x00000010 memsz 0x00000010 flags rw-  일 크기는 hello world 만큼의 크기이다. 이 크기를 제외한 4k에서 남는부분은 패딩 된다 


Sections:

Idx Name          Size      VMA       LMA       File off  Algn

  0 .text         00000016  08048080  08048080  00000080  2**4

                  CONTENTS, ALLOC, LOAD, READONLY, CODE

  1 .data         00000010  08049098  08049098  00000098  2**2

                  CONTENTS, ALLOC, LOAD, DATA

  2 .bss          00000000  080490a8  080490a8  000000a8  2**0

                  CONTENTS

  3 .comment      0000001f  00000000  00000000  000000a8  2**0

                  CONTENTS, READONLY



실행파일을 분석할줄 알아야한다 

위의 heade 내용을 찾아보면


여기 까지가 헤더 내용인걸 알수있다.

LOAD off    0x00000098 vaddr 0x08049098 paddr 0x08049098 align 2**12 

9098을 확인해보면 90에서 8번 까지가 헤더 내용이다


여기는 hello 를 표현 하는 부분이다.



이런식으로 바이너리와 어셈블리를 하나하나 비교하면 확인이 가능하다




초기화 데이터의 차이 


char str[]="Hello,World!!";<- 초기화된 데이터 크기를 알아야한다

char str[];<-초기화가 안된 데이터


여기서 data와 bss 의 차이가 나온다 


2. DATA 섹션 ( 세그먼트 )  // 문자열 데이터 출력 

- 읽기/쓰기 가능한 메모리 영역 

- 프로그램 실행에 필요한 데이터  

- 실행 권한이 없다

segment .data

string  db      'hello,world!!!',10,00



3. BSS 섹션( 세그먼트 )

- 읽기/쓰기 가능한 메모리 영역 

- 초기화 되지 않은 데이터 영역 


저번시간에 배운 DATA 섹션은 초기화가 된 데이터가 들어가고 BSS 섹션은 초기화 되지 않은 데이터 영역이 들어간다



NASM 문법은 홈페이지 에서 참조가 가능하다



C언어

1.)데이터 타입

-숫자(정수,실수),문자,문자열




nasm 숫자를 표현하는 방법 





10진수

1.아무 숫자 없이 사용

2.d를 붙여서 사용


16진수

1.h를 붙여서 사용 

2.앞에 Ox 붙여서 사용


8진수

1.q를 붙여서 사용

2.o를 붙여서 사용


2진수 

1.b를 붙여서 사용




nasm 문자 표현 하는 방법


segment .data

string  db      'hello,world!!!',10,00  //여기서 10은 new line을 표현해준다,문자열은 null로 끝나야해서 00을 추가해야한다


열거형으로 사용이 가능하다

string  db      'h','e','l','l','o',10,00


기본 구조

label:    instruction operands        ; comment



2.)레이블 Vs. 변수


1).변수

-값,주소,크기

-int var = 10;


2).레이블은 주소 대신에 이름표를 붙인다는 의미

    주소를 매번 계산하기가 어렵기 때문에 주소를 나타내는 이름표를 사용한다. 

    쓰는건 문자열을 쓰지만 실제로는 주소를 쓴다


변수와 레이블은 전혀 다른 개념이다.


entry point

segment .text

global _start

_start: //명령어의 시작 위치 

mov eax, 4

mov ebx, 1

mov ecx, string

mov edx, 16

int 0x80


_start 는 entry point 이다.



extern printf

segment .data

string  db      'hello,world!!!',10,00

segment .text

global main


main:

        push string

        call printf


gcc를 쓰는 경우에는  entry point가 main이 되어야 한다.


그래서 레이블을 중복해서 사용할수 없다 

extern printf

segment .data

string  db      'hello,world!!!',10,00

string  db      'hello!!!',10,00

//이런식으로 사용이 불가능하고 

string2  db      'hello!!!',10,00

//이렇게 사용해야한다 

레이블 이름을 붙이는건 변수이름 지정하는거랑 비슷하다




segment .text

global main


main:

        push string

        call printf



3.데이터의 크기

1).접두사: d, res

데이터에다가 쓰는경우는 d, bss에 사용하는 경우는 res를 사용한다



2).단위

byte unit C

1 (b)yte char

2 (w)ord short

4 (d)word int, float, pointer, ...

8 (q)word long long,double..

10 (t)enbyte


db,dw,dd,dq,dt 이렇게 사용이 가능하다


C언어로 표현하면 

string  db      'hello,world!!!',10,00    ;char string[]="hello, world\n"


BSS 섹션( 세그먼트 )

- 읽기/쓰기 가능한 메모리 영역 

- 초기화 되지 않은 데이터 영역 

segment .bss

buffer resb 1024 ;char buffer[1024];


ex) int apple =10;

segment .data

apple dd 10


ex) int orange;

segment .bss

buffer resd 1


명령어


main:

        ;printf("hello, world!!!\n")

        push string //인자의 개수와 push 의 개수는 일치해야한다. string(주소)만 입력을 해주면 된다.

        call printf //printf 를 호출


        ;printf("num of apple: %d\n",apple)

        push dword [apple]

        push prompt

        call print

        

        인자가 여러개인 경우에는 뒤에서 부터 수행하면된다 push,push,call

        사용할 함수는 extern을 꼭 해야한다



실습


(0)_(0)(0)_(0)

(=^.^=)(*^.^*)

(_m_m)(_m_m_)

만들기 


extern printf

segment .data

string1 db     '%s%s%s',10,00

string2 db     '(0)_(0)(0)_(0)',10,00

string3 db     '(=^.^=)(*^.^*)',10,00

string4 db     '(_m_m)(_m_m_)',10,00

segment .text

global main


main:

        push string4

        push string3

        push string2

        push string1

        call printf



역순으로 PUSH후 CALL을 해주어야 한다



















[시스템 해킹] Compile


1.C 프로그램 

   -visual studio (컴파일러 아님),이클립스

-IDE: 통합 개발 환경 [컴파일러+편집기+디버거+...])

-Compiler:GCC

-editor:vi

-debugger:GDB


바이너리란?

-실행파일 우리가 흔히 보는 윈도우즈의 .exe 확장자의 실행파일이라고 생각하면 된다.

-0과 1로 되어 있는 기게어로 번역되있는 파일이다.


프로그래밍된 코드가 어떻게 기게어로 번역되는지 이런 과정을 크게 Compile 이라고 한다.

hello.c 파일 생성(* gcc는 확장자를 보기 때문에 파일 이름 뒤에 .c를 붙여줘야한다.)




#include <stdio.h>


int main()

{

printf("Hello,world\n");

return 0;

}



  컴파일:gcc hello.c

        실행:./a.out(실행파일 이름 정의 gcc -o Hello hello.c




2.컴파일 과정

컴파일 :gcc가 소스 코드를 불러와서 실행파일을 만들어주는 걸 컴파일 과정이라고 한다.



#>gcc -v hello.c (소스파일 컴파일 과정을 자세하게 보여준다)


 총 4개의 명령어가 쓰고있다- cpp cc1 as collect2 

 

 1).전처리 단계:cpp ( C PreProcess) 

sample.c /tmp/ccmsu4yd.i 이 보이는데 sample.c를 임시디렉터리에 .i파일을 만드는 과정이다.

하지만 /tmp 디렉토리에 들어가면 .i 파일을 확인할 수 없는데 그 이유는 컴파일이 끝나면 삭제시키기 때문이다. 


우리는 이 파일들을 보면서 확인할 것이므로 이러한 파일들이 삭제되지 않는 추가적인 옵션을 주어야한다.

-save-temps 옵션을 주어 임시파일 .i 파일을 삭제 하지 않게 하겠다.


그럼 hello.i hello.o hello.s 파일이 만들어진다

전처리 파일은 hello.i 이다 

-매크로

-해더파일



#define NUMBER 1000

int main()

{

        printf("Hello,world\n");

        printf("%d\n",NUMBER);

        return 0;

}


변경하고 다시 컴파일 해보면 차이를 느낄수 있다.  

- define 문장이 사라지고 소스코드 안에 썻던 define이 1000(우리가 설정했던 값)

으로 모두 치환되어 있는 것을 확인 할 수 있다.

-> 바로 이게 전처리기의 역할이다.



 2).컴파일 단계:cc1

 

소스파일->컴파일(어셈블리어)->기계어(binary)

컴파일 과정은 소스파일에서 어셈블리어로 바꾸는걸 말한다


왜 어셈블리어로 바꿔야 하는가?

우리가 보통 사용하는 C코드를 기계어로 바로 바꾸지 못한다. 그래서 어셈블리어로 먼저 바꾼다음 기계어로 바꿔야한다.


 !! 소프트웨어 공학(Software Engineering)

 기획->설계->구현->배포->유지보수

 

 !!리버싱은 반대로 한다

 기계어(binary)->어셈블리어->소스파일


  3).어셈블로 단계:as

목적 파일 생성(바이너리)

as -V -Qy -o /tmp/ccs3N70j.o /tmp/cc0q9vPg.s


as : 어셈블러를 뜻한다. 즉 as 명령으로 -o옵션으로 .s파일을 .o 파일로 만든다.

.o 파일 (기계어로 뽑아낸 파일, 오브젝트 파일)


 4).링크:collect2,ld

실행에 필요한 라이브러리를 합쳐준다.

/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 -L/usr/i386-redhat-linux/lib /tmp/ccs3N70j.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtend.o /usr/lib/crtn.o

: 오브젝트 파일과 라이브러리 오브젝트를 전부 합쳐서 하나의 실행파일로 만든다.



1.C 라이브러리를 이용한 어셈블리 프로그램 

nasm을 이용해서 작성

nasm 에서 ; 은 주석


#>vi hello.asm


extern printf


segment .data

string  db      'hello,world!!!',10,00


segment .text

global main


main:

        push string

        call printf


#>nasm -f elf -o hello.o hello.asm

#>gcc -o hello hello.o

이렇게 두번 해주는 이유는 우선 asm을 목적 파일로 변경해주는 역할을 해야해서 이고

extern printf  C라이브러리 사용하기 위해서 gcc로 컴파일을 다시 해주어야 한다.


결과 값을 확인해보면 

C코드와 동일한 결과값이 출력된다.



!! 어셈블리 프로그래밍-> 각각의 세그먼트를 직접 지정


실행파일을 메모리에 올릴때 어떤 세그먼트에 값이 올라갈지 알아야한다

실행파일 구조를 만들어야한다. (ELF 구조 )



 리눅스는 커널메모리1기가 + 프로세스 3기가

커널 메모리에는 접근하지 못한다. 

프로세스는 영역을 나눈다


1. text 섹션 ( 세그먼트 )

- 실행 가능한 메모리 영역 

- 읽기전용

- 명령어들이 들어있다.

push string

        call printf



2. DATA 섹션 ( 세그먼트 )  // 문자열 데이터 출력 

- 읽기/쓰기 가능한 메모리 영역 

- 프로그램 실행에 필요한 데이터  

- 실행 권한이 없다

segment .data

string  db      'hello,world!!!',10,00



3. BSS 섹션( 세그먼트 )

- 읽기/쓰기 가능한 메모리 영역 

- 초기화 되지 않은 데이터 영역 


4. 스택

- 파일 상에는 나타나지 않는다.



파일의 크기가 gcc 컴파일로 사용하는것과 어셈블리로 컴파일 하는것과 차이가 난다

a.out 파일과 hello2 파일의 크기가 차이가난다




xxd hello.c 를 사용하면 바이너리로 볼수있다

objdump 를 이용해서 헤더 구조를 확인할 수 있다.



objdump -x hello2

Program Header:

    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12

         filesz 0x00000096 memsz 0x00000096 flags r-x

    LOAD off    0x00000098 vaddr 0x08049098 paddr 0x08049098 align 2**12

         filesz 0x00000010 memsz 0x00000010 flags rw-





2.오리진 어셈블리 프로그램



segment .data

string  db      'hello,world!!!',10,00


segment .text

global _start


_start:

        mov     eax,    4

        mov     ebx,    1

        mov     ecx,    string

        mov     edx,    16

        int     0x80




#>nasm -f elf -o hello2.o hello2.asm

#>ld -o hello2 hello2.o




 

 

 

 

 



- 실습 환경


  - Red Hat Linux 6.2


    * 부팅시마다 LILO BOOT에서 linux-up을 입력해줘야 한다.


1.부팅


LILO boot:linux-up

vi /etc/sysconfig network 로 이름 변경 

!! pause 기능은 사용하지 말고 정상적으로 poweroff 해서 종료



2.실습도구


-GCC:C 컴파일러( GNU C Complier)

-GAS( GNU Assembler),NASM(Netwired ASM) 어셈블러

-GDB(바이너리 분석 도구)

!! 만약 network환경이 고장난 경우 VMWARE Edit -> 초기화 버튼 클릭(운영체제 종료 후에)

!! 실습환경 운영체제를 Red Hat Linux6.2를 사용하기 때문에 nasm은 제일 낮은 버전인 0.99.05를 설치했다.

NASM 다운로드


http://www.nasm.us/ 홈페이지 접속

#> wget http://www.nasm.us/pub/nasm/releasebuilds/0.99.05/nasm-0.99.05.tar.gz

#> tar xvfz nasm-0.99.05.tar.gz

#> cd nasm-0.99.05

#> ./configure

#> make

#> make install

#> cp nasm /usr/bin/



3.원격 쉘



1) vi /etc/securetty

pts/0~9 까지 만들기 

2) PAM: cd /etc/pam.d

# auth required /lib/security/pam_securetty.so


   * 위 내용을 해당 파일에 추가 시켜주기만 하면 다음 부팅시부터 Telnet을 이용한 원격접속이 가능하다.

+ Recent posts