返回介绍

17.1 Specific bit checking

发布于 2025-02-22 14:00:46 字数 3840 浏览 0 评论 0 收藏 0

17.1.1 x86

Win32 API 例子:

#!cpp
    HANDLE fh;

    fh=CreateFile ("file", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);

MSVC 2010: Listing 17.1: MSVC 2010

#!bash
push    0
push    128            ; 00000080H
push    4
push    0
push    1
push    -1073741824    ; c0000000H
push    OFFSET $SG78813
call    DWORD PTR __imp__CreateFileA@28
mov     DWORD PTR _fh$[ebp], eax

我们再查看 WinNT.h:

Listing 17.2: WinNT.h

#!cpp
#define GENERIC_READ (0x80000000L)
#define GENERIC_WRITE (0x40000000L)
#define GENERIC_EXECUTE (0x20000000L)
#define GENERIC_ALL (0x10000000L)

容易看出 GENERIC_READ | GENERIC_WRITE = 0x80000000 | 0x40000000 = 0xC0000000 ,该值作为 CreateFile()1 函数的第二个参数。 CreateFile() 如何检查该标志呢? 以 Windows XP SP3 x86 为例,在 kernel32.dll 中查看 CreateFileW 检查该标志的代码片段: Listing 17.3: KERNEL32.DLL (Windows XP SP3 x86)

#!bash
.text:7C83D429 test byte ptr [ebp+dwDesiredAccess+3], 40h
.text:7C83D42D mov [ebp+var_8], 1
.text:7C83D434 jz short loc_7C83D417
.text:7C83D436 jmp loc_7C810817

我们来看 TEST 指令,该指令并未检测整个第二个参数,仅检测关键的一个字节(ebp+dwDesiredAccess+3),检测 0x40 标志(这里代表 GENERIC_WRITE 标志)。 Test 对两个参数(目标,源) 执行 AND 逻辑操作,并根据结果设置标志寄存器,结果本身不会保存(CMP 和 SUB 与此类似(6.6.1))。 该代码片段逻辑如下:

#!cpp
if ((dwDesiredAccess&0x40000000) == 0) goto loc_7C83D417

如果 AND 指令没有设置 ZF 位,JZ 将不触发跳转。如果 dwDesiredAccess 不等于 0x40000000,AND 结果将是 0,ZF 位将会被设置,条件跳转将被触发。

我们在 linux GCC 4.4.1 下查看:

#!bash
#include <stdio.h>
#include <fcntl.h>
void main()
{
    int handle;

    handle=open ("file", O_RDWR | O_CREAT);
};

我们得到: Listing 17.4: GCC 4.4.1

#!bash
    public main
main proc near

var_20 = dword ptr -20h
var_1C = dword ptr -1Ch
var_4 = dword ptr -4

    push ebp
    mov ebp, esp
    and esp, 0FFFFFFF0h
    sub esp, 20h
    mov [esp+20h+var_1C], 42h
    mov [esp+20h+var_20], offset aFile ; "file"
    call _open
    mov [esp+20h+var_4], eax
    leave
    retn
main endp

我们在 libc.so.6 库中查看 open() 函数,看到 syscall: Listing 17.5: open() (libc.so.6)

#!bash
.text:000BE69B mov edx, [esp+4+mode] ; mode
.text:000BE69F mov ecx, [esp+4+flags] ; flags
.text:000BE6A3 mov ebx, [esp+4+filename] ; filename
.text:000BE6A7 mov eax, 5
.text:000BE6AC int 80h ; LINUX - sys_open

因此 open() 对于标志位的检测在内核中。 对于 linux2.6,当 sys_open 被调用时,最终传递到 do_sys_open 内核函数,然后进入 do_filp_open() 函数(该函数位于源码 fs/namei.c 中)。 除了通过堆栈传递参数,还可以通过寄存器传递方式,这种调用方式成为 fastcall(47.3)。这种调用方式 CPU 不需要访问堆栈就可以直接读取参数的值,所以速度更快。GCC 有编译选项 regram2,可以设置通过寄存器传递的参数的个数。 Linux2.6 内核编译附加选项为-mregram=33 4。 这意味着前 3 个参数通过 EAX、EDX、ECX 寄存器传递,剩余的参数通过堆栈传递。如果参数小于 3,仅部分寄存器被使用。 我们下载 linux 内核 2.6.31 源码,在 Ubuntu 中编译:make vmlinux,在 IDA 中打开,找到 do_filp_open() 函数。在开始部分我们可以看到(注释个人添加): Listing 17.6:do_filp_open() (linux kernel 2.6.31)

#!bash
do_filp_open proc near
...
    push ebp
    mov ebp, esp
    push edi
    push esi
    push ebx
    mov ebx, ecx
    add ebx, 1
    sub esp, 98h
    mov esi, [ebp+arg_4] ; acc_mode (5th arg)
    test bl, 3
    mov [ebp+var_80], eax ; dfd (1th arg)
    mov [ebp+var_7C], edx ; pathname (2th arg)
    mov [ebp+var_78], ecx ; open_flag (3th arg)
    jnz short loc_C01EF684
    mov ebx, ecx ; ebx <- open_flag

GCC 保存 3 个参数的值到堆栈。否则,可能会造成寄存器浪费。 我们来看代码片段: Listing 17.7: do_filp_open() (linux kernel 2.6.31)

#!bash
loc_C01EF6B4:            ; CODE XREF: do_filp_open+4F
    test bl, 40h         ; O_CREAT
    jnz loc_C01EF810
    mov edi, ebx
    shr edi, 11h
    xor edi, 1
    and edi, 1
    test ebx, 10000h
    jz short loc_C01EF6D3
    or edi, 2

O_CREAT 宏等于 0x40,如果 open_flag 为 0x40,标志位被置 1,接下来的 JNZ 指令将被触发。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文