💾 Archived View for aphrack.org › issues › phrack60 › 8.gmi captured on 2022-03-01 at 15:52:17. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
==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 $@ {body}lt; bootsect.o: bootsect.s $(AS) -o $@ {body}lt; bootsect.s: bootsect.S Makefile $(BOOT_INCL) $(CPP) $(CPPFLAGS) -traditional $(SVGA_MODE) $(RAMDISK) \ {body}lt; -o $@ .. setup: setup.o $(LD) -Ttext 0x0 -s --oformat binary -e begtext -o $@ {body}lt; setup.o: setup.s $(AS) -o $@ {body}lt; 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) {body}lt; -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 M'XL(`$\'Y#T``^P\2W`<QW6@1"N<M6++43Y.G#C-50CM_X<?C2404A0HL42" M+`"T(T.H]6)W%CO$[LQZ9Y8`*2&1?8E\<U4N.<15KDH..215ON20N%*E0U*5 M2BZIW'+3):=<?/`E.:3R/MT]W;.S`,F8DE/F%`:[W?VZ^_5[K]]O>N=PU(XZ M_>K<L[QJM<7:RM(2?-*5_.3O*TLK]5IM87%E>:Y6K]<6%^;$TC/%2EZ3,&J/ MA9@;!T%T&MQ9[?]/KT/F_^'0'58ZSVB.&C!V>7%Q%O^7:TO+Q/_:XG)]9;D! M\(WZ4GU.U)X1/M;U"\[_:D%\8^Q%KB_V'XIM;_#`"\1U-VR/77&E0Y_AU2@8 MM1]5.M^>5-SNI-*>K&=$07BBZW7]UR,Q#+I>[Z'PHHI8+>>AJ9IYS?,[@TD7 MA@BCKA=4^NMVU<#;M^LZT<.1FP0;>_Z!73?Q/>AMU_4Z?C28FB$YZ<#S)\=5 M0'4R<--:4D;.TI[H9S,90&72B<1A^'`X`##Q?L9)5!4<WSV.FG&]._;=00M: M'01I9DZ:4\/TW7;WE*&P1[60<0!.]-MA7X3>(U<,05S%OBO:OO#\R#T8MP=B M%!RY8Q'T1'049(#ZF=>Z;L_S7?'.]KNW6V]?VWZ[M7WSFQO.4KV1C@06<(I= MN\->C