앞서우리는버퍼오버플로우로인해리턴어드레스 (return address) 가변조될수있음을알았습니다. 이제곧리턴어드레스를원하는값으로변경하는실습을해볼것인데요, 그전에앞서, 메모리에저장된값들을살펴보는방법에대해배워보겠습니다. 여러분모두 Windows 에서 hex editor(hex dump, hex viewer) 라는것을사용해보셨을겁니다. 바로바이너리파일을 16 진수 (hex) 로보여주는프로그램입니다. 90
이러한 hex editor 는주로평문으로출력할수없는값들을확인하거나, 바이너리파일내의특정값들을변경하고자할때사용됩니다. 그럼리눅스에서바이너리파일을 16 진수로보고자할때주로사용되는프로그램엔무엇이있을까요? 대표적인예로 /usr/bin/xxd 가있습니다 $ xxd /bin/ls 0000000: 7f45 4c46 0101 0100 0000 0000 0000 0000.ELF... 0000010: 0200 0300 0100 0000 a093 0408 3400 0000...4... 0000020: 50a4 0000 0000 0000 3400 2000 0600 2800 P...4....(. 0000030: 1800 1700 0600 0000 3400 0000 3480 0408...4...4... 0000040: 3480 0408 c000 0000 c000 0000 0500 0000 4... 0000050: 0400 0000 0300 0000 f400 0000 f480 0408... 0000060: f480 0408 1300 0000 1300 0000 0400 0000... 0000070: 0100 0000 0100 0000 0000 0000 0080 0408... 0000080: 0080 0408 00a1 0000 00a1 0000 0500 0000... 0000090: 0010 0000 0100 0000 00a1 0000 0031 0508...1.. 00000a0: 0031 0508 7002 0000 0805 0000 0600 0000.1..p... 00000b0: 0010 0000 0200 0000 c8a2 0000 c832 0508...2.. 00000c0: c832 0508 a800 0000 a800 0000 0600 0000.2... 00000d0: 0400 0000 0400 0000 0801 0000 0881 0408... 00000e0: 0881 0408 2000 0000 2000 0000 0400 0000......... 00000f0: 0400 0000 2f6c 6962 2f6c 642d 6c69 6e75.../lib/ld-linu... 생략... 그리고비슷한프로그램으로 /usr/bin/hexdump 가있습니다. hexdump 는 xxd 와는반대의 byte order( 바이트출력순서 ) 를사용합니다. 가장첫 2 바이트를보면 hexdump 와는순서가반대인것을알수있습니다. 91
$ hexdump /bin/ls 0000000 457f 464c 0101 0001 0000 0000 0000 0000 0000010 0002 0003 0001 0000 93a0 0804 0034 0000 0000020 a450 0000 0000 0000 0034 0020 0006 0028 0000030 0018 0017 0006 0000 0034 0000 8034 0804 0000040 8034 0804 00c0 0000 00c0 0000 0005 0000 0000050 0004 0000 0003 0000 00f4 0000 80f4 0804 0000060 80f4 0804 0013 0000 0013 0000 0004 0000 0000070 0001 0000 0001 0000 0000 0000 8000 0804 0000080 8000 0804 a100 0000 a100 0000 0005 0000 0000090 1000 0000 0001 0000 a100 0000 3100 0805 00000a0 3100 0805 0270 0000 0508 0000 0006 0000 00000b0 1000 0000 0002 0000 a2c8 0000 32c8 0805 00000c0 32c8 0805 00a8 0000 00a8 0000 0006 0000 00000d0 0004 0000 0004 0000 0108 0000 8108 0804 00000e0 8108 0804 0020 0000 0020 0000 0004 0000 00000f0 0004 0000 6c2f 6269 6c2f 2d64 696c 756e... 생략... 이처럼 xxd 나 hexdump 를이용하면바이너리파일을 16 진수로출력해볼수있습니다. 그리고이들을 vi 와연동시켜사용하면 hex editor 처럼값을수정할수있기도합니다. (vi [ 파일명 ] 실행후 :%!xxd, 복귀시엔 :%!xxd -r) 그럼, 파일형태의바이너리가아닌, 현재실행중인메모리의주소와값을 16 진수로출력하려면어떻게해야할까요? 크게두가지방법이있는데요, 첫째, 디버거 (Debugger) 라는툴을이용하여동적으로메모리를분석하는방법, 둘째, 메모리의주소를출력하는코드를소스코드에추가하는방법입니다. 이중비교적간단하고또많이쓰이는두번째방법에대해설명해드리겠습니다. 다음은메모리주소와값을출력해볼예제프로그램입니다. 92
./10/ex1.c int main() char str[20] = hackerschool! ; printf( %s\n, str); 여기서우리가알고싶은것은변수 str의주소와그에해당하는 16진수값들입니다. 그럼다음과같이소스코드를수정하면될것입니다../10/ex2.c int main() int i; char str[20] = hackerschool! ; printf( %s\n, str); printf( ============= HEX DUMP START =============\n ); // 주소값출력 printf( 0x%08x, &str); // 16 진수값출력 for(i=0; i<sizeof(str); i++) printf( %02x, str[i]); printf( \n============= HEX DUMP END =============\n ); 실행화면은다음과같습니다. 93
$ gcc -o ex2 ex2.c $./ex2 hackerschool! ================== HEX DUMP START =============== 0xbffffb30 68 61 63 6b 65 72 73 63 68 6f 6f 6c 21 00 00 00 00 00 00 00 ================== HEX DUMP END ================= $ 이제위코드를기본으로하여더욱 hex dump 답게만들어나갈수있는데요, 다음은국내해커 ohhara님께서이미만들어놓으신 dumpcode.h라는헤더입니다../10/dumpcode.h // dumpcode.h by ohhara void printchar(unsigned char c) if(isprint(c)) printf( %c,c); // 해당하는값을문자로표시 else printf(. ); // 출력불가능한문자는그냥. 으로표시 // 메모리시작주소와출력할크기를인자로받음 void dumpcode(unsigned char *buff, int len) int i; for(i=0;i<len;i++) // 16 바이트단위로주소출력 if(i%16==0) printf( 0x%08x,&buff[i]); // hex 값출력 printf( %02x,buff[i]); // 해당 16 진수들을각각문자로출력 if(i%16-15==0) 94
int j; printf( ); for(j=i-15;j<=i;j++) printchar(buff[j]); printf( \n ); // 마지막라인이 16바이트이하일경우정렬유지 if(i%16!=0) int j; int spaces=(len-i+16-i%16)*3+2; for(j=0;j<spaces;j++) printf( ); // 부족한공간만큼 space로이동한후, for(j=i-i%16;j<len;j++) printchar(buff[j]); // 남은문자값들출력 printf( \n ); 이를소스코드에추가하거나, 혹은 dumpcode.h 로만든후 include 시키면 dumpcode() 라는함수를사용할수있게됩니다. 또는 dumpcode.h 를 /usr/include/ 디렉토리에복사해넣으시면어느경로에서든 include 하여사용하실수있습니다. 그리고 dumpcode() 함수의원형은다음과같습니다. void dumpcode(unsigned char *buff, int len); buff 엔 dump 하고자하는메모리의시작주소, 그리고 len 엔 dump 할크기를지정합니다. 95
다음은 dumpcode() 를사용한예제입니다../10/ex3.c #include dumpcode.h int main() int i; char str[20] = hackerschool! ; printf( %s\n, str); // str 변수에서부터 100 바이트를출력 dumpcode((unsigned char *)&str, 100); [ 실행결과 ] $./ex3 hackerschool! 0xbffffb30 68 61 63 6b 65 72 73 63 68 6f 6f 6c 21 00 00 00 hackerschool!... 0xbffffb40 00 00 00 00 20 97 04 08 68 fb ff bf cb 09 03 40......h...@ 0xbffffb50 01 00 00 00 94 fb ff bf 9c fb ff bf 68 38 01 40...h8.@ 0xbffffb60 01 00 00 00 90 83 04 08 00 00 00 00 b1 83 04 08... 0xbffffb70 24 86 04 08 01 00 00 00 94 fb ff bf e4 82 04 08 $... 0xbffffb80 ac 86 04 08 60 ae 00 40 8c fb ff bf 90 3e 01 40...`..@...>.@ 0xbffffb90 01 00 00 00... $ 이제마치 xxd 명령을사용한것처럼메모리내용을볼수있게되었습니다. 이처럼메모리의주소와값을눈으로직접확인해가면서버퍼오버플로우를공부하면, 더쉽게메모리의구조를이해할수있게되고, 우리가변조한메모리값이잘바뀌었는지확인하거나, 버퍼오버플로우취약점을어떻게공략해야할지에대한전략을잘짤수있게됩니다. 96
버퍼 오버플로우-왕기초편 10.메모리를 Hex dump 뜨기 dumpcode()는 시스템 해킹을 공부할 때 매우 유용하게 사용되니 잘 익혀두시기 바랍니다. 97