linux下实现在程序运行时的函数替换(热补丁)
Posted sky
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux下实现在程序运行时的函数替换(热补丁)相关的知识,希望对你有一定的参考价值。
转自:http://www.cnblogs.com/leo0000/p/5632642.html
声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享。
但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的。也正因为这些错误,加深了我的学习深度。
最近因为在学习一些调试的技术,但是很少有提到如何在函数运行时实现函数替换的。
为什么会想到这一点?因为在学习调试时,难免会看到一些内核方面的调试技术,内核中的调试有一个kprobe,很强大,可以实现运行时的函数替换。其原理就是hook,钩子,但是学习了这个kprobe之后会发现,kprobe内部有检测所要钩的函数是不是属于内核空间,必须是内核函数才能实现替换。而实际上,我的工作大部分还是在应用层的,所以想要实现应用程序的热补丁技术。
一些基础的知识这边的就不展开了,需要的基础有,elf文件格式,ptrace,waitpid,应用程序间通信时的信号,汇编。
- 1、elf文件加载过程
elf简单地说是由以下四部分组成的,elf文件头,program header和section header,内容。其中program header是运行时使用的,而section header并不会被加载进程序运行空间,但他们可以在编译时被指定该段的加载地址等信息,当然一般这个链接脚本.lds是由gcc默认的。
第一步,加载elf文件头,检验文件类型版本等,重要的是找到program header的地址和header的个数,如果连接器脚本是默认的,那么elf文件头会被加载在0x804800地址处。
第二步,加载program header,接着扫描program header,找到一个类型为PT_INTERP的program header,这个header里面放着的是有关解释器的地址,这时候将解释器程序的elf文件头加载进来。一般是这样:
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
第三步,扫描program header,如果类型为PT_LOAD,则将该段加载进来。
第四步,判断是否需要解释器程序,如果需要,把解释器程序加载进来,并把程序入口设置为解释器程序的地址。否则是应用程序本身的入口。反汇编为_start标号。
第五步,设置命令行传入的参数等应用程序需要的信息。
第六步,解释器程序开始运行,加载程序需要的库,填写重定向符号表中的地址信息。
- 2.elf文件动态链接过程
上一步,解释器程序根据program header已经将应用程序的段都加载进内存了,接下来再扫描program header,找到类型为PT_DYNAMIC,这里面包含了很多由section header描述的内容,包括重定向表,符号表,字符串表等等。解释器需要这个段描述的一些信息。
DT_NEEDED描述了所需要的动态库名称,DT_REL描述了重定位表地址,DT_JMPREL描述了重定位表地址(这个表是懒惰链接使用的),DT_PLTGOT全局偏移表地址。
此时解释器程序就可以根据所需要的动态库,将其加载进内存。每一个被加载进来的库的相关信息会被记录在link_map结构中,这个结构是一个链表,保存了所有的动态信息。
其中,全局偏移表got,got[0]保存了PT_DYNAMIC的起始地址,got[1]保存link_map的地址,而link_map中就可以找到PT_DYNAMIC的起始地址,和下一个或者上一个共享文件或者可执行文件的link_map地址。
DT_REL这个重定向表中的符号必须在此时就被解析完成。
而DT_JMPREL这个重定向表中的符号可以在运行时再解析。
所有的库和符号全部解析完成之后,解释器程序就会把控制权交给可执行文件的_start。程序开始执行。
- 3.替换函数和被替换函数
被替换程序源码。
1
2
3
4
5
6
7
8
9
|
#include <stdio.h> #include <time.h> int main() { while (1){ sleep(10); printf( "%d : original\\n" ,time(0)); } } |
替换新库代码。
1
2
3
4
5
6
7
|
#include <stdio.h> int newmyprint() { write(1, "hahahahahahaha" ,14); return 0; } |
够简单明了吧,如果替换成功,目标程序将会一直输出“哈哈哈哈哈哈”。
- 4.功能函数
ptrace相关代码:
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
/* 读进程寄存器 */ void ptrace_readreg( int pid, struct user_regs_struct *regs) { if (ptrace(PTRACE_GETREGS, pid, NULL, regs)) printf ( "*** ptrace_readreg error ***\\n" ); /*printf("ptrace_readreg\\n"); printf("%x\\n",regs->ebx); printf("%x\\n",regs->ecx); printf("%x\\n",regs->edx); printf("%x\\n",regs->esi); printf("%x\\n",regs->edi); printf("%x\\n",regs->ebp); printf("%x\\n",regs->eax); printf("%x\\n",regs->xds); printf("%x\\n",regs->xes); printf("%x\\n",regs->xfs); printf("%x\\n",regs->xgs); printf("%x\\n",regs->orig_eax); printf("%x\\n",regs->eip); printf("%x\\n",regs->xcs); printf("%x\\n",regs->eflags); printf("%x\\n",regs->esp); printf("%x\\n",regs->xss);*/ } /* 写进程寄存器 */ void ptrace_writereg( int pid, struct user_regs_struct *regs) { /*printf("ptrace_writereg\\n"); printf("%x\\n",regs->ebx); printf("%x\\n",regs->ecx); printf("%x\\n",regs->edx); printf("%x\\n",regs->esi); printf("%x\\n",regs->edi); printf("%x\\n",regs->ebp); printf("%x\\n",regs->eax); printf("%x\\n",regs->xds); printf("%x\\n",regs->xes); printf("%x\\n",regs->xfs); printf("%x\\n",regs->xgs); printf("%x\\n",regs->orig_eax); printf("%x\\n",regs->eip); printf("%x\\n",regs->xcs); printf("%x\\n",regs->eflags); printf("%x\\n",regs->esp); printf("%x\\n",regs->xss);*/ if (ptrace(PTRACE_SETREGS, pid, NULL, regs)) printf ( "*** ptrace_writereg error ***\\n" ); } /* 关联到进程 */ void ptrace_attach( int pid) { if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { perror ( "ptrace_attach" ); exit (-1); } waitpid(pid, NULL, /*WUNTRACED*/ 0); ptrace_readreg(pid, &oldregs); } /* 进程继续 */ void ptrace_cont( int pid) { int stat; if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) { perror ( "ptrace_cont" ); exit (-1); } /*while(!WIFSTOPPED(stat)) waitpid(pid, &stat, WNOHANG);*/ } /* 脱离进程 */ void ptrace_detach( int pid) { ptrace_writereg(pid, &oldregs); if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) { perror ( "ptrace_detach" ); exit (-1); } } /* 写指定进程地址 */ void ptrace_write( int pid, unsigned long addr, void *vptr, int len) { int count; long word; count = 0; while (count < len) { memcpy (&word, vptr + count, sizeof (word)); word = ptrace(PTRACE_POKETEXT, pid, addr + count, word); count += 4; if ( errno != 0) printf ( "ptrace_write failed\\t %ld\\n" , addr + count); } } /* 读指定进程 */ int ptrace_read( int pid, unsigned long addr, void *vptr, int len) { int i,count; long word; unsigned long *ptr = (unsigned long *)vptr; i = count = 0; //printf("ptrace_read addr = %x\\n",addr); while (count < len) { //printf("ptrace_read addr+count = %x\\n",addr + count); word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL); while (word < 0) { if ( errno == 0) break ; //printf("ptrace_read word = %x\\n",word); perror ( "ptrace_read failed" ); return 2; } count += 4; ptr[i++] = word; } return 0; } /* 在进程指定地址读一个字符串 */ char * ptrace_readstr( int pid, unsigned long addr) { char *str = ( char *) malloc (64); int i,count; long word; char *pa; i = count = 0; pa = ( char *)&word; while (i <= 60) { word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL); count += 4; if (pa[0] == 0) { str[i] = 0; break ; } else str[i++] = pa[0]; if (pa[1] == 0) { str[i] = 0; break ; } else str[i++] = pa[1]; if (pa[2] ==0) { str[i] = 0; break ; } else str[i++] = pa[2]; if (pa[3] ==0) { str[i] = 0; break ; } else str[i++] = pa[3]; } return str; } /* 将指定数据压入进程堆栈并返回堆栈指针 */ void * ptrace_push( int pid, void *paddr, int size) { unsigned long esp; struct user_regs_struct regs; ptrace_readreg(pid, ®s); esp = regs.esp; esp -= size; esp = esp - esp % 4; regs.esp = esp; ptrace_writereg(pid, ®s); ptrace_write(pid, esp, paddr, size); return ( void *)esp; } /* 在进程内调用指定地址的函数 */ void ptrace_call( int pid, unsigned long addr) { void *pc; struct user_regs_struct regs; int stat; void *pra; pc = ( void *) 0x41414140; pra = ptrace_push(pid, &pc, sizeof (pc)); ptrace_readreg(pid, ®s); regs.eip = addr; ptrace_writereg(pid, ®s); ptrace_cont(pid); //while(WIFSIGNALED(stat)) // waitpid(pid, &stat, WNOHANG); } |
这里面的东西我就不展开了,对ptrace的学习,请自行man。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|