绕过 libc 直接使用系统调用


当前版本: 0.1
完成日期: 2007-6-15
作者: Dajie Tan <jiankemeng@gmail.com>


系统调用是用户态进程切入内核态的唯一入口,是内核为用户态进程提供服务的
接口。Linux Kernel 提供了大约300个左右的系统调用,作为用户空间进程访问
内核的 API。

C 语言环境下,经常使用的(如getpid)是系统基础库 libc 封装过的。在其
后面究竟隐藏着什么样的秘密呢?


1. 先看一个例子:

#include <stdio.h>

int main()
{
long ID1;

ID1 = getpid();
printf("pid=%d/n", ID1);

return 0;
}

存为 sc.c,如下命令编译之:

gcc sc.c -o sc -static


看看它的汇编码:

objdump -d sc > sc.S

查看 sc.S 我们发现 getpid 被 libc 替换成 __getpid,而这个函数只有下面
的 3 行指令:

0804e0d0 <__getpid>:
804e0d0: b8 14 00 00 00 mov $0x14,%eax
804e0d5: cd 80 int $0x80
804e0d7: c3 ret

将 0x14 置于 eax 后,触发 0x80 号软件中断。

这个 0x80 号软件中断服务程序,就是内核系统调用的统一入口。

至于如何区分具体的是哪个系统调用,则通过 eax 传过来的整型值来区分。内
核定义了一个系统调用表,表的每项是一个函数指针,指向具体的实现函数,
每个系统调用函数在表中的位置固定,其序号 (从 0 开始计数)就是该系统
调用的系统调用号。实际上就是一个元素固定的数组。如 i386 内核,系统调
用表名为 sys_call_table,则 sys_call_table[20] 就是 getpid 的函数指针:

[arch/i386/kernel/syscall_table.S]

ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long sys_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
.long sys_unlink /* 10 */
.long sys_execve
.long sys_chdir
.long sys_time
.long sys_mknod
.long sys_chmod /* 15 */
.long sys_lchown16
.long sys_ni_syscall /* old break syscall holder */
.long sys_stat
.long sys_lseek
.long sys_getpid /* 20 */
.long sys_mount

...

int 0x80 触发,使内核进入相应的处理程序,在保存了寄存器后,就使用 eax
的值索引 sys_call_table,得到地址后,跳转过去就进入了实质的服务函数。

因此,我们可以改写上面的程序为:

#include <stdio.h>

int main()
{
long ID1;

//ID1 = getpid();
asm volatile(
"int $0x80/n/t"
: "=a" (ID1) /* eax 亦是返回值所在,因此要与ID1 对应 */
: "0" (20) /* 20 置入 eax */
);

printf("pid=%d/n", ID1);

return 0;
}

编译 gcc sc.c -o sc,执行之,结果是一样的。

上面简单的系统调用不需要传递参数,当需要传递 3 个参数时,那么如何处理呢?


2. 参数传递

i386 内核实现系统调用时有一个参数传递约定:

ebx --- 置第一个参数
ecx --- 置第二个参数
edx --- 置第三个参数
esi --- 置第四个参数
edi --- 置第五个参数
ebp --- 置第六个参数 (系统调用最大参数个数为6)

特别注意,使用 ebp 前要先将其进栈, int 0x80 返回后,要恢复之。



3. 使用标准宏

上面使用嵌入式汇编不是很方便,可以使用 POSIX 标准约定的一系列宏:

#include <linux/unistd.h>

_syscallX(type, name, type1, arg1, type2, arg2, ...)

其中 X 取值 0~5 表示系统调用的参数个数
name 为系统调用的名字,如 getpid, read 等
type1 为第一个参数类型
arg1 为第一个参数值
type2 为第二个参数类型
arg2 为第二个参数值
...

很显然,使用这些宏,要对所用之系统调用的参数个数和类型有清晰的了解。

如上面的程序改写为:

#include <stdio.h>
#include <linux/unistd.h>

_syscall0(int, getpid);

int main()
{
long id = getpid();
printf("id = %d/n", id);

return 0;
}

就绕过了 libc。可以使用 gcc -E sc.c > sc.C 看看预处理后的结果,就知道
这个_syscall 宏做了什么事了。

这些宏定义于内核源码目录的 include/asm-i386/unistd.h 下,抑或
/usr/include/asm-i486/unistd.h 下。


4. 查看系统调用号

可以查看内核源码目录的 include/asm-i386/unistd.h,该文件中定义了一系列以
__NR_ 开头的常量,紧随其后的即为系统调用名,相应的即为系统调用号。

更多的参考: http://people.openrays.org/~comcat/mydoc/syscall_ref.html

 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值