17.1 Specific bit checking

17.1.1 x86

Win32 API 例子:

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

MSVC 2010: Listing 17.1: MSVC 2010

  1. push 0
  2. push 128 ; 00000080H
  3. push 4
  4. push 0
  5. push 1
  6. push -1073741824 ; c0000000H
  7. push OFFSET $SG78813
  8. call DWORD PTR __imp__CreateFileA@28
  9. mov DWORD PTR _fh$[ebp], eax

我们再查看WinNT.h:

Listing 17.2: WinNT.h

  1. #define GENERIC_READ (0x80000000L)
  2. #define GENERIC_WRITE (0x40000000L)
  3. #define GENERIC_EXECUTE (0x20000000L)
  4. #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)

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

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

if ((dwDesiredAccess&0x40000000) == 0) goto loc_7C83D417

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

我们在linux GCC 4.4.1下查看:

  1. #include <stdio.h>
  2. #include <fcntl.h>
  3. void main()
  4. {
  5. int handle;
  6. handle=open ("file", O_RDWR | O_CREAT);
  7. };

我们得到: Listing 17.4: GCC 4.4.1

  1. public main
  2. main proc near
  3. var_20 = dword ptr -20h
  4. var_1C = dword ptr -1Ch
  5. var_4 = dword ptr -4
  6. push ebp
  7. mov ebp, esp
  8. and esp, 0FFFFFFF0h
  9. sub esp, 20h
  10. mov [esp+20h+var_1C], 42h
  11. mov [esp+20h+var_20], offset aFile ; "file"
  12. call _open
  13. mov [esp+20h+var_4], eax
  14. leave
  15. retn
  16. main endp

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

  1. .text:000BE69B mov edx, [esp+4+mode] ; mode
  2. .text:000BE69F mov ecx, [esp+4+flags] ; flags
  3. .text:000BE6A3 mov ebx, [esp+4+filename] ; filename
  4. .text:000BE6A7 mov eax, 5
  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)

  1. do_filp_open proc near
  2. ...
  3. push ebp
  4. mov ebp, esp
  5. push edi
  6. push esi
  7. push ebx
  8. mov ebx, ecx
  9. add ebx, 1
  10. sub esp, 98h
  11. mov esi, [ebp+arg_4] ; acc_mode (5th arg)
  12. test bl, 3
  13. mov [ebp+var_80], eax ; dfd (1th arg)
  14. mov [ebp+var_7C], edx ; pathname (2th arg)
  15. mov [ebp+var_78], ecx ; open_flag (3th arg)
  16. jnz short loc_C01EF684
  17. mov ebx, ecx ; ebx <- open_flag

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

  1. loc_C01EF6B4: ; CODE XREF: do_filp_open+4F
  2. test bl, 40h ; O_CREAT
  3. jnz loc_C01EF810
  4. mov edi, ebx
  5. shr edi, 11h
  6. xor edi, 1
  7. and edi, 1
  8. test ebx, 10000h
  9. jz short loc_C01EF6D3
  10. or edi, 2

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

17.1.2 ARM

Linux kernel3.8.0检测O_CREAT过程有点不同。 Listing 17.8: linux kernel 3.8.0

  1. struct file *do_filp_open(int dfd, struct filename *pathname, const struct open_flags *op)
  2. {
  3. ... filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU); ...
  4. }
  5. static struct file *path_openat(int dfd, struct filename *pathname, struct nameidata *nd, const struct open_flags *op, int flags)
  6. {
  7. ... error = do_last(nd, &path, file, op, &opened, pathname); ...
  8. }
  9. static int do_last(struct nameidata *nd, struct path *path, struct file *file, const struct open_flags *op, int *opened, struct filename *name)
  10. {
  11. ...
  12. if (!(open_flag & O_CREAT)) {
  13. ...
  14. error = lookup_fast(nd, path, &inode);
  15. ...
  16. } else {
  17. ... error = complete_walk(nd);
  18. }
  19. ...
  20. }

在IDA中查看ARM模式内核: Listing 17.9: do_last() (vmlinux)

  1. ...
  2. .text:C0169EA8 MOV R9, R3 ; R3 - (4th argument) open_flag
  3. ...
  4. .text:C0169ED4 LDR R6, [R9] ; R6 - open_flag
  5. ...
  6. .text:C0169F68 TST R6, #0x40 ; jumptable C0169F00 default case
  7. .text:C0169F6C BNE loc_C016A128
  8. .text:C0169F70 LDR R2, [R4,#0x10]
  9. .text:C0169F74 ADD R12, R4, #8
  10. .text:C0169F78 LDR R3, [R4,#0xC]
  11. .text:C0169F7C MOV R0, R4
  12. .text:C0169F80 STR R12, [R11,#var_50]
  13. .text:C0169F84 LDRB R3, [R2,R3]
  14. .text:C0169F88 MOV R2, R8
  15. .text:C0169F8C CMP R3, #0
  16. .text:C0169F90 ORRNE R1, R1, #3
  17. .text:C0169F94 STRNE R1, [R4,#0x24]
  18. .text:C0169F98 ANDS R3, R6, #0x200000
  19. .text:C0169F9C MOV R1, R12
  20. .text:C0169FA0 LDRNE R3, [R4,#0x24]
  21. .text:C0169FA4 ANDNE R3, R3, #1
  22. .text:C0169FA8 EORNE R3, R3, #1
  23. .text:C0169FAC STR R3, [R11,#var_54]
  24. .text:C0169FB0 SUB R3, R11, #-var_38
  25. .text:C0169FB4 BL lookup_fast
  26. ...
  27. .text:C016A128 loc_C016A128 ; CODE XREF: do_last.isra.14+DC
  28. .text:C016A128 MOV R0, R4
  29. .text:C016A12C BL complete_walk
  30. ...

TST指令类似于x86下的TEST指令。 这段代码来自do_last()函数源码,有两个分支lookup_fast()和complete_walk()。这里O_CREAT宏也等于0x40。