逆向工程——scanf()

[TOC]

程序

局部变量

1
2
3
4
5
6
7
8
#include<stdio.h>
int main(){
int x;
printf("Enter X:\n");
scanf("%d",&x);
printf("You entered %d...\n",x);
return 0;
}

全局变量

1
2
3
4
5
6
7
8
9
#include<stdio.h>
//now,x is global variable
int x;
int main(){
printf("Enter X:\n");
scanf("%d",&x);
printf("You entered %d...\n",x);
return 0;
}

scanf()函数的状态监测

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(){
int x;
printf ("Enter X:\n");
if (scanf ("%d", &x)==1)
printf ("You entered %d...\n", x);
else
printf ("What you entered? Huh?\n");
return 0;
};

x86

x86局部变量

MSVC(2010)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
CONST SEGMENT
$SG3831 DB 'Enter X:',0aH,00H
$SG3832 DB '%d',00H
$SG3833 DB 'You entered %d...',0aH,00H
CONST ENDS
PUBLIC _main
EXTRN _scanf:PROC
EXTRN _printf:PROC
;Function compile flags:/Odtp
_TEXT SEGMENT
_x$=-4 ;size=4
_main PROC
push ebp
mov ebp,esp
push ecx
push OFFSET $SG3831 ;'Enter X:'
call _printf
add esp,4
lea eax,DWORD PTR _x$[ebp]
push eax
push OFFSET $SG3832 ;'%d'
call _scanf
add esp,8
mov ecx,DWORD PTR _x$[ebp]
push ecx
push OFFSET $SG3833 ;'You entered %d...'
call _printf
add esp,8

;rentuen 0
xor eax,eax
mov esp,ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS

####OllyDbg调试
现在使用 OllyDbg 调试,加载程序之后,一直按 F8 键单步执行,等待程序退出 ntdll.dll、进入我们程序的主文件。然后向下翻滚滚轴,查找 main()主函数。在 main()里面点中第一条指令PUSH EBP并在此处按下 F2 键设置断点。接着按 F9 键,运行断点之前的指令。
1.png
在这个界面里,我们在寄存器的区域内用右键单击 EAX 寄存器,然后选择“Follow in stack”。如此一来,OllyDbg 就会在栈窗口里显示栈地址和栈内数据,以便我们清楚地观察栈里的局部变量。图中红箭头所示的就是栈里的数据。其中,在地址 0x6E494714 处的数据就是脏数据。在下一时刻,PUSH 指令会把数据存储到栈里的下一个地址。接下来,在程序执行完 scanf()函数之前,我们一直按 F8 键。在执行 scanf()函数的时候,我们要在运行程序的终端窗口里输入数据,例如 123,如下图:
2.png

scanf()函数的执行之后的情形如下图所示。EAX 寄存器里存有函数的返回值 1。这表示它成功地读取了 1 个值。我们可以在栈里找到局部变量的地址,其数值为 0x7B(即数字 123)。
3.png

这个值将通过栈传递给 ECX 寄存器,然后再次通过栈传递给 printf()函数,如下图
4.png

x86全局变量

MSVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
_DATA	SEGMENT
COMM _x:DWORD
$SG2456 DB 'Enter X: ', 0aH, 00H
$SG2457 DB '%d', 00H
$SG2458 DB 'You entered %d... ', 0aH, 00H
_DATA ENDS
PUBLIC _main
EXTRN __scanf:PROC
EXTRN __printf:PROC
; Function compile flags: /Odtp
_TEXT SEGMENT
_main PROC
push ebp
mov ebp,esp
push OFFSET $SG2456
call _printf
add esp,4
push OFFSET _x
push OFFSET $SG2457
call _scanf
add esp,8
mov eax,DWORD PTR_x
push eax
push OFFSET $SG2458
call _printf
add esp,8
xor eax,eax
pop ebp
ret 0
_main ENDP
_TEXT ENDS

x 变量的存储空间是数据段(_data 域),反而没有使用数据栈。因此整个程序的所有指令都可以直接访问全局变量 x。在可执行文件中,未经初始化的变量不会占用任何存储空间

OllyDbg调试

我们可以在 OllyDbg 观察程序的数据段里的变量:
6.png
全局变量 x 出现在数据段里。在调试器执行完 PUSH 指令之后,变量 x 的指针推即被推送入栈,我们就可在栈里右键单击 x 的地址并选择“Follow in dump”,并在左侧的内存窗口观察它。在控制台输入 123 之后,栈里的数据将会变成0x7B。若考虑到数权,此处应该是 00 00 00 7B。可见,这是 x86 系统低位优先的“小端字节序/LITTLE-ENDIAN”的典型特征。小端字节序属于“字节(顺)序/endianness”的一种,它的第一个字节是数权最低的字节,数权最高的字节会排列在最后。
此后,EAX 寄存器将存储这个地址里的 32 位值,并将之传递给 printf()函数。本例中,变量 x 的内存地址是011F7138.
在 OllyDbg 里,按下 Alt+M 组合键可查看这个进程的内存映射(process memory map),如下图所示,这个地址位于程序 PE 段的.data 域:
7.png

x86scanf()函数的状态监测

MSVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	lea eax,DOWORD PTR_x$[ebp]
push eax
push OFFSET $SG3833 ;'%d',00H
call _scanf
add esp, 8
cmp eax, 1
jne SHORT $LN2@main
mov ecx, DWORD PTR _x$[ebp]
push ecx
push OFFSET $SG3834 ; 'You entered %d... ', 0aH, 00H
call _printf
add esp, 8
jmp SHORT $LN1@main
$LN2@main:
push OFFSET $SG3836 ; 'What you entered? Huh? ', 0aH, 00H
call _printf
add esp, 4
$LN1@main:
eax, eax

不仅是 CMP
指令所有的“数学/算术计算”指令都会设置标志位。如果将 1 与 1 进行比较,1−1=0,ZF 标志位(“零”标识位,最终运算结果是 0)将被计算指令设定为 1。将两个不同的数值进行 CMP 比较时,ZF 标志位的值绝不会是 1。JNE 指令会依据 ZF 标志位的状态判断是否需要进行跳转,实际上此两者(Jump if Not Zero)的同义指令。JNE 和 JNZ 的 opcode 都相同。所以,即使使用减法运算操作指令 SUB 替换 CMP指令,Jcc 指令也可以进行正常的跳转。不过在使用 SUB 指令时,我们还需要分配一个寄存器保存运算结果,而 CMP 则不需要使用寄存器保存运算结果。

x64

在编译面向 x64 平台的可执行程序时,由于这个程序的参数较少,编译器会直接使用寄存器传递参数。除此之外,编译过程和 x86 的编译过程没有太大的区别。

x64局部变量

msvc 2010

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
_DATA SEGMENT
$SG3831 DB 'Enter X:',0aH,00H
$SG3832 DB '%d',00H
$SG3833 DB 'You entered %d...',0aH,00H
_DATA ENDS

_TEXT SEGMENT
x$=32
mian PROC
$LN3:
sub rsp,56
lea rcx, OFFSET FLAT:$SG1289 ; 'Enter X: '
call printf
lea rdx, QWORD PTR x$[rsp]
lea rcx, OFFSET FLAT:$SG1291 ; '%d'
call scanf
lea edx, DWORD PTR x$[rsp]
lea rcx, OFFSET FLAT:$SG1292 ; 'You entered %d... '
call printf
;return 0
xor eax, eax
add rsp, 56
ret 0
main ENDP
_TEXT ENDS

使用radare2调试

使用gcc编译源代码,之后使用radare2进行调试,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
root@kali:~# r2 a.out
-- Press 'C' in visual mode to toggle colors
[0x000005f0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x000005f0]> s main
[0x000006fa]> pdf
;-- main:
/ (fcn) sym.main 73
| sym.main ();
| ; var int local_4h @ rbp-0x4
| ; DATA XREF from 0x0000060d (entry0)
| 0x000006fa 55 push rbp
| 0x000006fb 4889e5 mov rbp, rsp
| 0x000006fe 4883ec10 sub rsp, 0x10
| 0x00000702 488d3dcb0000. lea rdi, str.Enter_X: ; 0x7d4 ; "Enter X:"
| 0x00000709 e8a2feffff call sym.imp.puts ; int puts(const char *s)
| 0x0000070e 488d45fc lea rax, [local_4h]
| 0x00000712 4889c6 mov rsi, rax
| 0x00000715 488d3dc10000. lea rdi, 0x000007dd ; "%d"
| 0x0000071c b800000000 mov eax, 0
| 0x00000721 e8aafeffff call sym.imp.__isoc99_scanf
| 0x00000726 8b45fc mov eax, dword [local_4h]
| 0x00000729 89c6 mov esi, eax
| 0x0000072b 488d3dae0000. lea rdi, str.You_entered__d..._n ; 0x7e0 ; "You entered %d...\n"
| 0x00000732 b800000000 mov eax, 0
| 0x00000737 e884feffff call sym.imp.printf ; int printf(const char *format)
| 0x0000073c b800000000 mov eax, 0
| 0x00000741 c9 leave
\ 0x00000742 c3 ret
[0x000006fa]>

x64全局变量

GCC编译,使用radare2调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
r2 b.out
-- Use 'rabin2 -ris' to get the import/export symbols of any binary.
[0x000005f0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x000005f0]> s main
[0x000006fa]> pdf
;-- main:
/ (fcn) sym.main 72
| sym.main ();
| ; DATA XREF from 0x0000060d (entry0)
| 0x000006fa 55 push rbp
| 0x000006fb 4889e5 mov rbp, rsp
| 0x000006fe 488d3dcf0000. lea rdi, str.Enter_X: ; 0x7d4 ; "Enter X:"
| 0x00000705 e8a6feffff call sym.imp.puts ; int puts(const char *s)
| 0x0000070a 488d35330920. lea rsi, obj.x ; 0x201044
| 0x00000711 488d3dc50000. lea rdi, 0x000007dd ; "%d"
| 0x00000718 b800000000 mov eax, 0
| 0x0000071d e8aefeffff call sym.imp.__isoc99_scanf
| 0x00000722 8b051c092000 mov eax, dword [obj.x] ; [0x201044:4]=0
| 0x00000728 89c6 mov esi, eax
| 0x0000072a 488d3daf0000. lea rdi, str.You_entered__d..._n ; 0x7e0 ; "You entered %d...\n"
| 0x00000731 b800000000 mov eax, 0
| 0x00000736 e885feffff call sym.imp.printf ; int printf(const char *format)
| 0x0000073b b800000000 mov eax, 0
| 0x00000740 5d pop rbp
\ 0x00000741 c3 ret
[0x000006fa]>

x64: scanf()函数的状态监测

:GCC编译,使用radare2调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
root@kali:~# r2 c.out
-- You can debug a program from the graph view ('ag') using standard radare2 commands
[0x000005f0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x000005f0]> s main
[0x000006fa]> pdf
;-- main:
/ (fcn) main 92
| main ();
| ; var int local_4h @ rbp-0x4
| ; DATA XREF from 0x0000060d (entry0)
| 0x000006fa 55 push rbp
| 0x000006fb 4889e5 mov rbp, rsp
| 0x000006fe 4883ec10 sub rsp, 0x10
| 0x00000702 488d3ddb0000. lea rdi, str.Enter_X: ; 0x7e4 ; "Enter X:"
| 0x00000709 e8a2feffff call sym.imp.puts ; int puts(const char *s)
| 0x0000070e 488d45fc lea rax, [local_4h]
| 0x00000712 4889c6 mov rsi, rax
| 0x00000715 488d3dd10000. lea rdi, 0x000007ed ; "%d"
| 0x0000071c b800000000 mov eax, 0
| 0x00000721 e8aafeffff call sym.imp.__isoc99_scanf
| 0x00000726 83f801 cmp eax, 1
| ,=< 0x00000729 7518 jne 0x743
| | 0x0000072b 8b45fc mov eax, dword [local_4h]
| | 0x0000072e 89c6 mov esi, eax
| | 0x00000730 488d3db90000. lea rdi, str.You_entered__d..._n ; 0x7f0 ; "You entered %d...\n"
| | 0x00000737 b800000000 mov eax, 0
| | 0x0000073c e87ffeffff call sym.imp.printf ; int printf(const char *format)
| ,==< 0x00000741 eb0c jmp 0x74f
| || ; JMP XREF from 0x00000729 (main)
| |`-> 0x00000743 488d3db90000. lea rdi, str.What_you_entered__Huh_ ; 0x803 ; "What you entered? Huh?"
| | 0x0000074a e861feffff call sym.imp.puts ; int puts(const char *s)
| | ; JMP XREF from 0x00000741 (main)
| `--> 0x0000074f b800000000 mov eax, 0
| 0x00000754 c9 leave
\ 0x00000755 c3 ret
[0x000006fa]>

其他系统

关于ARM、MIPS系统对于printf()函数的处理,请各位同学参考《RE4B》一书

#作业