我一心一意想写一个指令解析器,我的目的是扫描出Linux内核text段中的所有的jmp和call指令,从而检测内核是否已经被篡改。基于以下事实:
- 一般内核函数互相调用都跑不出内核的text段,从0xffffffff开始的几兆空间,凡是跳转越界跑出这个空间的,都要详查,过滤掉正常的hotfix,export回调这些,剩下的就是篡改了。
- 像bridge/bonding的rx_handler回调函数,是在模块里注册的回调指针,并非立即数出现在call指令中,即便rx_handler有问题,也只是模块自己的无心错(没人傻到用这种方式进行二进制注入)。
关于指令解析器,有现成的就不用自己写了,我用的是Intel XED,代码在:
https://github.com/intelxed/xed
照着readme操作就可以了,最后会得到一个叫做xed的可执行程序:
[root@localhost obj]# pwd /root/test/xed/kits/xed-install-base-2020-03-25-lin-x86-64/examples/obj [root@localhost obj]#
讯享网
配合下面的stap脚本,我们先看下这个指令解析的效果:
讯享网// scan_text.stp %{
#include <linux/module.h> %} function scan_text:long() %{
#define MAX_TEXT_SIZE 0xff0000 int i; unsigned int size; unsigned char *__text; unsigned char *__etext; // 内核代码的开始和结束 __text = (void *)kallsyms_lookup_name("_text"); __etext = (void *)kallsyms_lookup_name("_etext"); __text ++; STAP_PRINTF("90 "); size = __etext - __text; // 将内核text段全部dump下来 for (i = 0; i < size; i++) {
STAP_PRINTF("%x ", __text[i]); } STAP_RETVALUE = 0; %} probe begin {
scan_text(); exit(); // oneshot模式 }
我们跑一下试试看:
[root@localhost obj]# stap -g ./scanner.stp >./code.hex [root@localhost obj]# cat code.hex |more 90 8d 2d f9 ff ff ff 48 81 ed 0 0 0 1 48 89 e8 25 ff ff 1f 0 85 c0 f 85 a7 1 0 0 48 8d 5 db ff ff ff 48 c1 e8 2e f 85 96 1 0 0 48 1 2d c2 8f ae 0 48 1 2d b3 df 94 0 48 1 2d b4 df 94 0 48 1 2d 85 ff 94 0 48 8d 3d ae ff ff ff 48 8d 1d a7 7f ae 0 48 89 f8 48 c1 ...
好的,现在让我们上xed程序:
讯享网[root@localhost obj]# ./xed -ih ./code.hex -64 >./code.txt [root@localhost obj]# [root@localhost obj]# cat ./code.txt |head -n 10 XDIS 0: NOP BASE 90 nop XDIS 1: MISC BASE 8D2DF9FFFFFF lea ebp, ptr [rip-0x7] XDIS 7: BINARY BASE 4881ED00000001 sub rbp, 0x XDIS e: DATAXFER BASE 4889E8 mov rax, rbp XDIS 11: LOGICAL BASE 25FFFF1F00 and eax, 0x1fffff XDIS 16: LOGICAL BASE 85C0 test eax, eax XDIS 18: COND_BR BASE 0F85A jnz 0x1c5 XDIS 1e: MISC BASE 488D05DBFFFFFF lea rax, ptr [rip-0x25] XDIS 25: SHIFT BASE 48C1E82E shr rax, 0x2e XDIS 29: COND_BR BASE 0F jnz 0x1c5 [root@localhost obj]#
这不就是内核代码吗?是的,这就是内核代码!
现在,让我们揪出里面所有的0xe8相对地址call指令:
[root@localhost obj]# cat ./code.txt |egrep '\sE8[0-9A-F]{8}\s' >./call.txt [root@localhost obj]# [root@localhost obj]# cat call.txt |head -n 20|tail -n 10 XDIS 21db: CALL BASE E8007D2F00 call 0x2f9ee0 XDIS 21fd: CALL BASE E85E call 0x XDIS 2226: CALL BASE E call 0x74760 XDIS 2254: CALL BASE E call 0x XDIS 227f: CALL BASE E82C722F00 call 0x2f94b0 XDIS 2353: CALL BASE EF00 call 0x2f9770 XDIS 2387: CALL BASE E8D4722F00 call 0x2f9660 XDIS 23b1: CALL BASE E81A982F00 call 0x2fbbd0 XDIS 23c8: CALL BASE E840C06200 call 0x62e40d XDIS 2408: CALL BASE E8C3972F00 call 0x2fbbd0
以最后一行为例,它在Linux内核的text段的偏移为0x2408,相对call的偏移是0x2fbbd0,加上_text基地址就是绝对地址了,一般而言,这个基地址就是0xffffffff,所以该call指令的目标是0xffffffff812fbbd0,我们确认一下:
讯享网[root@localhost obj]# cat /proc/kallsyms |grep ffffffff812fbbd0 ffffffff812fbbd0 T sscanf
嗯,它超大概率是正常的内核函数之间的互相调用。
所以,我们只需要寻找相对偏移大于0xffffff的即可:
[root@localhost obj]# cat ./code.txt |egrep '\sE8[0-9A-F]{8}\s' |gawk --non-decimal-data '{if ($NF > 0xffffff)print $0}' [root@localhost obj]#
没有,什么都没有…
当然什么都没有咯,因为我们的内核目前还是干净的!
现在,让我们注入昨天的那个hack ip_local_deliver的代码,再次尝试:
讯享网[root@localhost obj]# insmod ./drop.ko [root@localhost obj]# stap -g ./scanner.stp >./code2.hex [root@localhost obj]# ./xed -ih ./code2.hex -64 >./code2.txt [root@localhost obj]# cat ./code2.txt |egrep '\sE8[0-9A-F]{8}\s' |gawk --non-decimal-data '{if ($NF > 0xffffff)print $0}' XDIS 561ebd: CALL BASE E83EA1B41E call 0x1f0ac000 [root@localhost obj]#
输出一条,我们需要严查它了,看看它到底是什么,call到了哪里:
crash> dis 0xffffffff81561ebd 5 0xffffffff81561ebd <ip_local_deliver+173>: callq 0xffffffffa00ac000 <test_stub1> 0xffffffff81561ec2 <ip_local_deliver+178>: cmp $0x1,%eax 0xffffffff81561ec5 <ip_local_deliver+181>: jne 0xffffffff81561e69 <ip_local_deliver+89> 0xffffffff81561ec7 <ip_local_deliver+183>: jmp 0xffffffff81561e5f <ip_local_deliver+79> 0xffffffff81561ec9 <ip_local_deliver+185>: nopl 0x0(%rax) crash>
注意第一行那个 callq 0xffffffffa00ac000 <test_stub1> 成功揪出了真凶!!
注意!上面的实验结果不是一次的结果,所以数值对不上,以下图的计算为准:

自己和自己下棋还是很有趣的,左右互搏,道高一尺,魔高一丈。
谁说我必须把test_stub1放在0xffffffffa00ac000这么远的地方了,我把它塞进Linux内核的text段行不行?
谁说不行,当然行!问题是你要找到一片空隙,足够你塞入你的stub代码。
那就扫nop序列呗!
我还真就找到了:
讯享网[root@localhost obj]# cat code.hex |egrep -o '(90\s{1}){10}' |head -n 10 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 [root@localhost obj]# cat ./code.txt |egrep '\s90\s' |more ... XDIS 1ce: NOP BASE 90 nop XDIS 1cf: NOP BASE 90 nop XDIS 1d0: NOP BASE 90 nop ... XDIS 1e2: NOP BASE 90 nop XDIS 1e3: NOP BASE 90 nop ...
就从1d0开始吧!换算成地址就是0xffffffffd0。来吧,动手,修改那个drop hook:
#include <linux/module.h> #include <linux/slab.h> #include <linux/kallsyms.h> #include <linux/cpu.h> char *stub; char *addr = NULL; // 传入ip_local_deliver的地址 static unsigned long laddr; module_param(laddr, ulong, 0644); // 计数INPUT链上的被DROP的数据包的数量 static unsigned int counter = 0; module_param(counter, int, 0444); #define FTRACE_SIZE 5 #define POKE_OFFSET 173 #define POKE_LENGTH 5 #define COND_LENGTH 5 #define COUNTE_LENGTH 8 static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len); static struct mutex *_text_mutex; static unsigned int pos, target; static int __init hotfix_init(void) {
unsigned char e8_call[POKE_LENGTH]; unsigned char incl[COUNTE_LENGTH]; unsigned char cond[COND_LENGTH]; s32 offset, i; u32 low32 = (unsigned int)(((unsigned long)&counter) & 0xffffffff); char *gap = (void *)0xffffffffd0; laddr = (unsigned long)kallsyms_lookup_name("ip_local_deliver"); _text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp"); _text_mutex = (void *)kallsyms_lookup_name("text_mutex"); if (!laddr || !_text_poke_smp || !_text_mutex) {
printk("not found\n"); return -1; } addr = (void *)laddr; stub = (void *)gap; offset = (s32)((long)stub - (long)addr - FTRACE_SIZE); pos = (unsigned int)((long)stub - (long)addr); _text_poke_smp(&stub[0], &addr[POKE_OFFSET], POKE_LENGTH); target = *((unsigned int *)&addr[POKE_OFFSET + 1]); target -= pos; target += POKE_OFFSET; _text_poke_smp(&stub[1], &target, sizeof(target)); cond[0] = 0x83; // cmp $0x1, %eax cond[1] = 0xf8; cond[2] = 0x01; cond[3] = 0x74; // jz $ret cond[4] = 0x07; // skip "incl $counter" _text_poke_smp(&stub[POKE_LENGTH], &cond, COND_LENGTH); incl[0] = 0xff; // incl $counter incl[1] = 0x04; incl[2] = 0x25; (*(u32 *)(&incl[3])) = low32; incl[7] = 0xc3; // retq _text_poke_smp(&stub[POKE_LENGTH + COND_LENGTH], &incl, 8); e8_call[0] = 0xe8; (*(s32 *)(&e8_call[1])) = offset - POKE_OFFSET; for (i = 5; i < POKE_LENGTH; i++) {
e8_call[i] = 0x90; // nop 占位符 } get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&addr[POKE_OFFSET], e8_call, POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); return 0; } static void __exit hotfix_exit(void) {
target -= POKE_OFFSET; target += pos; _text_poke_smp(&stub[1], &target, sizeof(target)); get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&addr[POKE_OFFSET], &stub[0], POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); } module_init(hotfix_init); module_exit(hotfix_exit); MODULE_LICENSE("GPL");
加载该模块,再用xed检测,就什么都检测不出来了!我们用crash看看就是ip_local_deliver调用了哪里:
讯享网crash> dis ip_local_deliver+173 0xffffffff81561ebd <ip_local_deliver+173>: callq 0xffffffffd0 <_stext+8>
我们可以看到 0xffffffffd0 这个地址看上去非常正常,它妥妥就在text范围内!我们看看它是什么:
crash> dis 0xffffffffd0 10 0xffffffffd0 <_stext+8>: callq 0xffffffffa0 <nf_hook_slow> 0xffffffffd5 <_stext+13>: cmp $0x1,%eax 0xffffffffd8 <_stext+16>: je 0xffffffffe1 <_stext+25> 0xffffffffda <_stext+18>: incl 0xffffffffa0 0xffffffffe1 <_stext+25>: retq 0xffffffffe2 <_stext+26>: nop 0xffffffffe3 <_stext+27>: nop 0xffffffffe4 <_stext+28>: nop 0xffffffffe5 <_stext+29>: nop 0xffffffffe6 <_stext+30>: nop crash>
噢,我的天,它就是我们的stub函数,这下它藏匿到了一个更加不容易被检测到的地方了!
所以说咯,检测程序还需要更加智能一些。
本以为右手打赢了左手,没想到左手最后一击反扑成功!
如果你担心text即便用text poke也不能写,那就自己改页表项呗…
浙江温州皮鞋湿,下雨进水不会胖。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/38762.html