目录
一段有趣的c语言代码

今天看到一段有趣的c代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
void function(int a,int b,int c){
int *ret;
ret=&a-1;
(*ret)+=8;
}
void main(){
int x;
x=0;
function(1,2,3);
x=1;
printf("x is %d\n",x);
}

群里的人讨论最终输出x的值是0还是1呐?

经过我的测试,编译成32位的程序x是0,而64位的是1

1
2
3
4
5
6
$ gcc text.c -o text_64
$ gcc text.c -o text_32 -m32
$ ./text_32
x is 0
$ ./text_64
x is 1

这是为什么呐?我决定用gdb调一下,先调一下32位的程序

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
0x08048445 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────────────────────────────────────────────
EAX 0xf7f90dbc (environ) —▸ 0xffffcd1c —▸ 0xffffcf56 ◂— 'XDG_VTNR=7'
EBX 0x0
ECX 0xffffcc80 ◂— 0x1
EDX 0xffffcca4 ◂— 0x0
EDI 0xf7f8f000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
ESI 0xf7f8f000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
EBP 0xffffcc68 ◂— 0x0
ESP 0xffffcc44 ◂— 0x1
EIP 0x8048445 (main+30) —▸ 0xffffc1e8 ◂— 0x0
─────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────
0x8048435 <main+14> sub esp, 0x14
0x8048438 <main+17> mov dword ptr [ebp - 0xc], 0
0x804843f <main+24> push 3
0x8048441 <main+26> push 2
0x8048443 <main+28> push 1
► 0x8048445 <main+30> call function <0x804840b>
arg0: 0x1

0x804844a <main+35> add esp, 0xc
0x804844d <main+38> mov dword ptr [ebp - 0xc], 1
0x8048454 <main+45> sub esp, 8
0x8048457 <main+48> push dword ptr [ebp - 0xc]
0x804845a <main+51> push 0x80484f0
─────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffcc44 ◂— 0x1
01:0004│ 0xffffcc48 ◂— 0x2
02:0008│ 0xffffcc4c ◂— 0x3
03:000c│ 0xffffcc50 ◂— 0x1
04:0010│ 0xffffcc54 —▸ 0xffffcd14 —▸ 0xffffcf13 ◂— 0x6d6f682f ('/hom')
05:0014│ 0xffffcc58 —▸ 0xffffcd1c —▸ 0xffffcf56 ◂— 'XDG_VTNR=7'
06:0018│ 0xffffcc5c ◂— 0x0
07:001c│ 0xffffcc60 —▸ 0xf7f8f3dc (__exit_funcs) —▸ 0xf7f901e0 (initial) ◂— 0x0
───────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 8048445 main+30
f 1 f7df5637 __libc_start_main+247
pwndbg> n
x is 0
[Inferior 1 (process 10680) exited with code 07]

纳尼,程序执行完function函数之后,就输出x的值了,并退出了。
看来我之前的思路完全错了,我还以为那个函数把1修改为0了,看来是劫持了程序的执行流,跳过了x=1;这个语句

接下里就分析一下那个函数是如何劫持程序执行流的,进入function函数进行调试

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
0x08048422 in function ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────────────────────────────────────────────
EAX 0xffffcc40 —▸ 0x804844a (main+35) ◂— add esp, 0xc
EBX 0x0
ECX 0xffffcc80 ◂— 0x1
EDX 0x8048452 (main+43) ◂— add byte ptr [eax], al
EDI 0xf7f8f000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
ESI 0xf7f8f000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
EBP 0xffffcc3c —▸ 0xffffcc68 ◂— 0x0
ESP 0xffffcc2c —▸ 0xf7fd3388 —▸ 0xf7ddd000 ◂— jg 0xf7ddd047
EIP 0x8048422 (function+23) ◂— mov dword ptr [eax], edx
─────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────
0x8048414 <function+9> mov dword ptr [ebp - 4], eax
0x8048417 <function+12> mov eax, dword ptr [ebp - 4]
0x804841a <function+15> mov eax, dword ptr [eax]
0x804841c <function+17> lea edx, [eax + 8]
0x804841f <function+20> mov eax, dword ptr [ebp - 4]
► 0x8048422 <function+23> mov dword ptr [eax], edx <0x8048452> ##这里是关键
0x8048424 <function+25> nop
0x8048425 <function+26> leave
0x8048426 <function+27> ret

0x8048452 <main+43> add byte ptr [eax], al
0x8048454 <main+45> sub esp, 8
─────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffcc2c —▸ 0xf7fd3388 —▸ 0xf7ddd000 ◂— jg 0xf7ddd047
01:0004│ 0xffffcc30 ◂— 0x8000
02:0008│ 0xffffcc34 —▸ 0xf7f8f000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
03:000c│ 0xffffcc38 —▸ 0xffffcc40 —▸ 0x804844a (main+35) ◂— add esp, 0xc
04:0010│ ebp 0xffffcc3c —▸ 0xffffcc68 ◂— 0x0
05:0014│ eax 0xffffcc40 —▸ 0x804844a (main+35) ◂— add esp, 0xc
06:0018│ 0xffffcc44 ◂— 0x1
07:001c│ 0xffffcc48 ◂— 0x2
───────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 8048422 function+23
f 1 804844a main+35
f 2 f7df5637 __libc_start_main+247

可以看到下面这句汇编把eax寄存器指向的地址0x804844a (main+35)修改为了0x8048452
0x8048422 <function+23> mov dword ptr [eax], edx <0x8048452>

我们配合着mian函数的汇编来看一下,0x8048844a这个地址是调用完function函数的下一个地址,也就相当于function的函数返回地址;
但是这里被修改成了0x8048452,那这样就造成了执行完function函数之后,就跳转到0x8048452这个地址继续执行;
从而跳过了0x0804844d <+38>: mov DWORD PTR [ebp-0xc],0x1 这个赋值语句

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
disassemble main
Dump of assembler code for function main:
0x08048427 <+0>: lea ecx,[esp+0x4]
0x0804842b <+4>: and esp,0xfffffff0
0x0804842e <+7>: push DWORD PTR [ecx-0x4]
0x08048431 <+10>: push ebp
0x08048432 <+11>: mov ebp,esp
0x08048434 <+13>: push ecx
0x08048435 <+14>: sub esp,0x14
0x08048438 <+17>: mov DWORD PTR [ebp-0xc],0x0
0x0804843f <+24>: push 0x3
0x08048441 <+26>: push 0x2
0x08048443 <+28>: push 0x1
0x08048445 <+30>: call 0x804840b <function>
0x0804844a <+35>: add esp,0xc
0x0804844d <+38>: mov DWORD PTR [ebp-0xc],0x1
0x08048454 <+45>: sub esp,0x8
0x08048457 <+48>: push DWORD PTR [ebp-0xc]
0x0804845a <+51>: push 0x80484f0
0x0804845f <+56>: call 0x80482e0 <printf@plt>
0x08048464 <+61>: add esp,0x10
0x08048467 <+64>: nop
0x08048468 <+65>: mov ecx,DWORD PTR [ebp-0x4]
0x0804846b <+68>: leave
0x0804846c <+69>: lea esp,[ecx-0x4]
0x0804846f <+72>: ret
End of assembler dump.

现在就很明显了,0x8048452-0x8048844a=8;

从c语言的角度来看ret=&a-1;这句话就是取栈中 函数第一个 参数 上面的函数返回地址的 地址指针
注意运算符优先级,先算术运算,后移位运算,最后位运算;ret=&(a-1)
然后(*ret)+=8,函数返回地址再加8,劫持了程序执行流。
这个程序加8不是很好,应该改为加10更为严谨;

而64位程序是通过寄存器来传参的,ret取到的不是函数返回地址,就没办法劫持程序执行流了

64位的程序就不在演示调试了

文章作者: nocbtm
文章链接: https://nocbtm.github.io/2019/10/10/一段有趣的c语言代码/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nocbtm's Blog
打赏
  • 微信
  • 支付宝