Copy Link
Add to Bookmark
Report
Phrack Inc. Volume 11 Issue 60 File 08
==Phrack Inc.==
Volume 0x0b, Issue 0x3c, Phile #0x08 of 0x10
|=-------------------=[ Static Kernel Patching ]=------------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------=[ jbtzhm <jbtzhm@nsfocus.com> ]=---------------------=|
|=---------------------[ http://www.nsfocus.com ]=-----------------------=|
--[ Contents
1 - Introduction
2 - Get kernel from the image
3 - Allocate some space in image
4 - Relocate the symbol in module file
5 - Make it autorun when reboot
6 - Possible solutions
7 - Conclusion
8 - References
9 - Appendix: The implementation
--[ 1 - Introduction
This paper will show a simple way to patch a common LKM into the
static linux kernel image.Most kernel backdoors are implemented by loadable
kernel module which is loaded into kernel by insmod or /dev/kmem,and the
backdoor module can found easily if the disk can be mounted on other
machines.It is not the expected result.What is wanted is just to find a
method to put the LKM into kernel image,and make it run when reboot.
The program attached in the appendix contains codes and debugs in redhat7.2
(Intel)default installation,and can be easily tested on other kernel
versions by some modification.Also the program is based on the
/boot/System.map file which contains the kernel symbol address.If the file
doesn't exist on your system,more works have to be done to make it run.
--[ 2 - Get kernel from the image
The first step is getting kernel from image file that is usually compressed
(uncompress image is not concerned because it is much easier).The image
file can be analyzed from the kernel source files,and Makefile will
clarify the structure of the image.
[/usr/src/linux/arch/i386/boot/Makefile]
..
zImage: $(CONFIGURE) bootsect setup compressed/vmlinux tools/build
$(OBJCOPY) compressed/vmlinux compressed/vmlinux.out
tools/build bootsect setup compressed/vmlinux.out $(ROOT_DEV) >
zImage
..
(bzImage is similar)
bootsect: bootsect.o
$(LD) -Ttext 0x0 -s --oformat binary -o $@ $<
bootsect.o: bootsect.s
$(AS) -o $@ $<
bootsect.s: bootsect.S Makefile $(BOOT_INCL)
$(CPP) $(CPPFLAGS) -traditional $(SVGA_MODE) $(RAMDISK) \
$< -o $@
..
setup: setup.o
$(LD) -Ttext 0x0 -s --oformat binary -e begtext -o $@ $<
setup.o: setup.s
$(AS) -o $@ $<
setup.s: setup.S video.S Makefile $(BOOT_INCL) $(TOPDIR)\
/include/linux/version.h $(TOPDIR)/include/linux/compile.h
$(CPP) $(CPPFLAGS) -D__ASSEMBLY__ -traditional \
$(SVGA_MODE) $(RAMDISK) $< -o $@
The bootsect and setup file are easy to understand.They are created by
bootsect.s and setup.s respectively.The vmlinux.out file is raw binary
file generated by objcopy command.The value of $(OBJCOPY) is
"objcopy -O binary -R .note -R .comment -S". More details are available
by `man objcopy`.When the three files are ready the build program will
bind the three files to on file which is the kernel image file.
However the vmlinx file is generated more complicatedly.It is also possible
to go into the compressed directory and look through the Makefile.
[/usr/src/linux/arch/i386/boot/compressed/Makefile]
..
vmlinux: piggy.o $(OBJECTS)
$(LD) $(ZLINKFLAGS) -o vmlinux $(OBJECTS) piggy.o
The $(OBJECTS) includes head.o and misc.o,compiled by head.S and
misc.c respectively.The most important step in head.S is calling
the decompress_kernel function in misc.c.The function will inflate
the compressed kernel.When the decompress_kernel takes effect,it requires
input_len and input_data symbol which are defined in piggy.o
..
piggy.o: $(SYSTEM)
tmppiggy=_tmp_$$$$piggy; \
rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk; \
$(OBJCOPY) $(SYSTEM) $$tmppiggy; \
gzip -f -9 < $$tmppiggy > $$tmppiggy.gz; \
echo "SECTIONS { .data : { input_len = .; \
LONG(input_data_end - input_data) input_data = .; \
*(.data) input_data_end = .; }}" > $$tmppiggy.lnk;
$(LD) -r -o piggy.o -b binary $$tmppiggy.gz -b elf32-i386 -T \
$$tmppiggy.lnk;
rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk
The piggy.o file is a common ELF object file.However,it only contains data
section.The ld requires a command file like this\
SECTIONS { .data : { input_len = .; LONG(input_data_end - input_data)\
input_data = .; *(.data) input_data_end = .; }}
The command file enables the piggy.o to have the symbol which is required
by misc.o.Hopefully the command "gzip -f -9" also can be seen. It just
compressed the kernel file compiled by thousands of kernel source files.
So the kernel image could be described like this
[bootsect][setup][[head][misc][compressed_kernel]]
Now let us understand more about the boot process.
The process can be separated into the following some logical stages:
1.BIOS selects the boot device.
2.BIOS loads the [bootsecto] from the boot device.
3.[bootsect] loads [setup] and [[head][misc][compressed_kernel]].
4.[setup] do sth. and jmp to [head](it is at 0x1000 or 0x100000).
5.[head] call uncompressed_kernel in [misc].
6.[misc] uncompressed [compressed_kernel] and put it at 0x100000.
7.high level init(begin at startup_32 in linux/arch/i386/kernel/head.S).
After the machine run into step 7,the high level initialization begins.
When the structure of the kernel image is clear,kernel text from the
compressed image with a dirty method are easily available.It is matching
the compress-magic contained in image.It is also known the 4-byte number
before the magic is the input_data from which the offset can be verified.
After this gunzip the kernel is pretty easy.
--[ 3 - Allocate some space in image to use
The allocation here doesn't mean vmalloc or kmalloc method.It just means
space is required to contain the LKM file.The lkm file >> the kernel can
be easily catted,but it will not work.To find the reason,the best method is
to go back into the kernel initial code,which is all in step 7 mentioned
above.
[/usr/src/linux/arch/i386/kernel/head.S]
..
/*
* Clear BSS first so that there are no surprises...
*/
xorl %eax,%eax
movl $ SYMBOL_NAME(__bss_start),%edi
movl $ SYMBOL_NAME(_end),%ecx
subl %edi,%ecx
cld
rep
stosb
..
After reading the head.S file,the above code can be found,which clearly
expressed that it will clarify BSS range.The BSS area contains the
uninitialized variable which is not included in the kernel file,but the
kernel memory will leave the area to bss.So the lkm will be clear if just
appending the code to the kernel.To solve the problem some dummy data
can be added before the code the length of which is just equal to the bss
size.Though it will make the new kernel much larger,the compressed will
help to deflate all the zero data effectively.
However there is also another problem.Read through followed code
[/usr/src/linux/arch/i386/kernel/setup.c]
..
void __init setup_arch(char **cmdline_p)---called by start_kernel
..
init_mm.
/*
* partially used pages are not usable - thus
* we are rounding upwards:
*/
..
start_pfn = PFN_UP(__pa(&_end));start_code = (unsigned long) &_text;
init_mm.end_code = (unsigned long) &_etext;
init_mm.end_data = (unsigned long) &_edata;
init_mm.brk = (unsigned long) &_end;//it is bss end
..
The kernel wouldn't leave any space to the lkm unreasonable,so it will
manage the space available from the bss end which is just the beginning
of the LKM code.Therefore,the _end symbol in text should be modified to
give the start_pfn a larger value.Then the new kernel will be like this:
[modified kernel][all zero dummy][module]
--[ 4 - Relocate the symbol in module file
The module is common LKM file and its type is usually ELF object file,and
the object file need to be relocated before it could be used.The following
example make it easier to understand.
int init_module()
{
char s[] = "hello world\n";
printk("%s\n",s);
return 0;
}
After compiling the program by command gcc,the module.o is available:
[root@linux-jbtzhm test]#gcc -O2 -c module.c
[root@linux-jbtzhm test]# objdump -x module.o|more
..
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000004 R_386_32 .rodata
00000009 R_386_32 .rodata
0000000e R_386_PC32 printk
[root@linux-jbtzhm test]# objdump -d module.o
test.o: file format elf32-i386
Disassembly of section .text:
00000000 <init_module>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 68 00 00 00 00 push $0x0
8: 68 0d 00 00 00 push $0xd
d: e8 fc ff ff ff call e <init_module+0xe>
12: 31 c0 xor %eax,%eax
14: c9 leave
15: c3 ret
The object file structure is clear from the output of objdump.There are
three entries in the text relocation section,and the offset shows the place
should be corrected.For the printk symbol,the type is R_386_PC32 which
means relative call instruction(R_386_32 means absolute address).So after
relocation the value of "fc ff ff ff" in the text that calls printk will
be put out in the right value.
However,it is more complex than what can be described about the relocation,
and more information about it is available from ELF specifications.About
the implementation of relocation Silvio had written many codes in his
paper-RUNTIME KERNEL KMEM PATCHING-.Many lines are refereed from it and some
operations are added about uninitialized static and SHN_COMMON variables.
-- [ 5 - Make it autorun when reboot
After the above steps the new kernel appears like this
[modified kernel][all zero dummy][relocated module]
But the module don't have chance to be called,so the kernel running path
has to be changed to call the function init_module in lkm.My method is
adding some code between the dummy data and the module and changing the
value of sys_call_table[SYS_getpid] to the code.Many programs (like init)
will call getpid when machine reboots,then the code will be called.
char init_code[]={
"\xE8\x00\x00\x00\x00" //call init_module
"\xC7\x05\x00\x00\x00\x00\x11\x11\x11\x11" //restore orig_getpid
"\xE8\x11\x11\x11\x11" //call orig_getpid
"\xC3" //ret
};
All the relative and absolute addr is written by wrong value,but it is not
necessary to worry about that.When relocating the module the accurate
values about those address can be also make certain.
So the final new kernel had come int being.
[modified kernel][all zero dummy][init_code][relocated module]
Then the new kernel image followed the steps in Makefile is generated.Now
a new kernel patched by the module is available.
--[ 6 - Possible solutions
Deleting the /boot/System.map is the easiest way to prevent someone from
using this program without any modification.However Silvio had shown some
ways to generate the kernel symbol list from kernel.So it is not the final
solutions.Adding some module to prevent kernel image from being modified is
not a bad idea,but the precondition is the system should support the module.
When a kernel image was patched,its size and checksum could be detected,
so some function can be added to cheat the manager,but I don't have time
to do that.If you have more ideas please don't hesitate to contact me.
--[ 7 - Conclusion
Now it is clear that it is so easy to patch the kernel image.If the host
is compromised,nothing should be trusted,even if your own eyes.Halting the
machine and mounting the disk to another host is a good idea.
This paper is just for education.Please don't use it for other purposesA.
Sorry about my poor English and the dirty code of my program.Everything
should be better if I have more time.Though it could work well at
redhat 7.2,there maybe some problems if moved to all versions of linux
kernel.However,time is not enough for the tests on all kinds of
environment.
--[ 8 - References
[1] Silvio's article describing run-time kernel patching (System.map)
[http://www.big.net.au/~silvio/runtime-kernel-kmem-patching.txt]
[2] "Complete Linux Loadable Kernel Modules. The definitive guide
for hackers, virus coders and system administrators."
[http://www.thehackerschoice.com/papers]
[3] <<Linux Kernel:Scenarios and Analysis>> by Mao Decao and Hu Ximing
[4] linux kernel source
[http://www.kernel.org]
[5] Linux Kernel 2.4 Internals
[http://www.moses.uklinux.net/patches/lki.html]
At last,many thanks to Silvio the source about the relocation.Also to my
co-worker lgx who give me many good advise when i debug the program.At
the same time i extend special thanks to Hou Hanshu who help me correct
much grammar mistakes in english.
--[ 9 - Appendix: The implementation
begin 644 kpatch.tgz
end
|=[ EOF ]=---------------------------------------------------------------=|