1. 程式人生 > >多種方法獲取sys_call_table(linux系統呼叫表)的地址

多種方法獲取sys_call_table(linux系統呼叫表)的地址

一.方法一:常用方式,也是一google一堆的方式

我們首先需要找到call table-with-offset的特徵,先看下面的程式碼

syscall_call:
        call *sys_call_table(,%eax,4)
假設我們沒有vmlinux可供gdb反彙編,那也只有採用模擬的方式了,模擬出一個call *sys_call_table(,%eax,4),然後看其機器碼,然後在system_call的附近基於這個特徵進行尋找


然後用objdump進行dump可見下面一行:
080483ac <main>:
...
 80483bc:       ff 14 85 1c 95 04 08    call   *0x804951c(,%eax,4)
...


於是ff 14 85 後面就是sys_call_table的地址,注意大小端,x86機器是小端機器,因此是反著的。如果system_call也不知道,比如不能掛載procfs,並且也沒有System.map,那麼就只有通過中斷描述符來先獲取system_call的地址了,方法如下:
0.你必須知道中斷描述符的結構以及有中斷描述符暫存器這麼一回事。不過就算不知道也比較好查,google即可;
1.通過sidt指令獲取中斷描述符的基地址;
2.將這個地址加上8*0x80就是系統呼叫描述符的地址了;
3.從這個描述符中取出系統呼叫處理程式即system_call地址的高16位和低16位,拼接在一起即可。

二.方法二:使用dump_stack
寫一個很簡單的核心模組,內部呼叫dump_stack ,然後就可以看到:
 [<f88f300b>] init_module+0xb/0x53 [gettable]
 [<c013adc4>] sys_init_module+0x104/0x250
 [<c010620b>] syscall_call+0x7/0xb

既然看到了syscall_call+0x7的地址,那麼也就知道了標號syscall_call的地址,而我們需要找的sys_call_table的地址就在它下面地址的指令中 ,對於2.6.8核心而言,就是它下面的第一條指令:
syscall_call:
        call *sys_call_table(,%eax,4)
實際上syscall_call這個標號可以在/proc/kallsym中取到的,如果沒有procfs再使用dump_stack的方法。為了不讓人說我是胡扯的,貼上程式碼:

然後dmesg的結果如下:
 [<f88e000b>] init_module+0xb/0x50 [gettable]
 [<c013adc4>] sys_init_module+0x104/0x250
 [<c010620b>] syscall_call+0x7/0xb
ff 14 85 1c ad 2d c0 89

在這個方法中,即使不能從procfs中獲取任何資訊,還是可以使用dump_stack的,就算有一天這個函式也不能用了,那怎麼辦呢?很好辦,在模組中故意訪問一個NULL指標,然後核心就算替你列印stack了 ...逼到最後,大不了遍歷所有的memory(通過/dev/mem?或者/proc/kcore?),然後從中查詢匹配的機器碼,這是最後的辦法,即使這樣,我們也可以肯定這個地址不會太靠後的。
三.方法三:直接使用棧結構 獲取
這種方式不是那麼直觀,然而卻很直接,在x86機器上,我們知道棧的重要性,棧儲存了函式呼叫的路徑,它就是程式執行流的家 ,任何後續需要的本執行流都有一個棧。對於核心模組而言,在insmod載入它並初始化的時候,這個棧是存在的,實際上就是insmod程序的核心棧。我們可以順著這個核心棧來向上回溯 ,直到找到call *sys_call_table(,%eax,4)的下一跳指令的地址,這有個基本原則,那就是我們知道在呼叫call指令的時候,需要將下一條指令的地址壓入棧(注意是地址),因此這個call *sys_call_table(,%eax,4)指令的下一條指令的地址肯定能在回溯的途中遇到,既然找到了call的下一條指令,那麼往上一條指令不就是call嗎?既然找到了call指令,通過分析Intel的指令格式,我們就能抽出sys_call_table的地址。
3.1.如何判斷誰是call指令的下一條地址
答曰:在載入核心的時候,核心的text段被載入到了0xC0000000 + 0x100000這個地址 ,這是通過vmlinux.lds連結檔案知道的,並且system_call這個0x80的entry直到call sys_call_table(,%eax,4)沒有呼叫任何call指令(在正常的前提下,既然已經到了模組的init函式,當然正常了),而system_call這個entry是insmod程序切到核心棧的第一條指令,因此核心棧到此為止,因此從回溯的末尾開始,第一個遇到的0xc01XXXXX附近的值就是了。
3.2.前置知識
想這麼幹,並不需要知道核心棧的結構以及current巨集的相關知識,不過理解了也沒什麼壞處!
3.3.如何找到call指令中的sys_call_table值
答曰:通過原始碼知道這是一條:CALL dword ptr [REG*SCALE+BASE]
查閱Intel的指令手冊或者google前人發現的捷徑,可以知道這類指令是帶有SIB的call指令,應該是FF 14 xx的樣子,因為base是一個地址,因此xx就應該是85,這是從intel提供的一張表中獲取的,從而最終,這條指令就應該是FF 14 XX Y1 Y2 Y3 Y4 這個樣子,於是從找到的call指令的下一條指令地址直接減去4之後就能獲取Y1 Y2 Y3 Y4了,而這就是最終需要的sys_call_table
3.4.程式碼:

3.5.此方法不需要獲取system_call的地址。
四.方法四:通過/dev/mem在使用者態完成
這種方式不會汙染執行中的核心(不會載入任何模組),然而弄不好很容易PTD(panic to death,對應windows的blue screen...)。這種方式實際上是最直接的,相當於直接使用機器碼對整個實體記憶體程式設計,需要相當高的水平。不過,整個記憶體都擁有了,還有什麼做不到呢?
     之所以可以動態修改機器碼,是因為馮諾依曼機器是基於儲存模型的,指令和資料一樣是儲存在記憶體中的,而記憶體是可存取的 ,雖然現代機器架構使用了保護模型比如記憶體存取許可權或者特權環等機制限制了某些存取,但是卻無法從根本上改變馮諾依曼模型的存取特徵,因為在任何領域,對於主體的鑑權 都是有缺陷了,比如一旦有特權的主體被以某種方式劫持了,那麼它的行為將是危險的和有害的(暫且不考慮禁止向下寫等單向資訊鑑權模型,那樣會引入新的複雜性和新的不確定因素),所以模型決定了一切而不是區域性的設計決定了一切。
五:總結
1.不贊成替換系統呼叫。 因為這是linux,不是windows,你既然能編譯並載入模組,說明你有root許可權,既然你有root許可權,替換一個系統呼叫毫無疑義,說明不了你的水平
2.不贊成用gdb反彙編核心, 我們需要的僅僅是一個地址值而已,沒有必要那麼麻煩反彙編核心。
3.我所作的一切只是為了除錯新新增的系統呼叫而不希望重新編譯核心,並不是搞攻擊 ,如果真的搞攻擊,最難的不是寫程式碼,是如何發現漏洞以及如何利用漏洞,首先你不是root,弄到最後你成了root,接下來你就可以做替換系統呼叫這種簡單至極的事了,前提是你怎麼從非root成為root?最簡單也是最難的攻擊辦法就是:直接逼問管理員!