How to do normal things with a Trick that replaces a function

Wenzhou, Zhejiang, the leather shoes are wet, and it will not get fat when it rains. Rainy night on Saturday, expecting more rain and colder tomorrow.

How long has it been since you programmed? It's been a long time... In fact, I don't know how to write code at all. From time to time, I just write some toys to verify the characteristics of a system. For me, engineering code is really difficult.

I have encountered some problems recently and need a specific solution, so I have the opportunity to write some code. In fact, I remember that the last time I met this topic was 8 years ago, and time passed by so fast.

Replacing a function already in memory so that the flow of execution flows into our own logic before calling the original function is an old topic. For example, there is a function called funcion, and you want to count the number of times the function is called. The most direct way is if someone calls the function, just call the following:

void new_function()

{

count++;

return function();

}

Many articles on the Internet give a trick to realize this idea, and computer viruses have always used this trick to achieve their goals. However, when you test it out for yourself, it's not that simple.

Many of the methods given on the Internet are no longer applicable, the reason is that in the early days, there were relatively few people doing this, and the processor and operating system did not need to pay attention to some unconventional practices, but as these kinds of tricks started to do bad things. When it comes to normal business logic, processor manufacturers and operating system manufacturers or communities have to add some restrictive mechanisms at the bottom to prevent such tricks from continuing to function.

There are two common measures:

Executable code segment is not writable

This measure blocks the scenario where you want to replace function instructions with a simple memcpy.

Memory buffer is not executable

This action blocks the scenario where you want to jmp the execution stream to one of your buffers that holds the instructions.

stack is not executable

Although these measures are relatively low, everyone understands at a glance, but they avoid the harm caused by a large number of buffer overflows.

So what if we want to do normal things with the Trick of the replacement function?

Let me briefly talk about my method. First of all, I will not HOOK the function of the user-mode process, because it is of little significance, and it will be much better to change and restart the service. Therefore, this article refers specifically to the practice of the HOOK kernel function. After all, recompiling the kernel and restarting the device is very expensive.

We know that almost all the computers we are currently using are von Neumann-style unified storage computers, that is, instructions and data exist together, which means that we must be free to interpret the meaning of memory space at the operating system level .

We're doing the right thing, so I'm assuming we have root access to the system and can compile and insert kernel modules. Then the next thing seems to be a process.

Yes, it is enough to modify the page table entry. Even if the function instruction cannot be simply replaced by memcpy, we can still use the following steps to replace the instruction:

Remap the physical memory corresponding to the function address to be writable;

Replace function instructions with your own jmp instructions;

Unwritable mapping.

Luckily, the kernel already has existing text_poke/text_poke_smp functions to do the above.

Likewise, for a heap or stack allocated buffer that is not executable, we still have a solution. The method is as follows:

Write a stub function, the implementation is optional, and its code instructions are equivalent to the buffer;

Rewrite the stub function with buffer using the above remap function address as writable method;

Save the stub function as a function pointer to call.

Isn't it interesting? The following is a schematic diagram of the steps:

Here is a code, I will talk about a few details about this code later:

#include

#include

#include

#include

#include

#define OPTSIZE5

// saved_op saves the instruction to jump to the original function

char saved_op[OPTSIZE] = {0};

// jump_op saves the instruction to jump to the hook function

char jump_op[OPTSIZE] = {0};

static unsigned int (*ptr_orig_conntrack_in)(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const struct nf_hook_state *state);

static unsigned int (*ptr_ipv4_conntrack_in)(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const struct nf_hook_state *state);

// The stub function will eventually be overwritten by the buffer that holds the instruction

static unsigned int stub_ipv4_conntrack_in(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const struct nf_hook_state *state)

{

printk("hook stub conntrack");

return 0;

}

// This is our hook function, which will be reached when the kernel calls ipv4_conntrack_in.

static unsigned int hook_ipv4_conntrack_in(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const struct nf_hook_state *state)

{

printk("hook conntrack");

// After printing just one line of information, call the original function.

return ptr_orig_conntrack_in(ops, skb, in, out, state);

}

static void *(*ptr_poke_smp)(void *addr, const void *opcode, size_t len);

static __init int hook_conn_init(void)

{

s32 hook_offset, orig_offset;

// This poke function completes the remapping and writes the text segment

ptr_poke_smp = kallsyms_lookup_name("text_poke_smp");

if (!ptr_poke_smp) {

printk("err");

return -1;

}

// Well, we're going to hook ipv4_conntrack_in, so find it first!

ptr_ipv4_conntrack_in = kallsyms_lookup_name("ipv4_conntrack_in");

if (!ptr_ipv4_conntrack_in) {

printk("err");

return -1;

}

// The first byte is of course jump

jump_op[0] = 0xe9;

// Calculate the relative offset of the target hook function to the current position

hook_offset = (s32)((long)hook_ipv4_conntrack_in - (long)ptr_ipv4_conntrack_in - OPTSIZE);

// The next 4 bytes are a relative offset

(*(s32*)(&jump_op[1])) = hook_offset;

// In fact, we didn't save the first few instructions of the original ipv4_conntrack_in function,

// But directly jmp to the instruction after 5 instructions, corresponding to the above picture, it should be that there is no instruction in the buffer

// If there is old inst, it is directly jmp y, why? Details later.

saved_op[0] = 0xe9;

// Calculate the offset from the position where the target original function will be executed to the current position

orig_offset = (s32)((long)ptr_ipv4_conntrack_in + OPTSIZE - ((long)stub_ipv4_conntrack_in + OPTSIZE));

(*(s32*)(&saved_op[1])) = orig_offset;

get_online_cpus();

// Replace operation!

ptr_poke_smp(stub_ipv4_conntrack_in, saved_op, OPTSIZE);

ptr_orig_conntrack_in = stub_ipv4_conntrack_in;

barrier();

ptr_poke_smp(ptr_ipv4_conntrack_in, jump_op, OPTSIZE);

put_online_cpus();

return 0;

}

module_init(hook_conn_init);

static __exit void hook_conn_exit(void)

{

get_online_cpus();

ptr_poke_smp(ptr_ipv4_conntrack_in, saved_op, OPTSIZE);

ptr_poke_smp(stub_ipv4_conntrack_in, stub_op, OPTSIZE);

barrier();

put_online_cpus();

}

module_exit(hook_conn_exit);

MODULE_DESCRIPTION("hook test");

MODULE_LICENSE("GPL");

MODULE_VERSION("1.1");

The test is OK.

In the above code, why is there no old inst in saved_op? It's just a jmp y. Isn't this missing the first few bytes of instructions in the original function?

In fact, here is a trick that is not fun. At first, I honestly saved the first 5 instructions of my own, and then when I need to call the original ipv4_conntrack_in, I will execute the 5 saved instructions first, which is also OK of. Then I objdump this function and found the following code:

0000000000000380 :

380: e8 00 00 00 00 callq 385

385: 55 push %rbp

386: 49 8b 40 18 mov 0x18(%r8),%rax

38a: 48 89 f1 mov %rsi,%rcx

38d: 8b 57 2c mov 0x2c(%rdi),%edx

390: be 02 00 00 00 mov $0x2,%esi

395: 48 89 e5 mov %rsp,%rbp

398: 48 8b b8 e8 03 00 00 mov 0x3e8(%rax),%rdi

39f: e8 00 00 00 00 callq 3a4

3a4: 5d pop %rbp

3a5: c3 retq

3a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)

3ad: 00 00 00

Note the first 5 instructions: e8 00 00 00 00 callq 385

As you can see, this can be ignored. Because in any case, the following instructions are executed immediately. So, I saved the inst save.

If you follow the conventional method in my diagram, the code can be changed a little:

char saved_op[OPTSIZE+OPTSIZE] = {0};

...

// Add an instruction copy operation

memcpy(saved_op, (unsigned char *)ptr_ipv4_conntrack_in, OPTSIZE);

saved_op[OPTSIZE] = 0xe9;

orig_offset = (s32)((long)ptr_ipv4_conntrack_in + OPTSIZE - ((long)stub_ipv4_conntrack_in + OPTSIZE + OPTSIZE));

(*(s32*)(&saved_op[OPTSIZE+1])) = orig_offset;

But the above are just toys.

There is a very real problem. What exactly is n when I save the first n instructions of the original function? In this example, obviously n is 5, which is in line with the convention that the first instruction of Linux kernel functions is almost always callq xxx.

However, if the first instruction of a function looks like this:

op d1 d2 d3 d4 d5

That is, an opcode requires 5 operands. If I only save 5 bytes, the final instruction in the stub will look like this:

op d1 d2 d3 d4 0xe9 off1 off2 off3 off4

This is obviously wrong, the op opcode interprets the jmp instruction 0xe9 as an operand.

What about the antidote? Of course there is.

Instead of recklessly backing up fixed-length instructions, we should do:

curr = 0

if orig[0] is a single-byte opcode

saved_op[curr] = orig[curr];

curr++;

else if orig[0] takes a 1-byte operand

memcpy(saved_op, orig, 2);

curr += 2;

else if orig[0] carries a 2-byte operand

memcpy(saved_op, orig, 3);

curr += 3;

...

saved_op[curr] = 0xe9; // jmp

offset = ...

(*(s32*)(&saved_op[curr+1])) = offset;

This is the right thing to do.

3 In 1 Wireless Charger

For Iphone:
For Iphone8/X/XR/XS Max


Compabile Models:


For Samsung:
For Galaxy S6, For Galaxy S6 Edge, For Galaxy S6 Edge+,
For Galaxy S6 Active, For Galaxy S6 Duos, For Galaxy Note Edge,
For Galaxy S7, For Galaxy S7 Edge, For Galaxy Note 5
For Galaxy S8, For Galaxy S8 Plus, For Galaxy Note 8

For Galaxy S9,For Galaxy S9 Plus


For Sony:
For Xperia Z4V, For Xperia Z3V
For Google:
For Nexus 4, For Nexus 5, For Nexus 6, For Nexus 7


For MOTORALA:
For Moto Droid Turbo, For Moto Droid Turbo 2, For Moto Droid 5

For NOKIA:
For Lumia 920, For Lumia 928, For Nokia Lumia 93, For Lumia 950, For Lumia 950 XL, For Lumia 1020, For Nokia Lumia 1050, For Nokia Lumia 822, For Nokia Lumia 735

For HTC:
For HTC ONE MAX T6, For HTC Incredible 4G, For HTC ONE mini 2, For HTC Droid DNA

For LG:
For LG Nexus 4, For LG Nexus 5, For LG G Pro, For LG D1L, For LG LTE2

For Others:
For YotaPhone 2, For Elephone P9000

Guangzhou HangDeng Tech Co. Ltd , http://www.hangdengtech.com