-
Notifications
You must be signed in to change notification settings - Fork 9
/
main.go
323 lines (307 loc) · 7.85 KB
/
main.go
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
package main
import (
"bytes"
"fmt"
"github.com/4ra1n/swing-rce-inspector/asm"
"github.com/4ra1n/swing-rce-inspector/classfile"
"github.com/4ra1n/swing-rce-inspector/common"
"github.com/4ra1n/swing-rce-inspector/files"
"github.com/4ra1n/swing-rce-inspector/global"
"github.com/4ra1n/swing-rce-inspector/inherit"
"github.com/4ra1n/swing-rce-inspector/log"
"os"
"os/signal"
"runtime"
"strings"
)
// analysis data
var (
discoveryClass []*asm.Class
discoveryClassFile []*savedClassFile
classMap map[string]*asm.Class
)
// conditions
var (
IsComponentSubclass bool
HasNoParamInit bool
HasStringSetMethod bool
)
// save analysis data
type (
savedClassFile struct {
ClassName string
ClassFile *classfile.ClassFile
ClassRef *asm.Class
}
resultData struct {
StringData string
InstListData []string
}
)
// startAnalysis is the core function of project
// cannot use goroutine because of constant pool
func startAnalysis(saved *savedClassFile, resultChan chan *resultData) {
IsComponentSubclass = false
HasNoParamInit = false
HasStringSetMethod = false
cf := saved.ClassFile
cl := saved.ClassRef
// is subclass of component
if inherit.IsSubclassOf(cl.Name(), global.ComponentName) {
IsComponentSubclass = true
}
// refresh constant pool
// IMPORTANT
global.CP = cf.ConstantPool()
for _, method := range cf.Methods() {
// has no parameter init method
if IsComponentSubclass && method.Name() == global.InitMethod {
if method.Descriptor() == global.NoParamVoidMethod {
HasNoParamInit = true
// OK
continue
}
}
// has field with set method
// only one string parameter
var continueFlag bool
if strings.HasPrefix(method.Name(), global.SetPrefix) {
// setField -> Field
temp := method.Name()[3:]
var flag bool
for _, field := range cl.Fields() {
if strings.ToLower(field.Name()) == strings.ToLower(temp) {
flag = true
break
}
}
// IMPORTANT: refresh flag
HasStringSetMethod = false
if flag && method.Descriptor() == global.StringParamSetVoidMethod {
HasStringSetMethod = true
}
// check conditions
if IsComponentSubclass && HasStringSetMethod && HasNoParamInit {
continueFlag = true
}
}
if !continueFlag {
continue
}
var invokeFlag bool
// **************************************************
// ************ Analysis JVM Instruction ************
// **************************************************
codeAttr := method.CodeAttribute()
if codeAttr == nil {
// interface or abstract
continue
}
bytecode := codeAttr.Code()
// virtual program counter
var pc int
reader := &common.BytecodeReader{}
// save all instructions to struct
instSet := &common.InstructionSet{}
instSet.ClassName = cl.Name()
instSet.MethodName = method.Name()
instSet.Desc = method.Descriptor()
var instList []string
// simple taint analysis
var taint bool
for {
// read finish
if pc >= len(bytecode) {
break
}
// offset
reader.Reset(bytecode, pc)
// read instruction
opcode := reader.ReadUint8()
inst := asm.NewInstruction(opcode)
// read operands of the instruction
inst.FetchOperands(reader)
ops := inst.GetOperands()
instEntry := common.InstructionEntry{
Instrument: getInstructionName(inst),
Operands: ops,
}
// (1) set(param) init -> param in LOCAL VARIABLE ARRAYS[1]
// (2) load arrays[1] -> param on top of stack
// (3) INVOKE ANY -> POP -> taint
//
// | LOCAL VARIABLES | | OPERAND STACK TOP |
// [ this | string param ] [ OTHERS ]
// AFTER LOAD INST
// [ this | string param ]->[ string param ]
// AFTER INVOKE ANY INST
// [ this | string param ] [ OTHERS ] -> POP
setLoadInst := &asm.ALOAD_1{}
if instEntry.Instrument == getInstructionName(setLoadInst) {
taint = true
}
instSet.InstArray = append(instSet.InstArray, instEntry)
// offset++
// read next
pc = reader.PC()
// INVOKE ANY
if strings.HasPrefix(instEntry.Instrument, global.Invoke) {
invokeFlag = true
// do not show desc info
temp := strings.Split(ops[0], " ")[0]
// now top of stack is taint
if taint {
temp = temp + " (taint)"
// clean
taint = false
}
instList = append(instList, temp)
}
}
if invokeFlag {
fmt.Println(instSet.ClassName, instSet.MethodName)
for _, i := range instList {
fmt.Println("->", i)
}
fmt.Println()
s := fmt.Sprintf("%s %s", instSet.ClassName, instSet.MethodName)
data := &resultData{
StringData: s,
InstListData: instList,
}
resultChan <- data
// clean cache
instList = nil
}
}
}
func startDiscovery(class string) {
data, err := os.ReadFile(class)
cf, err := classfile.Parse(data)
if err != nil {
log.Error(err.Error())
os.Exit(-1)
}
cl := asm.NewClass(cf)
s := &savedClassFile{
ClassName: cl.Name(),
ClassFile: cf,
ClassRef: cl,
}
discoveryClassFile = append(discoveryClassFile, s)
classMap[s.ClassName] = s.ClassRef
discoveryClass = append(discoveryClass, s.ClassRef)
}
func getInstructionName(instruction asm.Instruction) string {
// type name -> instruction name
i := fmt.Sprintf("%T", instruction)
return strings.Split(i, ".")[1]
}
func collect(resultChan chan *resultData, finishChan chan bool) {
buf := bytes.Buffer{}
for {
select {
case r := <-resultChan:
buf.Write([]byte(r.StringData))
buf.Write([]byte("\n"))
for _, v := range r.InstListData {
buf.Write([]byte("->"))
buf.Write([]byte(v))
buf.Write([]byte("\n"))
}
buf.Write([]byte("\n"))
break
case <-finishChan:
goto WRITE
}
}
WRITE:
os.WriteFile("result.txt", buf.Bytes(), 0644)
close(finishChan)
close(resultChan)
}
func handle() {
if err := recover(); err != nil {
message := fmt.Sprintf("%s", err)
var pcs [32]uintptr
n := runtime.Callers(3, pcs[:])
var str strings.Builder
temp := fmt.Sprintf("%s\nTraceback:", message)
str.WriteString(temp)
for _, pc := range pcs[:n] {
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
s := fmt.Sprintf("\n\t%s:%d", file, line)
str.WriteString(s)
}
log.Error("%s\n\n", str.String())
os.Exit(-1)
}
}
// swing-rce-inspector
// author: 4ra1n of Chaitin Tech
// create: 2022/10/02
// update: 2022/10/10
// line: 2663
func main() {
// set log level
log.SetLevel(log.InfoLevel)
// recover panic
defer handle()
log.Info("start swing-rce-inspector")
log.Info("delete last analysis data")
// delete last data
files.RemoveTempFiles()
log.Info("unzip jar files")
files.UnzipJars("jars")
// class file path array
classes := files.ReadAllClasses()
// (1) discovery
// all class -> discoveryClass
log.Info("start discovery")
classMap = make(map[string]*asm.Class)
for _, c := range classes {
startDiscovery(c)
}
log.Info("finish discovery")
// (2) IMPORTANT: build inheritance
// class A extends B
// class B extends C
// class C implements D,E
// A is subclass of B,C,D,E
// B is subclass of C,D,E
// C is subclass of D,E
log.Info("build inherit data")
inherit.Init(discoveryClass, classMap)
// (3) start look up
// <1> must be a subclass of java/awt/Component
// <2> must have a construction with no parameters
// <3> must have a field with set method
// <4> the set method has only one string parameters
// <5> have method invoke in the set method
// <6> simple taint analysis
log.Info("start analysis")
resultChan := make(chan *resultData)
finishChan := make(chan bool, 1)
go collect(resultChan, finishChan)
for _, i := range discoveryClassFile {
// IMPORTANT
// do not use goroutine
// because of static ConstantPool
startAnalysis(i, resultChan)
}
// make sure task finish
finishChan <- true
// delete temp data
log.Info("delete temp data")
files.RemoveTempFiles()
log.Info("finish")
log.Info("press ctrl+c exit")
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, os.Kill)
for {
s := <-c
fmt.Println(s)
os.Exit(0)
}
}