2012年5月21日 星期一

實作第一個BenOS 系統呼叫 puts

最近很久沒有更新BenOS的訊息,最近專注在研讀MIT-OSE 課程。

本次仿造 MIT-OSE JOS 中的系統呼叫,
筆者為自己小OS加新功能,算是對最近學習的一個驗證。

實作程式碼在 https://github.com/benwei/bos (develop branch)。

讓我們先來看JOS的系統呼叫的概念圖:
 可參考Lab3 JOS 實作程式碼在branch lab3 https://github.com/benwei/MIT-JOS

Lab3 JOS syscall calling sequence

執行步驟:
  1. lib/libmain.c:17 呼叫 sys_getenvid()
  2. lib/syscall.c:61 呼叫 syscall(SYS_getenvid)
  3. lib/syscall.c:23 呼叫48號中斷 帶入系統呼叫SYS_getenvid(2) 號
  4. 發生中斷 kern/trapentry.S:98, 將返回值0及 系統呼叫SYS_getenvid(2) 號放入堆疊,
  5. 跳至 kern/trapentry.S:312 _alltraps中,將相關的暫存器放入堆疊,接著執行332行trap(%esp), %esp == struct Trapframe *tf
  6. 為避免重覆執行中斷, kern/trap.c:200, eflags & FL_IF == 0 必須為真,204行檢查只否由使用者空間進入(tf->tf_cs & 3) == 3, 將tf 複製到核心空間保存;執行220行trap_dispatch()
  7. 執行trap.c:172 核心的syscall() 在 kern/syscall.c
  8. 跳至kern/syscall.c:87 執行 sys_getenvid(), 取得 curenv->env_id
  9. 返回trapdispatch, 執行kern/env.c:520 env_run 中 env_pop_tf()
  10. 呼叫kern/env.c:502 重新設置返回值及相關暫存器
  11. 於kern/env.c:507 執行iret 結束系統呼叫中斷
  12. 回到使用者空間,lib/syscall.c:34 檢查回傳值等等,完成一個呼叫流程
(注意:在lib/syscall.c:23及 TRAPHANDLER_NOEC&_alltrap 放入堆疊的數量與env_pop_tf 取回堆疊的數值必須一致,否則系統將發生不可預的行為。)

BenOS 實作


本次只先實作部分 kernel thread 間的系統呼叫,尚無ring3 至ring 0之間的權限切換。

詳見 user/libsyscall.c:10 系統呼叫:int TRAP_SYSCALL(68) , 其中 BSYS_PUTS(8)

1 #include "user/syscall.h"
2 #include "stdio.h"
3
4 /*
5  * num: BSYS_PUTS, a1 is string for display, a2 is undefined.
6  */
7 unsigned int syscall(int num, unsigned int a1, unsigned int a2)
8 {
9         unsigned int ret = 0;
10          __asm __volatile(
11                 "int %1\n"
12                 :"=a" (ret)
13                 :"i" (TRAP_SYSCALL),
14                  "a" (num),
15                  "d" (a1),
16                  "c" (a2)
17                 :"cc", "memory");
18         printf("syscall with ret=%d\n", ret);
19         return ret;
20 }
21
22 void uputs(const char *msg)
23 {
24         syscall(BSYS_PUTS, (unsigned int)msg, 202);
25 }

在kernel/osfunc.s:126 使用nasm 語法寫成,IDT 中斷呼叫256個函式庫。

其中osfunc.s:400 為減少堆疊使用,僅使用三個參數。
(註:將JOS 中的env_pop_tf 改寫成409~413,較易於理解)

400 _all_trap_handler:
401         push eax
402         push edx
403         push ecx
404
405         push esp
406         call trap_handler
407         add  esp, 4
408
409         pop ecx
410         pop edx
411         add esp, 4 ; no pop eax for return code
412         add esp, 8
413         iretd

kernel/trap.c
70 int trap_handler(struct trapframe *tf)
71 {
72         switch(tf->trapno) {
73         case TRAP_SYSCALL:
74                 print_tf(tf);
75
76                 return kern_syscall(tf->regs.eax,
77                              tf->regs.edx,
78                              tf->regs.ecx);
79                 break;
80         default:
81                 print_tf(tf);
82                 panic("panic: system halt\n");
83         }
84         return 0;
85 }

kernel/ksyscall.c
6 int kern_syscall(uint32_t num, uint32_t a1, uint32_t a2)
7 {
8         int ret = 0;
9         if (num == BSYS_PUTS) {
10                 do {
11                 struct task *t = get_now_task();
12                 printf("task_id(%d) syscall(BSYS_PUTS,a2=%d),a1: %s\n", t->pid, a2, a1);
13                 ret = strlen(a1);
14                 } while(0);
15         }
16         return ret;
17 }

執行結果:

開機完成至bos$ 下,執行run 來執行task 2 (events: hello_main)

0521_BOS


小結


本次的系統呼叫的基本框架,以最精簡的部分,實作出來。

筆者正在嘗試加入ring3 至 ring0 時,
在windows的qemu 0.13版,執行run 命令,qemu系統自動重新啟動bos;
但在MAC OS X 中使用qemu 0.12.5 執行同樣測試時,
qemu 會顯示正確存取權限失敗,
因為bos在ring3 中直接呼叫ring0函式。之後,會對使用者權限切換部分繼續實作。

有任何問題建議,歡迎至 http://www.juluos.org or http://julu.staros.mobi  中的Juluosdev google groups 參與討論。

2012年5月20日 星期日

python + shellcode 實測 64/32

0520-shellcode_py

最近看到Shellcode文章,手癢把它改相容64bit Ubuntu及32bit cygwin 了。

由此下載原始碼修改過smc.py


Ubuntu 11.10 (GNU/Linux 3.0.0-12-virtual x86_64)
使用Python 2.7.2+,進行測試

Linux pylab 3.0.0-12-virtual #20-Ubuntu SMP Fri Oct 7 18:19:02 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux
# python smc.py
linux2_add64(99, 1) = 100


Linux xubuntu 2.6.35-32-generic #65-Ubuntu SMP i686 GNU/Linux
使用Python 2.6.6,進行測試

$ python smc.py
linux2_add32(99, 1) = 100


Windows XP+cygwin+gcc(4.5.3) + python 2.7.2
使用dump_machine_code.sh, ASM 的結果:

add.o:     file format pe-i386
Disassembly of section .text:
00000000 <_add>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 0c                mov    0xc(%ebp),%eax
   6:   03 45 08                add    0x8(%ebp),%eax
   9:   5d                      pop    %ebp
   a:   c3                      ret
   b:   90                      nop
cygwin中,執行smc.py結果
$ python smc.py
cygwin_add32(99, 1) = 100


載入平台相依的libc

if sys.platform == "cygwin":
    libc = cdll.LoadLibrary("/bin/cygwin1.dll")
else:
    libc = CDLL('libc.so.6')


判斷32或64位元的方法之一


import sys
from math import log
def is64bit():
    return log(sys.maxsize, 2) == 63


小結


這技巧可以使用smc主程式,隨意在執行時修改程式碼的方法,也提供一條路讓python直接與硬體溝通。

如這是一個24小時不能停機的程式,意味著筆者可利用這方法做出不需要重新執行python主程式來動態載入修改過的函式庫。

當然這種方法也會影響Python 跨平台的可攜性,
解決方法只能為各種所需執行平台編繹對應的版本(若其平台安裝有gcc,則可以在執行檢查有無對應的platform_func.o 檔案,動態產生並載入它,類似pyc的做法)。