-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcallsite.go
More file actions
218 lines (188 loc) · 6.76 KB
/
Copy pathcallsite.go
File metadata and controls
218 lines (188 loc) · 6.76 KB
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
package resurgo
import (
"fmt"
"golang.org/x/arch/arm64/arm64asm"
"golang.org/x/arch/x86/x86asm"
)
const (
// Recognized call site instruction types.
CallSiteCall CallSiteType = "call"
CallSiteJump CallSiteType = "jump"
// Recognized addressing modes for call site instructions.
AddressingModePCRelative AddressingMode = "pc-relative"
AddressingModeAbsolute AddressingMode = "absolute"
AddressingModeRegisterIndirect AddressingMode = "register-indirect"
// DetectionCallTarget indicates the candidate was found only as a target
// of one or more CALL instructions.
DetectionCallTarget DetectionType = "call-target"
// DetectionJumpTarget indicates the candidate was found only as a target
// of one or more JMP instructions.
DetectionJumpTarget DetectionType = "jump-target"
// DetectionPrologueCallSite indicates the candidate was confirmed by both
// prologue matching and call-site analysis.
DetectionPrologueCallSite DetectionType = "prologue-callsite"
)
// CallSiteType represents the type of call site instruction.
type CallSiteType string
// AddressingMode represents how the target address is specified.
type AddressingMode string
// CallSiteEdge represents a detected call site (call or jump to a function).
type CallSiteEdge struct {
// SourceAddr is the virtual address of the call or jump instruction.
SourceAddr uint64 `json:"source_addr"`
// TargetAddr is the virtual address of the call or jump target.
TargetAddr uint64 `json:"target_addr"`
// Type indicates whether this edge was produced by a call or jump
// instruction.
Type CallSiteType `json:"type"`
// AddressMode describes how the target address is encoded in the
// instruction.
AddressMode AddressingMode `json:"address_mode"`
// Confidence is the reliability level of this edge.
Confidence Confidence `json:"confidence"`
}
// DetectCallSites analyzes raw machine code bytes and returns detected
// call sites (CALL and JMP instructions with their targets). baseAddr is the
// virtual address corresponding to the start of code. arch selects the
// architecture-specific detection logic. This function performs no I/O and
// works with any binary format.
func DetectCallSites(code []byte, baseAddr uint64, arch Arch) ([]CallSiteEdge, error) {
switch arch {
case ArchAMD64:
return detectCallSitesAMD64(code, baseAddr)
case ArchARM64:
return detectCallSitesARM64(code, baseAddr)
default:
return nil, fmt.Errorf("unsupported architecture: %s", arch)
}
}
func detectCallSitesAMD64(code []byte, baseAddr uint64) ([]CallSiteEdge, error) {
var result []CallSiteEdge
offset := 0
addr := baseAddr
for offset < len(code) {
// Skip ENDBR64 / ENDBR32: golang.org/x/arch/x86/x86asm does not
// recognise these CET instructions. They appear at function entries
// on binaries compiled with -fcf-protection and are transparent to
// call site detection.
if isENDBR(code, offset) {
offset += 4
addr += 4
continue
}
inst, err := x86asm.Decode(code[offset:], 64)
if err != nil {
offset++
addr++
continue
}
switch inst.Op {
case x86asm.CALL:
if edge := extractTargetAMD64(inst, addr, CallSiteCall, ConfidenceHigh); edge != nil {
result = append(result, *edge)
}
case x86asm.JMP:
// x86asm uses distinct Op values for conditional jumps (JNE, JE, JL, etc.),
// so Op == JMP is always unconditional.
if edge := extractTargetAMD64(inst, addr, CallSiteJump, ConfidenceMedium); edge != nil {
result = append(result, *edge)
}
}
offset += inst.Len
addr += uint64(inst.Len)
}
return result, nil
}
// extractTargetAMD64 extracts the call site target from an x86-64 CALL or JMP
// instruction. cfType and baseConfidence are applied to direct (Rel) and absolute
// (Mem without base/index) operands. Register-indirect and RIP-relative operands
// receive adjusted confidence levels.
func extractTargetAMD64(inst x86asm.Inst, sourceAddr uint64, cfType CallSiteType, baseConfidence Confidence) *CallSiteEdge {
edge := &CallSiteEdge{
SourceAddr: sourceAddr,
Type: cfType,
}
switch arg := inst.Args[0].(type) {
case x86asm.Rel:
// PC-relative: call/jmp rel32 or rel8
edge.TargetAddr = sourceAddr + uint64(inst.Len) + uint64(int64(arg))
edge.AddressMode = AddressingModePCRelative
edge.Confidence = baseConfidence
return edge
case x86asm.Mem:
if arg.Base == x86asm.RIP && arg.Index == 0 {
// RIP-relative: call/jmp [rip+disp32] - dominant indirect form in
// PIE binaries (PLT/GOT). The referenced memory address is
// computable: nextPC + disp.
edge.TargetAddr = sourceAddr + uint64(inst.Len) + uint64(arg.Disp)
edge.AddressMode = AddressingModePCRelative
edge.Confidence = ConfidenceMedium
return edge
}
if arg.Base == 0 && arg.Index == 0 {
// Absolute address: call/jmp [disp]
edge.TargetAddr = uint64(arg.Disp)
edge.AddressMode = AddressingModeAbsolute
edge.Confidence = baseConfidence
return edge
}
// Complex memory addressing (register-based) - cannot resolve statically
edge.AddressMode = AddressingModeRegisterIndirect
edge.Confidence = ConfidenceNone
return edge
case x86asm.Reg:
// Register-indirect: call/jmp rax - cannot resolve statically
edge.AddressMode = AddressingModeRegisterIndirect
edge.Confidence = ConfidenceNone
return edge
default:
return nil
}
}
func detectCallSitesARM64(code []byte, baseAddr uint64) ([]CallSiteEdge, error) {
var result []CallSiteEdge
const insnLen = 4
for offset := 0; offset+insnLen <= len(code); offset += insnLen {
inst, err := arm64asm.Decode(code[offset : offset+insnLen])
if err != nil {
continue
}
addr := baseAddr + uint64(offset)
switch inst.Op {
case arm64asm.BL:
if edge := extractTargetARM64(inst, addr, CallSiteCall, ConfidenceHigh); edge != nil {
result = append(result, *edge)
}
case arm64asm.B:
// B.cond (conditional branches) carry a Cond argument;
// they are usually intra-function branches (low confidence).
// Unconditional B may be a tail call (medium confidence).
conf := ConfidenceMedium
for _, arg := range inst.Args {
if _, ok := arg.(arm64asm.Cond); ok {
conf = ConfidenceLow
break
}
}
if edge := extractTargetARM64(inst, addr, CallSiteJump, conf); edge != nil {
result = append(result, *edge)
}
}
}
return result, nil
}
// extractTargetARM64 extracts the PC-relative branch target from an ARM64
// BL or B instruction. Returns nil if the first argument is not a PCRel offset.
func extractTargetARM64(inst arm64asm.Inst, sourceAddr uint64, cfType CallSiteType, confidence Confidence) *CallSiteEdge {
pcrel, ok := inst.Args[0].(arm64asm.PCRel)
if !ok {
return nil
}
return &CallSiteEdge{
SourceAddr: sourceAddr,
TargetAddr: sourceAddr + uint64(int64(pcrel)),
Type: cfType,
AddressMode: AddressingModePCRelative,
Confidence: confidence,
}
}