Detect whether kernel functions are hook ed by trace stack

Rootkit needs to find out if there is a program catching it in time, and the detection program itself needs to be vigilant about Rootkit injection and left-right interaction.

The instrumentation program found Rootkit to be very versatile, and I've previously described how to static scan through address ranges that are called from each other by kernel text segments:
https://blog.csdn.net/dog250/article/details/105474909
In this article, I will introduce a dynamic trace stack approach to catch call exceptions to kernel functions.

Take a neutral program for example.Never praise or disparage good or evil.

Last month, I implemented a function to count the number of packets dropped by iptables on the INPUT chain by DROP:
https://blog.csdn.net/dog250/article/details/105206753
See that article for more details.

I mean, how do I find out that the middle of ip_local_deliver y is hook ed?

It's hard, but it's not impossible.

Assuming that I add a jmp stub to the header of each function in the kernel without regard to performance degradation, dump the current stack in that stub, as long as there are addresses below the RPB of the stack that are not in the range of the text segment addresses of the kernel, then you need to go into detail and exclude the callback function, and the rest is illegal.

The kernel text segment address interval is roughly:

ffffffff81000000 T _text
...
ffffffff81649abb T _etext

Let's take an example from DROP statistics to see how code gets hook ed:

# This is the original call
0xffffffff81561eb5 <ip_local_deliver+165>:      movq   $0xffffffff81561ad0,-0x18(%rbp)
0xffffffff81561ebd <ip_local_deliver+173>:      callq  0xffffffff815586a0 <nf_hook_slow>
0xffffffff81561ec2 <ip_local_deliver+178>:      cmp    $0x1,%eax

# This is a call after being hook ed
0xffffffff81561eb5 <ip_local_deliver+165>:      movq   $0xffffffff81561ad0,-0x18(%rbp)
0xffffffff81561ebd <ip_local_deliver+173>:      callq  0xffffffffa00ef000 <test_stub1>
0xffffffff81561ec2 <ip_local_deliver+178>:      cmp    $0x1,%eax

crash> dis test_stub1
0xffffffffa00ef000 <test_stub1>:        callq  0xffffffff815586a0 <nf_hook_slow>
0xffffffffa00ef005 <test_stub1+5>:      cmp    $0x1,%eax
0xffffffffa00ef008 <test_stub1+8>:      je     0xffffffffa00ef011 <test_stub1+17>
0xffffffffa00ef00a <test_stub1+10>:     incl   0xffffffffa00f1280
0xffffffffa00ef011 <test_stub1+17>:     retq
0xffffffffa00ef012 <test_stub1+18>:     callq  0xffffffff8162e40d <printk>
0xffffffffa00ef017 <test_stub1+23>:     pop    %rbp
0xffffffffa00ef018 <test_stub1+24>:     retq

OK, we confirm that if nf_hook_slow prints the stack, test_stub1 should be found, after all, because it calls nf_hook_slow.The stack should have the address 0xffffffa00ef005.

Let's try it.

To code without compiling, I still use a guru-mode stap script:

%{
#include <linux/in.h>
#include <linux/ip.h>
%}

%{
unsigned char *_self;
%}

function init_self()
%{
	_self = (void *)kallsyms_lookup_name("nf_hook_slow");
%}

function filter:long(iphdr:long, stat:long)
{
	hooknum = 0;
	protocol = @cast(iphdr, "iphdr")->protocol
	if (stat) {
		hooknum = @cast(stat, "nf_hook_state")->hook
	}
	return (hooknum == 1 && protocol == %{ IPPROTO_ICMP %})
}
function ip_hdr:long(skb:long)
%{
	struct iphdr *iph = ip_hdr((struct sk_buff *)STAP_ARG_skb);
	STAP_RETVALUE = (long)iph;
%}
function _backtrace ()
%{
	unsigned long rbp;
	unsigned long *prbp;
	int i, prn = 0;
	asm ("mov %%rbp, %0;\n":"=m"(rbp)::);

	prbp = (unsigned long *)rbp;

	for (i = 0; i < 20; i++) {
		// Since the stap mechanism itself has a lot of calls piled up, we skip them out, starting just below nf_hook_slow.
		if (prbp[i] == (unsigned long)_self)
			prn = 1;
		if (prn == 1)
			STAP_PRINTF("0x%lx \n", prbp[i]);
	}
%}

probe begin {
	init_self();
}

probe kernel.function("nf_hook_slow")
{
	iph = ip_hdr(pointer_arg(1));
	stat = pointer_arg(2);
	if (filter(iph, stat)) {
		_backtrace();
		println();
		println();
	}
}

The experiment will now begin.

Add an iptables rule and run the stap script:

[root@localhost test]# iptables -A INPUT -p icmp -j DROP
[root@localhost test]# stap -g ./sdump.stp

Then ping the machine to see the output:

0xffffffff815586a0
0xffff88003fd83c80
0xffffffffa00ef005
0xffff88003fd83c70
0xffffffff8112945e
0xffff88003c92d000
0xffff8800361af410
0xffff88003d4f0000

Ha-ha, find 0xffffffa00ef005 now!I can see with my eyes that something is wrong, obviously not at the beginning of 81...This address is obviously not in the code snippet interval of the kernel, but in the mapping interval of the module:

0xffffffffa0000000 ~ 0xffffffffff000000

This tells you what you do in the module by allocating memory.

However, I find it embarrassing to be caught like this, so what if we hide the code in the kernel code snippet itself?See the following article:
https://blog.csdn.net/dog250/article/details/105496996

There are plenty of places in the kernel for you to hide dirt. Just look directly for the nop area. Here's one where I found it and copied the code from the past:

crash> dis 0xffffffff810001d0 10
0xffffffff810001d0 <_stext+8>:  callq  0xffffffff815586a0 <nf_hook_slow>
0xffffffff810001d5 <_stext+13>: cmp    $0x1,%eax
0xffffffff810001d8 <_stext+16>: je     0xffffffff810001e1 <_stext+25>
0xffffffff810001da <_stext+18>: incl   0xffffffffa0239280
0xffffffff810001e1 <_stext+25>: retq
0xffffffff810001e2 <_stext+26>: nop

Now load the module for this dirty and scale-resistant template and detect it again with the stap script mentioned above:

0xffffffff815586a0
0xffff88003fd83c80
0xffffffff810001d5
0xffff88003fd83c70
0xffffffff8112945e
0xffff88003ae96f00
0xffff88003bc71210
0xffff88003d4f0000

Notice this 0xffffffff810001d5 address, which is in _step.If you don't look carefully at the contents of this address, it's easy to leave it as a normal address.

Stop!Something the matter!

What if stap is blocked?Try perf probe:

perf probe --del 'nf_hook_slow*' && perf probe 'nf_hook_slow caller=$stack0'&& ping 127.0.0.1

Then print the result:

[root@localhost ~]# perf record -e probe:nf_hook_slow -aR sleep 3 && perf script
[ perf record: Woken up 1 times to write data ]
...
            ping  3376 [003]  5361.373481: probe:nf_hook_slow: (ffffffff815586a0) caller=ffffffff810001d5

We can see caller, ff ffffffff810001d5, still grabbing!

Of course, you'll question, how do I know to probe nf_hook_slow?Isn't this Zhuge Liang afterwards?

Yes, this question is entirely reasonable. I didn't know which function to probe beforehand. I'm just demonstrating here.

If we have a list of functions that can be called after a hook, that is, a list of suspected functions, it's half as simple.We actually have this list.For example, a system call function.

Many times, we hook the original system call, enter our own logic, and then call the original system call function:

int my_sys_write(...)
{
	do_something(...);
	return orig_sys_write(...);
}

Obviously, my_sys_write is definitely not within the scope of the kernel text segment, and we can successfully capture this hook behavior using the above method.

Okay, we're going to start. We'll be here today.

Zhejiang Wenzhou leather shoes are wet, so you won't get fat when it rains.

Keywords: iptables Linux REST

Added by psychowolvesbane on Mon, 04 May 2020 17:14:17 +0300