最近打算实现一个iot的fuzz,在此过程中遇到了许多问题,所以尝试通过阅读qemu源码来解决,不过现在的qemu已经是相当庞大的项目了,要想从当前版本的源码入手对于我这种小白来说过于困难,所以我会从历史版本入手,逐步分析v0.1.6,v0.10.6,v2.10.0这三个版本,本篇是对v0.1.6版本的源码分析。
v0.1.6 这个版本是qemu
相当早期的一个版本,许多后来我们熟知的功能比如tcg
等,此时还没有引入,但对于我们了解qemu
的结构,以及功能实现也有帮助,并且由于是早期代码,功能相对较少,读起来也相对容易,适合我这种新手阅读。
首先是参数解析的部分:
其中L用来指定解释器前缀,通常用来加载elf程序,比如 ld-linux.so.2
。
接着是
这两个函数用来清空regs和image_info的内存区域,其中target_pt_regs
结构体定义了在x86架构下的通用寄存器组,用来模拟真实寄存器,而image_info
结构体则用来描述ELF文件的信息定义如下:
接下来会调用:
进入elf_exec这个函数:
这里首先定义了一个struct linux_binprm
类型的变量bprm
,在linux系统中,这个结构体通常用来描述一个ELF的信息,定义如下:
其中filename用来指向要执行的文件名,而p用来指向一个内存地址,在上面的源码中可以看到bprm.p
指向了X86_PAGE_SIZE*MAX_ARG_PAGES-sizeof(unsigned int)
这个地址,而这个地址是通过将一个页面的大小乘以最大页数得到的,是最高的地址,类似进程内存映像中的内核区,后续为了方便就把这部分称为内核区,接下来的部分就是对bprm结构体中的各种变量初始化。初始化完成后,会拷贝一些环境变量与参数到内核区,代码如下:
这三个函数分别把要执行的文件名,参数个数,以及环境变量拷贝到bprm.p
指向的区域,并返回新的bprm.p
值。接下来便会加载elf文件:
其中load_elf_binary
函数部分代码如下:
这里定义了许多变量,先关注elf_ex
这个变量,他是struct elfhdr
类型的变量,查看交叉引用可以知道,该结构体是一个宏,实际上是struct elf32_hdr
,该结构体定义了elf头部的常见的字段,接下来会检查elf文件是否合法,代码如下:
elf_ex.e_indent
是elf头部的魔数,首先检查elf_ex.e_indent[0]
的位置是否为0x7f,其次检查elf_ex.e_indent[1-3]
的位置是否为ELF,如果是则合法,否则返回-ENOEXEC,表示文件非法,不可执行。
接下来会对于整个ELF文件的信息,代码如下:
其中读入的大小为:elf_ex.e_phentsize*elf_ex.e_phnum
,即elf中每个表项的大小乘以表项的数量,然后通过lseek
函数将读写指针移动到e_phoff
的位置,这个变量表示的是程序头表在ELF文件中的偏移,接着将程序头表读入到刚刚申请的空间中。
接下来便开始解析头表,首先初始化了如下变量,用于保存程序的各个段信息:
然后循环遍历各个表项的值,并逐个解析:
这段代码将加载ld与其他段到内存中,为执行做准备,后面会对各个段做初始化,需要注意的是堆的结束地址也就是info->brk
的值在这之中被确定,至于为什么brk存放的是堆的结束地址,个人觉得是因为当需要分配堆内存时,可以通过该变量配合检查是否还有足够的空间进行分配。
这段代码完成后将返回elf_exec
函数继续执行:
当上述步骤成功完成后,接下来会对栈顶指针和eip做初始化,值为栈的起始地址以及elf文件的加载基地址,并返回main函数执行 syscall_init();
与signal_init();
这两个函数,下面分别分析这两个函数。
syscall_init
函数内容如下:
这里使用了thunck
机制向内核中注册结构体,thunck
机制提供了允许在用户态下访问内核的结构体或者函数,允许用户态的代码与内核态的代码进行通信等功能。
例如当我们写出如下代码:
则会通过该宏定义转换为如下代码:
这段代码将向内核注册名为test
的结构体,其中第三个参数为test
结构体的定义。
signal_init
函数代码如下:
这段代码注册了信号处理的队列,需要注意的是使用sigfillset
将信号加入act.sa_mask
中,是为了在处理当前信号是不被中断,若处理当前信号时又来了新的信号,将会加入队列sigqueue_table
中,稍后处理。
执行完上述两个函数后,将会继续执行cpu_x86_init()
这个函数用于设置基本的运行环境,该函数代码如下:
接下来将执行:
其中TaskState的定义如下:
这是一个关于任务的结构体,在qemu中,可能需要并发执行多个任务。每个任务可以有自己的 CPU 寄存器状态、栈空间等。TaskState
结构体可以用于管理这些任务的状态,并在任务切换时保存和恢复寄存器状态,用next指针将任务链接起来,taget_vm86plus_struct
结构体中嵌套的定义太多,这里只挑一些关键点说明,这个结构体中定义了x86架构下的通用寄存器组,以及一些关于中断的结构体定义。
将ts的值赋值给env-opaque
变量,这个变量指向了用户的数据。
接下来将对各种寄存器以及段进行初始化,然后将进入cpu_loop
开始模拟执行程序。
cpu_loop
函数用于模拟cpu执行的过程,首先会调用cpu_x86_exec
函数进行执行,该函数首先定义了如下变量:
其中*tb
用于指向某一个特定的翻译块,而**ptb
这个二重指针可以认为是一个指针数组,用来指向所有的翻译块。
这里重点关注TranslationBlock
结构体,其定义如下:
pc用于指向下一个要执行的地址,是对程序计数器的模拟,采用了基址寻址的方式,值为EIP + CS BASE
,EIP
为寄存器的值指向要执行的语句,并加上cs段的基址来得到pc的值。
flags用于表示该翻译块的属性。
tc_ptr用于指向翻译得到的代码,即当前基本块经过翻译后得到的机器指令。
hash_next指向下一个具有相同hash值的TranslationBlock
结构体,hash_next的作用类似于cache,若当前指令已经被翻译过,则直接找到存放该指令的TranslationBlock
,而不需要再次翻译,从而提高速度。
该结构体的具体作用就是用来存放在执行过程中,经过翻译得到的机器码。
接下来将执行:
这段代码中,先是将env->interrupt_request
设置为0,表示开始执行时,无任何中断请求,接下来的一大段移位操作可以不管,但我们需要注意,此时确定了cs_base的值为env->seg_cache[R_CS].base
,seg_cache
数组存放了来源于LDT
或者GDT
中的段信息,从其中读出CS的地址,并赋值给变量cs_base
,从而计算出PC寄存器的值,从而得到了第一条指令的地址。
接下来便会进入tb_find函数用于寻找当前翻译块是否被翻译过了,代码如下:
在这里我们可以看到hash值是如何计算的,通过将hash表的大小-1和pc寄存器的值相遇得到hash值。
若当前代码以及被翻译过,则在对应的tb_hash[h]
中就能找到对应项,然后通过循环检查其pc、cs_base、flags等值是否匹配,如果匹配则返回该翻译块,而不需要再次翻译。
返回以后,检查tb
的值,若为0,则说明缓存中并未找到翻译块,说明未被翻译过,则开始翻译,代码如下:
首先对通过TestAndSet
方法,模拟硬件上锁的过程,然后将code_gen_ptr
的值赋给tc_ptr
,code_gen_ptr
用于指向当前正在生成的代码,而tc_ptr
用于指向翻译后的代码。
然后将调用cpu_x86_gen_code
来生成代码,具体内容如下:
这里首先通过对flags的移位操作来设置变量dc的一些字段,然后设置要翻译的指令的起始与结束,并将传入的参数赋值给变量,其中dc的结构体定义如下:
该结构体各字段意义已经有了注释,就不在此过多阐述,接下来便会调用disas_insn
来进行反汇编,对于这部分不做过多分析,但需要注意的是,在disas_insn
中,指令反汇编后将会直接执行,并在最后返回时,返回异常的编号,并在cpu_loop
中进行异常处理,对于系统调用的处理代码如下:
这里首先检查pc的值是否为0xcd80
,这是int 0x80
的机器码,如果是,则说明进行了系统调用,并将寄存器的值传入到do_syscall
函数中,进行处理,并把返回值保存到EAX
寄存器中,do_syscall
部分代码如下:
这里的num就是系统调用号,我们可以看到,在qemu早期的版本中,对于open
、read
、write
等系统调用,都是直接执行相关函数,从而实现对系统调用的模拟。
通过学习qemu的源码,让我对qemu的实现有了一个大概的认识,虽然分析的比较浅显以及跳过了许多代码,但也希望能给和我一样的初学者一些帮助。
本人水平有限,如果有任何问题欢迎师傅们指出。
参考链接:
https://github.com/lishuhuakai/qemu_reading
for
(;;)
{
if
(optind >= argc)
break
;
r = argv[optind];
if
(r[0] !=
'-'
)
break
;
optind++;
r++;
if
(!
strcmp
(r,
"-"
))
{
break
;
}
else
if
(!
strcmp
(r,
"d"
))
{
loglevel = 1;
}
else
if
(!
strcmp
(r,
"s"
))
{
r = argv[optind++];
x86_stack_size =
strtol
(r, (
char
**)&r, 0);
if
(x86_stack_size <= 0)
usage();
if
(*r ==
'M'
)
x86_stack_size *= 1024 * 1024;
else
if
(*r ==
'k'
|| *r ==
'K'
)
x86_stack_size *= 1024;
}
else
if
(!
strcmp
(r,
"L"
))
{
interp_prefix = argv[optind++];
}
else
{
usage();
}
}
for
(;;)
{
if
(optind >= argc)
break
;
r = argv[optind];
if
(r[0] !=
'-'
)
break
;
optind++;
r++;
if
(!
strcmp
(r,
"-"
))
{
break
;
}
else
if
(!
strcmp
(r,
"d"
))
{
loglevel = 1;
}
else
if
(!
strcmp
(r,
"s"
))
{
r = argv[optind++];
x86_stack_size =
strtol
(r, (
char
**)&r, 0);
if
(x86_stack_size <= 0)
usage();
if
(*r ==
'M'
)
x86_stack_size *= 1024 * 1024;
else
if
(*r ==
'k'
|| *r ==
'K'
)
x86_stack_size *= 1024;
}
else
if
(!
strcmp
(r,
"L"
))
{
interp_prefix = argv[optind++];
}
else
{
usage();
}
}
memset
(regs, 0,
sizeof
(
struct
target_pt_regs));
memset
(info, 0,
sizeof
(
struct
image_info));
memset
(regs, 0,
sizeof
(
struct
target_pt_regs));
memset
(info, 0,
sizeof
(
struct
image_info));
struct
image_info {
unsigned
long
start_code;
unsigned
long
end_code;
unsigned
long
end_data;
unsigned
long
start_brk;
unsigned
long
brk;
unsigned
long
start_mmap;
unsigned
long
mmap;
unsigned
long
rss;
unsigned
long
start_stack;
unsigned
long
arg_start;
unsigned
long
arg_end;
unsigned
long
env_start;
unsigned
long
env_end;
unsigned
long
entry;
int
personality;
};
struct
image_info {
unsigned
long
start_code;
unsigned
long
end_code;
unsigned
long
end_data;
unsigned
long
start_brk;
unsigned
long
brk;
unsigned
long
start_mmap;
unsigned
long
mmap;
unsigned
long
rss;
unsigned
long
start_stack;
unsigned
long
arg_start;
unsigned
long
arg_end;
unsigned
long
env_start;
unsigned
long
env_end;
unsigned
long
entry;
int
personality;
};
if
(elf_exec(filename, argv+optind, environ, regs, info) != 0)
{
printf
(
"Error loading %s\n"
, filename);
_exit(1);
}
if
(elf_exec(filename, argv+optind, environ, regs, info) != 0)
{
printf
(
"Error loading %s\n"
, filename);
_exit(1);
}
int
elf_exec(
const
char
* filename,
char
** argv,
char
** envp,
struct
target_pt_regs * regs,
struct
image_info *infop)
{
struct
linux_binprm bprm;
int
retval;
int
i;
bprm.p = X86_PAGE_SIZE*MAX_ARG_PAGES-
sizeof
(unsigned
int
);
for
(i=0 ; i<MAX_ARG_PAGES ; i++)
bprm.page[i] = 0;
retval = open(filename, O_RDONLY);
if
(retval == -1)
{
perror
(filename);
exit
(-1);
}
else
{
bprm.fd = retval;
}
bprm.filename = (
char
*)filename;
bprm.sh_bang = 0;
bprm.loader = 0;
bprm.exec = 0;
bprm.dont_iput = 0;
bprm.argc = count(argv);
bprm.envc = count(envp);
int
elf_exec(
const
char
* filename,
char
** argv,
char
** envp,
struct
target_pt_regs * regs,
struct
image_info *infop)
{
struct
linux_binprm bprm;
int
retval;
int
i;
bprm.p = X86_PAGE_SIZE*MAX_ARG_PAGES-
sizeof
(unsigned
int
);
for
(i=0 ; i<MAX_ARG_PAGES ; i++)
bprm.page[i] = 0;
retval = open(filename, O_RDONLY);
if
(retval == -1)
{
perror
(filename);
exit
(-1);
}
else
{
bprm.fd = retval;
}
bprm.filename = (
char
*)filename;
bprm.sh_bang = 0;
bprm.loader = 0;
bprm.exec = 0;
bprm.dont_iput = 0;
bprm.argc = count(argv);
bprm.envc = count(envp);
struct
linux_binprm
{
char
buf[128];
unsigned
long
page[MAX_ARG_PAGES];
unsigned
long
p;
int
sh_bang;
int
fd;
int
e_uid, e_gid;
int
argc;
int
envc;
char
* filename;
unsigned
long
loader;
unsigned
long
exec;
int
dont_iput;
};
struct
linux_binprm
{
char
buf[128];
unsigned
long
page[MAX_ARG_PAGES];
unsigned
long
p;
int
sh_bang;
int
fd;
int
e_uid, e_gid;
int
argc;
int
envc;
char
* filename;
unsigned
long
loader;
unsigned
long
exec;
int
dont_iput;
};
if
(retval>=0)
{
bprm.p = copy_strings(1, &bprm.filename, bprm.page, bprm.p);
bprm.exec = bprm.p;
bprm.p = copy_strings(bprm.envc,envp,bprm.page,bprm.p);
bprm.p = copy_strings(bprm.argc,argv,bprm.page,bprm.p);
if
(!bprm.p)
{
retval = -E2BIG;
}
}
if
(retval>=0)
{
bprm.p = copy_strings(1, &bprm.filename, bprm.page, bprm.p);
bprm.exec = bprm.p;
bprm.p = copy_strings(bprm.envc,envp,bprm.page,bprm.p);
bprm.p = copy_strings(bprm.argc,argv,bprm.page,bprm.p);
if
(!bprm.p)
{
retval = -E2BIG;
}
}
if
(retval>=0)
{
retval = load_elf_binary(&bprm,regs,infop);
}
if
(retval>=0)
{
retval = load_elf_binary(&bprm,regs,infop);
}
static
int
load_elf_binary(
struct
linux_binprm * bprm,
struct
target_pt_regs * regs,
struct
image_info * info)
{
struct
elfhdr elf_ex;
struct
elfhdr interp_elf_ex;
struct
exec interp_ex;
int
interpreter_fd = -1;
unsigned
long
load_addr, load_bias;
int
load_addr_set = 0;
unsigned
int
interpreter_type = INTERPRETER_NONE;
unsigned
char
ibcs2_interpreter;
int
i;
void
* mapped_addr;
struct
elf_phdr * elf_ppnt;
struct
elf_phdr *elf_phdata;
unsigned
long
elf_bss, k, elf_brk;
int
retval;
char
* elf_interpreter;
unsigned
long
elf_entry, interp_load_addr = 0;
int
status;
unsigned
long
start_code, end_code, end_data;
unsigned
long
elf_stack;
char
passed_fileno[6];
ibcs2_interpreter = 0;
status = 0;
load_addr = 0;
load_bias = 0;
elf_ex = *((
struct
elfhdr *) bprm->buf);
static
int
load_elf_binary(
struct
linux_binprm * bprm,
struct
target_pt_regs * regs,
struct
image_info * info)
{
struct
elfhdr elf_ex;
struct
elfhdr interp_elf_ex;
struct
exec interp_ex;
int
interpreter_fd = -1;
unsigned
long
load_addr, load_bias;
int
load_addr_set = 0;
unsigned
int
interpreter_type = INTERPRETER_NONE;
unsigned
char
ibcs2_interpreter;
int
i;
void
* mapped_addr;
struct
elf_phdr * elf_ppnt;
struct
elf_phdr *elf_phdata;
unsigned
long
elf_bss, k, elf_brk;
int
retval;
char
* elf_interpreter;
unsigned
long
elf_entry, interp_load_addr = 0;
int
status;
unsigned
long
start_code, end_code, end_data;
unsigned
long
elf_stack;
char
passed_fileno[6];
ibcs2_interpreter = 0;
status = 0;
load_addr = 0;
load_bias = 0;
elf_ex = *((
struct
elfhdr *) bprm->buf);
if
(elf_ex.e_ident[0] != 0x7f ||
strncmp
(&elf_ex.e_ident[1],
"ELF"
,3) != 0)
{
return
-ENOEXEC;
}
if
(elf_ex.e_ident[0] != 0x7f ||
strncmp
(&elf_ex.e_ident[1],
"ELF"
,3) != 0)
{
return
-ENOEXEC;
}
elf_phdata = (
struct
elf_phdr *)
malloc
(elf_ex.e_phentsize*elf_ex.e_phnum);
if
(elf_phdata == NULL)
{
return
-ENOMEM;
}
retval = lseek(bprm->fd, elf_ex.e_phoff, SEEK_SET);
if
(retval > 0)
{
retval = read(bprm->fd, (
char
*) elf_phdata,
elf_ex.e_phentsize * elf_ex.e_phnum);
}
if
(retval < 0)
{
perror
(
"load_elf_binary"
);
exit
(-1);
free
(elf_phdata);
return
-
errno
;
}
elf_phdata = (
struct
elf_phdr *)
malloc
(elf_ex.e_phentsize*elf_ex.e_phnum);
if
(elf_phdata == NULL)
{
return
-ENOMEM;
}
retval = lseek(bprm->fd, elf_ex.e_phoff, SEEK_SET);
if
(retval > 0)
{
retval = read(bprm->fd, (
char
*) elf_phdata,
elf_ex.e_phentsize * elf_ex.e_phnum);
}
if
(retval < 0)
{
perror
(
"load_elf_binary"
);
exit
(-1);
free
(elf_phdata);
return
-
errno
;
}
elf_ppnt = elf_phdata;
elf_bss = 0;
elf_brk = 0;
elf_stack = ~0UL;
elf_interpreter = NULL;
start_code = ~0UL;
end_code = 0;
end_data = 0;
elf_ppnt = elf_phdata;
elf_bss = 0;
elf_brk = 0;
elf_stack = ~0UL;
elf_interpreter = NULL;
start_code = ~0UL;
end_code = 0;
end_data = 0;
for
(i=0; i < elf_ex.e_phnum; i++)
{
if
(elf_ppnt->p_type == PT_INTERP)
{
if
( elf_interpreter != NULL )
{
free
(elf_phdata);
free
(elf_interpreter);
close(bprm->fd);
return
-EINVAL;
}
elf_interpreter = (
char
*)
malloc
(elf_ppnt->p_filesz);
if
(elf_interpreter == NULL)
{
free
(elf_phdata);
close(bprm->fd);
return
-ENOMEM;
}
retval = lseek(bprm->fd, elf_ppnt->p_offset, SEEK_SET);
if
(retval >= 0)
{
retval = read(bprm->fd, elf_interpreter, elf_ppnt->p_filesz);
}
if
(retval < 0)
{
perror
(
"load_elf_binary2"
);
exit
(-1);
}
if
(
strcmp
(elf_interpreter,
"/usr/lib/libc.so.1"
) == 0 ||
strcmp
(elf_interpreter,
"/usr/lib/ld.so.1"
) == 0)
{
ibcs2_interpreter = 1;
}
#if 0
printf
(
"Using ELF interpreter %s\n"
, elf_interpreter);
#endif
if
(retval >= 0)
{
retval = open(path(elf_interpreter), O_RDONLY);
if
(retval >= 0)
{
interpreter_fd = retval;
}
else
{
perror
(elf_interpreter);
exit
(-1);
}
}
if
(retval >= 0)
{
retval = lseek(interpreter_fd, 0, SEEK_SET);
if
(retval >= 0)
{
retval = read(interpreter_fd,bprm->buf,128);
}
}
if
(retval >= 0)
{
interp_ex = *((
struct
exec *) bprm->buf);
interp_elf_ex=*((
struct
elfhdr *) bprm->buf);
}
if
(retval < 0)
{
perror
(
"load_elf_binary3"
);
exit
(-1);
free
(elf_phdata);
free
(elf_interpreter);
close(bprm->fd);
return
retval;
}
}
elf_ppnt++;
}
for
(i=0; i < elf_ex.e_phnum; i++)
{
if
(elf_ppnt->p_type == PT_INTERP)
{
if
( elf_interpreter != NULL )
{
free
(elf_phdata);
free
(elf_interpreter);
close(bprm->fd);
return
-EINVAL;
}
elf_interpreter = (
char
*)
malloc
(elf_ppnt->p_filesz);
if
(elf_interpreter == NULL)
{
free
(elf_phdata);
close(bprm->fd);
return
-ENOMEM;
}
retval = lseek(bprm->fd, elf_ppnt->p_offset, SEEK_SET);
if
(retval >= 0)
{
retval = read(bprm->fd, elf_interpreter, elf_ppnt->p_filesz);
}
if
(retval < 0)
{
perror
(
"load_elf_binary2"
);
exit
(-1);
}
if
(
strcmp
(elf_interpreter,
"/usr/lib/libc.so.1"
) == 0 ||
strcmp
(elf_interpreter,
"/usr/lib/ld.so.1"
) == 0)
{
ibcs2_interpreter = 1;
}
#if 0
printf
(
"Using ELF interpreter %s\n"
, elf_interpreter);
#endif
if
(retval >= 0)
{
retval = open(path(elf_interpreter), O_RDONLY);
if
(retval >= 0)
{
interpreter_fd = retval;
}
else
{
perror
(elf_interpreter);
exit
(-1);
}
}
if
(retval >= 0)
{
retval = lseek(interpreter_fd, 0, SEEK_SET);
if
(retval >= 0)
{
retval = read(interpreter_fd,bprm->buf,128);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)