近日,为了能在AWD中对二进制文件进行patch,特意研究了一下elf文件结构,并找寻patch新代码的方法

思路

新增一个Segment到文件最末尾

需要做的有以下几点:

  1. 分析原文件,得到Program Header
  2. 在文件最末尾添加新的Program Header,原Program Header废置,并再添加一个LOAD Segment
  3. LOAD Segmentp_offset字段为此时文件最末尾,将其它字段设置妥当
  4. 修改ELF Header中的e_phoffsete_phnum,使它适应新的Program Header
  5. 在文件最末尾也就是新添加的LOAD Segmentp_offset所指添加code

这样就基本可以做到添加一个LOAD Segment来加载新patch的代码到内存了

问题

尽管readelfobjdump都正常加载并输出了,但是如果想要执行文件却会报错。原因是加载elf文件过程中,dl_main或者其之前的某个函数想要载入一个并未被映射到虚拟内存空间的内存,就导致了报错

具体原因我并未查明,我只知道这个地址是.text段的地址

代码

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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
from capstone import *
from capstone.x86 import X86_INS_PUSH
from ctypes import *
import struct

class Elf32_Ehdr(LittleEndianStructure):
_fields_ = [
("e_ident", c_ubyte * 16),
("e_type", c_ushort),
("e_machine", c_ushort),
("e_version", c_uint),
("e_entry", c_uint),
("e_phoff", c_uint), # program header offset
("e_shoff", c_uint), # section header offset
("e_flags", c_uint),
("e_ehsize", c_ushort), # elf header's size
("e_phentsize", c_ushort),
("e_phnum", c_ushort),
("e_shentsize", c_ushort), # a section header's size
("e_shnum", c_ushort), # section header's num
("e_shstrndx", c_ushort)
]

class Elf64_Ehdr(LittleEndianStructure):
_fields_ = [
("e_ident", c_ubyte * 16),
("e_type", c_ushort),
("e_machine", c_ushort),
("e_version", c_uint),
("e_entry", c_ulonglong),
("e_phoff", c_ulonglong),
("e_shoff", c_ulonglong),
("e_flags", c_uint),
("e_ehsize", c_ushort),
("e_phentsize", c_ushort),
("e_phnum", c_ushort),
("e_shentsize", c_ushort),
("e_shnum", c_ushort),
("e_shstrndx", c_ushort)
]

class Elf32_Phdr(LittleEndianStructure):
_fields_ = [
("p_type", c_uint),
("p_flags", c_uint),
("p_offset", c_uint),
("p_vaddr", c_uint),
("p_paddr", c_uint),
("p_filesz", c_uint),
("p_memsz", c_uint),
("p_align", c_uint)
]

class Elf64_Phdr(LittleEndianStructure):
_fields_ = [
("p_type", c_uint),
("p_flags", c_uint),
("p_offset", c_ulonglong),
("p_vaddr", c_ulonglong),
("p_paddr", c_ulonglong),
("p_filesz", c_ulonglong),
("p_memsz", c_ulonglong),
("p_align", c_ulonglong)
]

class Elf32_Shdr(LittleEndianStructure):
_fields_ = [
("sh_name", c_uint),
("sh_type", c_uint),
("sh_flags", c_uint),
("sh_addr", c_uint),
("sh_offset", c_uint),
("sh_size", c_uint),
("sh_link", c_uint),
("sh_info", c_uint),
("sh_addralign", c_uint),
("sh_entsize", c_uint)
]

class Elf64_Shdr(LittleEndianStructure):
_fields_ = [
("sh_name", c_uint),
("sh_type", c_uint),
("sh_flags", c_ulonglong),
("sh_addr", c_ulonglong),
("sh_offset", c_ulonglong),
("sh_size", c_ulonglong),
("sh_link", c_uint),
("sh_info", c_uint),
("sh_addralign", c_ulonglong),
("sh_entsize", c_ulonglong)
]

class Elf32_rel(LittleEndianStructure):
_fields_ = [
("r_offset", c_uint),
("r_info", c_uint)
]

class Plt_struct():
def __init__(self, plt_address, got_address, name):
self.plt_address = plt_address
self.got_address = got_address
self.name = name

class ELF():
def __init__(self, binary):
self.bin = bytearray(binary)
self.modifiedBin = []
self.elfHeader = None
self.mode = None
self.arch = None
self.entry = None
self.modifiedPos = 0
self.programHeaders = []
self.sectionHeaders = []
self.sortSectionHeaders = []
self.plt = []
self.judgeClass()
self.judgeMachine()
self.judgeMagic()
self.setEentry()
self.getPlt()

# set x86 elf header
def set32HeaderElf(self):
elf32_ehdr = self.bin[0:]
self.elfHeader = Elf32_Ehdr.from_buffer_copy(elf32_ehdr)

# set x86_64 header
def set64HeaderElf(self):
elf64_ehdr = self.bin[0:]
self.elfHeader = Elf64_Ehdr.from_buffer_copy(elf64_ehdr)

# judge magic
def judgeMagic(self):
magic = bytearray(self.elfHeader.e_ident[0:4])
if magic != '\x7fELF':
raise Exception("only support ELF")

# jugge it is x86 or x86_64
def judgeClass(self):
ei_class = self.bin[4]
if ei_class == 0x1:
self.mode = CS_MODE_32
self.set32HeaderElf()
self.set32HeaderProgram()
self.set32HeaderSection()
elif ei_class == 0x2:
self.mode = CS_MODE_64
self.set64HeaderElf()
self.set64HeaderProgram()
self.set64HeaderSection()
else:
raise Exception("unknown class whitch is not x86 or x86-64")

# just only support x86 or x86_64
def judgeMachine(self):
e_machine = self.elfHeader.e_machine
if e_machine == 3 or e_machine == 62:
self.arch = CS_ARCH_X86
else:
raise Exception("just only support EM_386 or EM_X86_64")

# set the entry of the program
def setEentry(self):
self.entry = self.elfHeader.e_entry

# set x86 program header
def set32HeaderProgram(self):
e_phnum = self.elfHeader.e_phnum
e_phoff = self.elfHeader.e_phoff
elf32_phdr = self.bin[e_phoff:]
for i in range(e_phnum):
self.programHeaders.append(Elf32_Phdr.from_buffer_copy(elf32_phdr))
elf32_phdr = elf32_phdr[self.elfHeader.e_phentsize:]

# set x86_64 program header
def set64HeaderProgram(self):
e_phnum = self.elfHeader.e_phnum
e_phoff = self.elfHeader.e_phoff
elf64_phdr = self.bin[e_phoff:]
for i in range(e_phnum):
self.programHeaders.append(Elf64_Phdr.from_buffer_copy(elf64_phdr))
elf64_phdr = elf64_phdr[self.elfHeader.e_phentsize:]

# set x86 section header
def set32HeaderSection(self):
e_shnum = self.elfHeader.e_shnum
e_shoff = self.elfHeader.e_shoff
elf32_shdr = self.bin[e_shoff:]
for i in range(e_shnum):
self.sectionHeaders.append(Elf32_Shdr.from_buffer_copy(elf32_shdr))
elf32_shdr = elf32_shdr[self.elfHeader.e_shentsize:]

# set x86_64 section header
def set64HeaderSection(self):
e_shnum = self.elfHeader.e_shnum
e_shoff = self.elfHeader.e_shoff
elf64_shdr = self.bin[e_shoff:]
for i in range(e_shnum):
self.sectionHeaders.append(Elf64_Shdr.from_buffer_copy(elf64_shdr))
elf64_shdr = elf64_shdr[self.elfHeader.e_shentsize:]

# find section by it's name
def findSectionByName(self, section_name):
e_shstrndx = self.elfHeader.e_shstrndx
shstrtab = self.sectionHeaders[e_shstrndx]
offset = shstrtab.sh_offset
shstrtab_bytes = self.bin[offset:]
for section in self.sectionHeaders:
idx = section.sh_name
while shstrtab_bytes[idx] != 0:
idx += 1
shstr = shstrtab_bytes[section.sh_name:idx]
if shstr == section_name:
return section
raise Exception("no such section called " + section_name)

# get the content of the section
def getSectionContent(self, section):
offset = section.sh_offset
size = section.sh_size
content = self.bin[offset:offset+size]
return content

def getLOADSegment(self):
LOAD = []
ph = self.programHeaders
for segment in ph:
if segment.p_type == 1:
return segment

def pack(self, d):
res = ''
for field_name, field_type in d._fields_:
t = field_type
i = getattr(d, field_name)
if t == c_uint:
res += struct.pack('I', i)
elif t == c_ubyte:
res += struct.pack('B', i)
elif t == c_ulonglong:
res += struct.pack('Q', i)
elif t == c_ushort:
res += struct.pack('H', i)
else:
try:
length = field_type._length_
for item in i:
res += struct.pack('b', item)
except Exception as e:
print e
return bytearray(res)

def align(self, res):
return (len(res)+15) & ~15

def modifyProgramHeader(self, p, l, sz=False):
if sz == False:
p.p_offset += l
p.p_paddr += l
p.p_vaddr += l
else:
p.p_memsz += l
p.p_filesz += l

def modifySectionHeader(self, s, l, sz=False):
if sz == False:
s.sh_offset += l
if s.sh_addr != 0:
s.sh_addr += l
else:
s.sh_size += l

def add(self, res, c, f, r = None):
if r == None:
l = len(c)
res[f:f+l] = c
self.modifiedPos = f+l
else:
c += r
self.add(res, c, f)

def getPlt(self):
dynstrSection = self.findSectionByName('.dynstr')
dynstr_offset = dynstrSection.sh_offset
dynsymSection = self.findSectionByName('.dynsym')
dynsym_offset = dynsymSection.sh_offset
pltSection = self.findSectionByName('.plt')
plt_offset = pltSection.sh_offset
plt_entSize = pltSection.sh_entsize
rel_pltSection = self.findSectionByName('.rela.plt')
rel_plt_offset = rel_pltSection.sh_offset
rel_plt_size = rel_pltSection.sh_size
rel_plt_entSize = rel_pltSection.sh_entsize
for i in range(rel_plt_offset, rel_plt_offset+rel_plt_size, rel_plt_entSize):
itemSize = rel_plt_entSize // 3
data = self.bin[i:i+rel_plt_size][:itemSize*2]
got_address = struct.unpack("Q", data[:itemSize])[0]
r_info = struct.unpack("Q", data[itemSize:])[0] >> 32
sectionDynsym_offset = dynsym_offset + r_info * 0x18
st_name = struct.unpack("I", self.bin[sectionDynsym_offset:sectionDynsym_offset+4])[0]
idx = dynstr_offset + st_name
while self.bin[idx] != 0:
idx += 1
name = self.bin[dynstr_offset+st_name:idx]
plt_address_offset = plt_offset + ((i - rel_plt_offset) // rel_plt_entSize + 1) * plt_entSize
plt_address = (self.entry&~0xfffff)+plt_address_offset
md = Cs(self.arch, self.mode)
code = self.bin[plt_address_offset:plt_address_offset+plt_entSize]
for disasm in md.disasm(code, plt_address):
if disasm.id == X86_INS_PUSH:
opstr = disasm.op_str
if self.mode == CS_MODE_64:
if int(opstr, 16) != (i - rel_plt_offset) // rel_plt_entSize:
raise Exception("Error when find plt!")
elif self.mode == CS_MODE_32:
if int(opstr, 16) != i - rel_plt_offset:
raise Exception("Error when find plt!")
break
plt = Plt_struct(plt_address, got_address, name)
self.plt.append(plt)

def InsertOpcode(self, opcode):
# make the length align to 0x10
code_length = self.align(opcode)
# I will add a section at the end of the file, and then add program header to the end
# and then add a LOAD segment
modifiedSize = len(self.bin) + \
(len(self.programHeaders)+1)*self.elfHeader.e_phentsize + \
code_length + self.elfHeader.e_shentsize
self.modifiedBin = [] # pack function is to change ctypes struct into bytearray
if self.mode == CS_MODE_64:
tmpElfHeader = Elf64_Ehdr.from_buffer_copy(self.pack(self.elfHeader))
addedSegment = Elf64_Phdr.from_buffer_copy(self.pack(self.getLOADSegment()))
elif self.mode == CS_MODE_32:
tmpElfHeader = Elf32_Ehdr.from_buffer_copy(self.pack(self.elfHeader))
addedSegment = Elf32_Phdr.from_buffer_copy(self.pack(self.getLOADSegment()))

tmpElfHeader.e_phoff = len(self.bin)
# add function is to add the bytearray of string into the third arg's point
self.add(self.modifiedBin, self.pack(tmpElfHeader), 0)
elfHeaderLength = len(self.pack(tmpElfHeader))
self.add(self.modifiedBin, self.bin[elfHeaderLength:], self.modifiedPos)
code_offset = self.modifiedPos + tmpElfHeader.e_phnum * tmpElfHeader.e_phentsize
code_offset = code_offset + (0x1000 - code_offset % 0x1000)
code_address = 0x10000 + code_offset
# add the added section
# add program headers
for segment in self.programHeaders:
if segment.p_type != 4:
self.add(self.modifiedBin, self.pack(segment), self.modifiedPos)
addedSegment.p_offset = code_offset
addedSegment.p_vaddr = code_address
addedSegment.p_paddr = code_address
addedSegment.p_filesz = code_length
addedSegment.p_memsz = 0x1000
addedSegment.p_align = 8
addedSegment.p_flags = 7
# add the added segment
self.add(self.modifiedBin, self.pack(addedSegment), self.modifiedPos)
# add code
self.add(self.modifiedBin, '\x00'*(code_offset-self.modifiedPos), self.modifiedPos)
self.add(self.modifiedBin, opcode.ljust(code_length, '\x00'), self.modifiedPos)
with open('patch', 'wb') as f:
f.write(bytearray(self.modifiedBin))

with open('./a', 'rb') as f:
elf = ELF(f.read())
opcode = '\xe8\x0b\xf9\xff\xff\xe9\xbd\xfb\xff\xff'
elf.InsertOpcode(opcode)

未完待续

最近用leaf做出来了一个patch的脚本,可以一用