@@ -86,7 +86,7 @@ func (f *elfFile) symbols() ([]Sym, error) {
86
86
return syms , nil
87
87
}
88
88
89
- func (f * elfFile ) pcln_scan () (candidates [] PclntabCandidate , err error ) {
89
+ func (f * elfFile ) pcln_scan () (candidates <- chan PclntabCandidate , err error ) {
90
90
// 1) Locate pclntab via symbols (standard way)
91
91
foundpcln := false
92
92
var pclntab []byte
@@ -110,99 +110,27 @@ func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) {
110
110
[]byte ("\xFF \xFF \xFF \xFB \x00 \x00 " ),
111
111
}
112
112
113
+ var symtab []byte
114
+ var symtab_err error
115
+ if sect := f .elf .Section (".gosymtab" ); sect != nil {
116
+ symtab , symtab_err = sect .Data ()
117
+ }
118
+
113
119
// 2) if not found, byte scan for it
114
120
pclntab_sigs := append (pclntab_sigs_le , pclntab_sigs_be ... )
115
121
116
- // candidate array for method 4 of scanning
117
- var stompedmagic_candidates []StompMagicCandidate = make ([]StompMagicCandidate , 0 )
118
- for _ , sec := range f .elf .Sections {
119
- // first section is all zeros, skip
120
- if sec .Type == elf .SHT_NULL {
121
- continue
122
- }
123
-
124
- data := f .elf .DataAfterSection (sec )
125
- if ! foundpcln {
126
- // malware can split the pclntab across multiple sections, re-merge
127
- // https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172
128
- matches := findAllOccurrences (data , pclntab_sigs )
129
- for _ , pclntab_idx := range matches {
130
- if pclntab_idx != - 1 && pclntab_idx < int (sec .Size ) {
131
- pclntab = data [pclntab_idx :]
132
-
133
- var candidate PclntabCandidate
134
- candidate .Pclntab = pclntab
135
-
136
- candidate .SecStart = uint64 (sec .Addr )
137
- candidate .PclntabVA = candidate .SecStart + uint64 (pclntab_idx )
138
-
139
- candidates = append (candidates , candidate )
140
- // we must scan all signature for all sections. DO NOT BREAK
141
- }
142
- }
143
- } else {
144
- // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things)
145
- pclntab_idx := bytes .Index (data , pclntab )
146
- if pclntab_idx != - 1 && pclntab_idx < int (sec .Size ) {
147
- var candidate PclntabCandidate
148
- candidate .Pclntab = pclntab
149
- candidate .SecStart = uint64 (sec .Addr )
150
- candidate .PclntabVA = candidate .SecStart + uint64 (pclntab_idx )
151
-
152
- candidates = append (candidates , candidate )
153
- }
154
- }
122
+ ch_tab := make (chan PclntabCandidate )
155
123
156
- // TODO this scan needs to occur in both big and little endian mode
157
- // 4) Always try this other way! Sometimes the pclntab magic is stomped as well so our byte OR symbol location fail. Byte scan for the moduledata, use that to find the pclntab instead, fix up magic with all combinations.
158
- // See the obfuscator 'garble' for an example of randomizing the pclntab magic
159
- sigResults := findModuleInitPCHeader (data , sec .Addr )
160
- for _ , sigResult := range sigResults {
161
- // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab!
162
- // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o
163
- // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ...
164
- // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize"
165
- // 0x000000000069D0D0 F0 db 0F0h
166
- // 0x000000000069D0D1 BB db 0BBh
167
-
168
- // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later
169
- // example: reads out ptr unk_5DF6E0
170
- pclntabVARaw64 , err := f .read_memory (sigResult .moduleDataVA , 8 ) // assume 64bit
171
- if err == nil {
172
- stompedMagicCandidateLE := StompMagicCandidate {
173
- binary .LittleEndian .Uint64 (pclntabVARaw64 ),
174
- sigResult .moduleDataVA ,
175
- true ,
176
- }
177
- stompedMagicCandidateBE := StompMagicCandidate {
178
- binary .BigEndian .Uint64 (pclntabVARaw64 ),
179
- sigResult .moduleDataVA ,
180
- false ,
181
- }
182
- stompedmagic_candidates = append (stompedmagic_candidates , stompedMagicCandidateLE , stompedMagicCandidateBE )
183
- }
184
-
185
- pclntabVARaw32 , err := f .read_memory (sigResult .moduleDataVA , 4 ) // assume 32bit
186
- if err == nil {
187
- stompedMagicCandidateLE := StompMagicCandidate {
188
- uint64 (binary .LittleEndian .Uint32 (pclntabVARaw32 )),
189
- sigResult .moduleDataVA ,
190
- true ,
191
- }
192
- stompedMagicCandidateBE := StompMagicCandidate {
193
- uint64 (binary .BigEndian .Uint32 (pclntabVARaw32 )),
194
- sigResult .moduleDataVA ,
195
- false ,
196
- }
197
- stompedmagic_candidates = append (stompedmagic_candidates , stompedMagicCandidateLE , stompedMagicCandidateBE )
198
- }
124
+ send_tab := func (candidate * PclntabCandidate ) {
125
+ if symtab_err != nil {
126
+ candidate .Symtab = symtab
127
+ ch_tab <- * candidate
199
128
}
129
+ ch_tab <- * candidate
200
130
}
201
131
202
- // even if we found the pclntab without signature scanning it may have a stomped magic. That would break parsing later! So, let's submit new candidates
203
- // with all the possible magics to get at least one that hopefully parses correctly.
204
- patched_magic_candidates := make ([]PclntabCandidate , 0 )
205
- for _ , candidate := range candidates {
132
+ // for any candidate, patch out the magic, and send all possible magics to parse too
133
+ send_patched_magic_candidates := func (candidate * PclntabCandidate ) {
206
134
has_some_valid_magic := false
207
135
for _ , magic := range append (pclntab_sigs_le , pclntab_sigs_be ... ) {
208
136
if bytes .Equal (candidate .Pclntab , magic ) {
@@ -219,82 +147,154 @@ func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) {
219
147
220
148
new_candidate := candidate
221
149
new_candidate .Pclntab = pclntab_copy
222
- patched_magic_candidates = append (patched_magic_candidates , new_candidate )
223
- candidate .Pclntab = pclntab_copy
150
+ send_tab (new_candidate )
224
151
}
225
152
}
226
153
}
227
154
228
- if len (patched_magic_candidates ) > 0 {
229
- candidates = patched_magic_candidates
155
+ send_stomped_magic_candidate := func (stompedMagicCandidate * StompMagicCandidate ) {
156
+ for _ , sec := range f .elf .Sections {
157
+ data := f .elf .DataAfterSection (sec )
158
+ pclntab_va_candidate := stompedMagicCandidate .PclntabVa
159
+
160
+ // use data length as some binaries have invalid section length
161
+ if pclntab_va_candidate >= sec .Addr && pclntab_va_candidate < (sec .Addr + sec .Size ) && pclntab_va_candidate < (sec .Addr + uint64 (len (data ))) {
162
+ sec_offset := pclntab_va_candidate - sec .Addr
163
+ pclntab = data [sec_offset :]
164
+
165
+ if stompedMagicCandidate .LittleEndian {
166
+ for _ , magicLE := range pclntab_sigs_le {
167
+ pclntab_copy := make ([]byte , len (pclntab ))
168
+ copy (pclntab_copy , pclntab )
169
+ copy (pclntab_copy , magicLE )
170
+
171
+ var candidate PclntabCandidate
172
+ candidate .StompMagicCandidateMeta = stompedMagicCandidate
173
+ candidate .Pclntab = pclntab_copy
174
+ candidate .SecStart = uint64 (sec .Addr )
175
+ candidate .PclntabVA = pclntab_va_candidate
176
+
177
+ send_tab (& candidate )
178
+ }
179
+ } else {
180
+ for _ , magicBE := range pclntab_sigs_be {
181
+ pclntab_copy := make ([]byte , len (pclntab ))
182
+ copy (pclntab_copy , pclntab )
183
+ copy (pclntab_copy , magicBE )
184
+
185
+ var candidate PclntabCandidate
186
+ candidate .StompMagicCandidateMeta = stompedMagicCandidate
187
+ candidate .Pclntab = pclntab_copy
188
+ candidate .SecStart = uint64 (sec .Addr )
189
+ candidate .PclntabVA = pclntab_va_candidate
190
+
191
+ send_tab (& candidate )
192
+ }
193
+ }
194
+ }
195
+ }
230
196
}
231
197
232
- if len (stompedmagic_candidates ) != 0 {
198
+ go func () {
199
+ defer close (ch_tab )
200
+
233
201
for _ , sec := range f .elf .Sections {
202
+ // first section is all zeros, skip
203
+ if sec .Type == elf .SHT_NULL {
204
+ continue
205
+ }
206
+
234
207
data := f .elf .DataAfterSection (sec )
235
- for _ , stompedMagicCandidate := range stompedmagic_candidates {
236
- pclntab_va_candidate := stompedMagicCandidate .PclntabVa
237
-
238
- // use data length as some binaries have invalid section length
239
- if pclntab_va_candidate >= sec .Addr && pclntab_va_candidate < (sec .Addr + sec .Size ) && pclntab_va_candidate < (sec .Addr + uint64 (len (data ))) {
240
- sec_offset := pclntab_va_candidate - sec .Addr
241
- pclntab = data [sec_offset :]
242
-
243
- if stompedMagicCandidate .LittleEndian {
244
- for _ , magicLE := range pclntab_sigs_le {
245
- pclntab_copy := make ([]byte , len (pclntab ))
246
- copy (pclntab_copy , pclntab )
247
- copy (pclntab_copy , magicLE )
248
-
249
- var candidate PclntabCandidate
250
- candidate .StompMagicCandidateMeta = & stompedMagicCandidate
251
- candidate .Pclntab = pclntab_copy
252
- candidate .SecStart = uint64 (sec .Addr )
253
- candidate .PclntabVA = pclntab_va_candidate
254
-
255
- candidates = append (candidates , candidate )
256
- }
257
- } else {
258
- for _ , magicBE := range pclntab_sigs_be {
259
- pclntab_copy := make ([]byte , len (pclntab ))
260
- copy (pclntab_copy , pclntab )
261
- copy (pclntab_copy , magicBE )
262
-
263
- var candidate PclntabCandidate
264
- candidate .StompMagicCandidateMeta = & stompedMagicCandidate
265
- candidate .Pclntab = pclntab_copy
266
- candidate .SecStart = uint64 (sec .Addr )
267
- candidate .PclntabVA = pclntab_va_candidate
268
-
269
- candidates = append (candidates , candidate )
270
- }
208
+ if ! foundpcln {
209
+ // malware can split the pclntab across multiple sections, re-merge
210
+ // https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172
211
+ matches := findAllOccurrences (data , pclntab_sigs )
212
+ for _ , pclntab_idx := range matches {
213
+ if pclntab_idx != - 1 && pclntab_idx < int (sec .Size ) {
214
+ pclntab = data [pclntab_idx :]
215
+
216
+ var candidate PclntabCandidate
217
+ candidate .Pclntab = pclntab
218
+
219
+ candidate .SecStart = uint64 (sec .Addr )
220
+ candidate .PclntabVA = candidate .SecStart + uint64 (pclntab_idx )
221
+ send_patched_magic_candidates (& candidate )
222
+
223
+ send_tab (& candidate )
224
+ // we must scan all signature for all sections. DO NOT BREAK
271
225
}
272
226
}
227
+ } else {
228
+ // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things)
229
+ pclntab_idx := bytes .Index (data , pclntab )
230
+ if pclntab_idx != - 1 && pclntab_idx < int (sec .Size ) {
231
+ var candidate PclntabCandidate
232
+ candidate .Pclntab = pclntab
233
+ candidate .SecStart = uint64 (sec .Addr )
234
+ candidate .PclntabVA = candidate .SecStart + uint64 (pclntab_idx )
235
+
236
+ send_patched_magic_candidates (& candidate )
237
+ send_tab (& candidate )
238
+ }
239
+ }
240
+
241
+ // 4) Always try this other way! Sometimes the pclntab magic is stomped as well so our byte OR symbol location fail. Byte scan for the moduledata, use that to find the pclntab instead, fix up magic with all combinations.
242
+ // See the obfuscator 'garble' for an example of randomizing the pclntab magic
243
+ sigResults := findModuleInitPCHeader (data , sec .Addr )
244
+ for _ , sigResult := range sigResults {
245
+ // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab!
246
+ // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o
247
+ // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ...
248
+ // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize"
249
+ // 0x000000000069D0D0 F0 db 0F0h
250
+ // 0x000000000069D0D1 BB db 0BBh
251
+
252
+ // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later
253
+ // example: reads out ptr unk_5DF6E0
254
+ pclntabVARaw64 , err := f .read_memory (sigResult .moduleDataVA , 8 ) // assume 64bit
255
+ if err == nil {
256
+ stompedMagicCandidateLE := StompMagicCandidate {
257
+ binary .LittleEndian .Uint64 (pclntabVARaw64 ),
258
+ sigResult .moduleDataVA ,
259
+ true ,
260
+ }
261
+ stompedMagicCandidateBE := StompMagicCandidate {
262
+ binary .BigEndian .Uint64 (pclntabVARaw64 ),
263
+ sigResult .moduleDataVA ,
264
+ false ,
265
+ }
266
+ send_stomped_magic_candidate (& stompedMagicCandidateBE )
267
+ send_stomped_magic_candidate (& stompedMagicCandidateLE )
268
+ }
269
+
270
+ pclntabVARaw32 , err := f .read_memory (sigResult .moduleDataVA , 4 ) // assume 32bit
271
+ if err == nil {
272
+ stompedMagicCandidateLE := StompMagicCandidate {
273
+ uint64 (binary .LittleEndian .Uint32 (pclntabVARaw32 )),
274
+ sigResult .moduleDataVA ,
275
+ true ,
276
+ }
277
+ stompedMagicCandidateBE := StompMagicCandidate {
278
+ uint64 (binary .BigEndian .Uint32 (pclntabVARaw32 )),
279
+ sigResult .moduleDataVA ,
280
+ false ,
281
+ }
282
+ send_stomped_magic_candidate (& stompedMagicCandidateBE )
283
+ send_stomped_magic_candidate (& stompedMagicCandidateLE )
284
+ }
273
285
}
274
286
}
275
- }
287
+ }()
276
288
277
- return candidates , nil
289
+ return ch_tab , nil
278
290
}
279
291
280
- func (f * elfFile ) pcln () (candidates [] PclntabCandidate , err error ) {
292
+ func (f * elfFile ) pcln () (candidates <- chan PclntabCandidate , err error ) {
281
293
candidates , err = f .pcln_scan ()
282
294
if err != nil {
283
295
return nil , err
284
296
}
285
297
286
- // 4) symtab is completely optional, but try to find it
287
- var symtab []byte
288
- if sect := f .elf .Section (".gosymtab" ); sect != nil {
289
- symtab , err = sect .Data ()
290
- }
291
-
292
- if err == nil {
293
- for _ , c := range candidates {
294
- c .Symtab = symtab
295
- }
296
- }
297
-
298
298
return candidates , nil
299
299
}
300
300
0 commit comments