Skip to content

Commit 8e7a823

Browse files
authored
Merge pull request github#5083 from raulgarciamsft/master
Adding queries related to the Solorigate campaign
2 parents d94f20f + cba9f42 commit 8e7a823

File tree

43 files changed

+1953
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1953
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- description: Queries related to Solorigate
2+
- qlpack: codeql-csharp
3+
- include:
4+
tags contain: solorigate
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>This query finds native calls to external functions that are often used in creating backdoors or are generally attributed to unsafe code practices. This is an example of a query that may be useful for detecting potential backdoors. Solorigate is one example that uses this mechanism.</p>
7+
</overview>
8+
9+
<recommendation>
10+
<p>Any findings from this rule are only intended to indicate suspicious code that shares similarities with known portions of code used for the Solorigate attack. There is no certainty that the code is related or that the code is part of any attack.</p>
11+
<p>For more information about Solorigate, please visit https://aka.ms/solorigate. </p>
12+
</recommendation>
13+
14+
</qhelp>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @name Potential dangerous use of native functions
3+
* @description Detects the use of native functions that can be used for malicious intent or unsafe handling.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @precision low
7+
* @id cs/backdoor/dangerous-native-functions
8+
* @tags security
9+
* solorigate
10+
*/
11+
12+
import csharp
13+
import semmle.code.csharp.frameworks.system.runtime.InteropServices
14+
15+
predicate isDangerousMethod(Method m) {
16+
m.getName() = "OpenProcessToken" or
17+
m.getName() = "OpenThreadToken" or
18+
m.getName() = "DuplicateToken" or
19+
m.getName() = "DuplicateTokenEx" or
20+
m.getName().matches("LogonUser%") or
21+
m.getName().matches("WNetAddConnection%") or
22+
m.getName() = "DeviceIoControl" or
23+
m.getName().matches("LoadLibrary%") or
24+
m.getName() = "GetProcAddress" or
25+
m.getName().matches("CreateProcess%") or
26+
m.getName().matches("InitiateSystemShutdown%") or
27+
m.getName() = "GetCurrentProcess" or
28+
m.getName() = "GetCurrentProcessToken" or
29+
m.getName() = "GetCurrentThreadToken" or
30+
m.getName() = "GetCurrentThreadEffectiveToken" or
31+
m.getName() = "OpenThreadToken" or
32+
m.getName() = "SetTokenInformation" or
33+
m.getName().matches("LookupPrivilegeValue%") or
34+
m.getName() = "AdjustTokenPrivileges" or
35+
m.getName() = "SetProcessPrivilege" or
36+
m.getName() = "ImpersonateLoggedOnUser" or
37+
m.getName().matches("Add%Ace%")
38+
}
39+
40+
predicate isExternMethod(Method externMethod) {
41+
externMethod.isExtern()
42+
or
43+
externMethod.getAnAttribute().getType() instanceof
44+
SystemRuntimeInteropServicesDllImportAttributeClass
45+
or
46+
externMethod.getDeclaringType().getAnAttribute().getType() instanceof
47+
SystemRuntimeInteropServicesComImportAttributeClass
48+
}
49+
50+
from MethodCall mc
51+
where
52+
isExternMethod(mc.getTarget()) and
53+
isDangerousMethod(mc.getTarget())
54+
select mc, "Call to an external method '" + mc.getTarget().getName() + "'."
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>This query detects situations in which an offset to a last file modification time is used to conditionally execute a particular block of code. This is a common pattern in backdoors, where the file's modification timestamp is the time at which the backdoor was planted, and the time offset is used as a time bomb before a particular code block is executed.</p>
7+
</overview>
8+
9+
<recommendation>
10+
<p>Any findings from this rule are only intended to indicate suspicious code that shares similarities with known portions of code used for the Solorigate attack. There is no certainty that the code is related or that the code is part of any attack.</p>
11+
<p>For more information about Solorigate, please visit https://aka.ms/solorigate. </p>
12+
</recommendation>
13+
14+
</qhelp>
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @name Potential Timebomb
3+
* @description If there is data flow from a file's last modification date and an offset to a condition statement, this could trigger a "time bomb".
4+
* @kind path-problem
5+
* @precision Low
6+
* @problem.severity warning
7+
* @id cs/backdoor/potential-time-bomb
8+
* @tags security
9+
* solorigate
10+
*/
11+
12+
import csharp
13+
import DataFlow
14+
15+
query predicate nodes = PathGraph::nodes/3;
16+
17+
query predicate edges(DataFlow::PathNode a, DataFlow::PathNode b) {
18+
PathGraph::edges(a, b)
19+
or
20+
exists(
21+
FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable conf1,
22+
FlowsFromTimeSpanArithmeticToTimeComparisonCallable conf2
23+
|
24+
conf1 = a.getConfiguration() and
25+
conf1.isSink(a.getNode()) and
26+
conf2 = b.getConfiguration() and
27+
b.isSource()
28+
)
29+
or
30+
exists(
31+
FlowsFromTimeSpanArithmeticToTimeComparisonCallable conf1,
32+
FlowsFromTimeComparisonCallableToSelectionStatementCondition conf2
33+
|
34+
conf1 = a.getConfiguration() and
35+
conf1.isSink(a.getNode()) and
36+
conf2 = b.getConfiguration() and
37+
b.isSource()
38+
)
39+
}
40+
41+
/**
42+
* Class that will help to find the source for the trigger file-modification date.
43+
*
44+
* May be extended as new patterns for similar time bombs are found.
45+
*/
46+
class GetLastWriteTimeMethod extends Method {
47+
GetLastWriteTimeMethod() {
48+
this.getQualifiedName() in [
49+
"System.IO.File.GetLastWriteTime", "System.IO.File.GetFileCreationTime",
50+
"System.IO.File.GetCreationTimeUtc", "System.IO.File.GetLastAccessTimeUtc"
51+
]
52+
}
53+
}
54+
55+
/**
56+
* Abstracts `System.DateTime` structure
57+
*/
58+
class DateTimeStruct extends Struct {
59+
DateTimeStruct() { this.getQualifiedName() = "System.DateTime" }
60+
61+
/**
62+
* holds if the Callable is used for DateTime arithmetic operations
63+
*/
64+
Callable getATimeSpanArtithmeticCallable() {
65+
(result = this.getAnOperator() or result = this.getAMethod()) and
66+
result.getName() in [
67+
"Add", "AddDays", "AddHours", "AddMilliseconds", "AddMinutes", "AddMonths", "AddSeconds",
68+
"AddTicks", "AddYears", "+", "-"
69+
]
70+
}
71+
72+
/**
73+
* Holds if the Callable is used for DateTime comparision
74+
*/
75+
Callable getAComparisonCallable() {
76+
(result = this.getAnOperator() or result = this.getAMethod()) and
77+
result.getName() in ["Compare", "CompareTo", "Equals", "==", "!=", "<", ">", "<=", ">="]
78+
}
79+
}
80+
81+
/**
82+
* Dataflow configuration to find flow from a GetLastWriteTime source to a DateTime arithmetic operation
83+
*/
84+
private class FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable extends TaintTracking::Configuration {
85+
FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable() {
86+
this = "FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable"
87+
}
88+
89+
override predicate isSource(DataFlow::Node source) {
90+
exists(Call call, GetLastWriteTimeMethod m |
91+
m.getACall() = call and
92+
source.asExpr() = call
93+
)
94+
}
95+
96+
override predicate isSink(DataFlow::Node sink) {
97+
exists(Call call, DateTimeStruct dateTime |
98+
call.getAChild*() = sink.asExpr() and
99+
call = dateTime.getATimeSpanArtithmeticCallable().getACall()
100+
)
101+
}
102+
}
103+
104+
/**
105+
* Dataflow configuration to find flow from a DateTime arithmetic operation to a DateTime comparison operation
106+
*/
107+
private class FlowsFromTimeSpanArithmeticToTimeComparisonCallable extends TaintTracking::Configuration {
108+
FlowsFromTimeSpanArithmeticToTimeComparisonCallable() {
109+
this = "FlowsFromTimeSpanArithmeticToTimeComparisonCallable"
110+
}
111+
112+
override predicate isSource(DataFlow::Node source) {
113+
exists(DateTimeStruct dateTime, Call call | source.asExpr() = call |
114+
call = dateTime.getATimeSpanArtithmeticCallable().getACall()
115+
)
116+
}
117+
118+
override predicate isSink(DataFlow::Node sink) {
119+
exists(Call call, DateTimeStruct dateTime |
120+
call.getAnArgument().getAChild*() = sink.asExpr() and
121+
call = dateTime.getAComparisonCallable().getACall()
122+
)
123+
}
124+
}
125+
126+
/**
127+
* Dataflow configuration to find flow from a DateTime comparison operation to a Selection Statement (such as an If)
128+
*/
129+
private class FlowsFromTimeComparisonCallableToSelectionStatementCondition extends TaintTracking::Configuration {
130+
FlowsFromTimeComparisonCallableToSelectionStatementCondition() {
131+
this = "FlowsFromTimeComparisonCallableToSelectionStatementCondition"
132+
}
133+
134+
override predicate isSource(DataFlow::Node source) {
135+
exists(DateTimeStruct dateTime, Call call | source.asExpr() = call |
136+
call = dateTime.getAComparisonCallable().getACall()
137+
)
138+
}
139+
140+
override predicate isSink(DataFlow::Node sink) {
141+
exists(SelectionStmt sel | sel.getCondition().getAChild*() = sink.asExpr())
142+
}
143+
}
144+
145+
/**
146+
* Holds if the last file modification date from the call to getLastWriteTimeMethodCall will be used in a DateTime arithmetic operation timeArithmeticCall,
147+
* which is then used for a DateTime comparison timeComparisonCall and the result flows to a Selection statement which is likely a TimeBomb trigger
148+
*/
149+
predicate isPotentialTimeBomb(
150+
DataFlow::PathNode pathSource, DataFlow::PathNode pathSink, Call getLastWriteTimeMethodCall,
151+
Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement
152+
) {
153+
exists(
154+
FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable config1, Node sink,
155+
DateTimeStruct dateTime, FlowsFromTimeSpanArithmeticToTimeComparisonCallable config2,
156+
Node sink2, FlowsFromTimeComparisonCallableToSelectionStatementCondition config3, Node sink3
157+
|
158+
pathSource.getNode() = exprNode(getLastWriteTimeMethodCall) and
159+
config1.hasFlow(exprNode(getLastWriteTimeMethodCall), sink) and
160+
timeArithmeticCall = dateTime.getATimeSpanArtithmeticCallable().getACall() and
161+
timeArithmeticCall.getAChild*() = sink.asExpr() and
162+
config2.hasFlow(exprNode(timeArithmeticCall), sink2) and
163+
timeComparisonCall = dateTime.getAComparisonCallable().getACall() and
164+
timeComparisonCall.getAnArgument().getAChild*() = sink2.asExpr() and
165+
config3.hasFlow(exprNode(timeComparisonCall), sink3) and
166+
selStatement.getCondition().getAChild*() = sink3.asExpr() and
167+
pathSink.getNode() = sink3
168+
)
169+
}
170+
171+
from
172+
DataFlow::PathNode source, DataFlow::PathNode sink, Call getLastWriteTimeMethodCall,
173+
Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement
174+
where
175+
isPotentialTimeBomb(source, sink, getLastWriteTimeMethodCall, timeArithmeticCall,
176+
timeComparisonCall, selStatement)
177+
select selStatement, source, sink,
178+
"Possible TimeBomb logic triggered by $@ that takes into account $@ from the $@ as part of the potential trigger.",
179+
timeComparisonCall, timeComparisonCall.toString(), timeArithmeticCall, "an offset",
180+
getLastWriteTimeMethodCall, "last modification time of a file"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>This query detects code flow from ProcessName property on the Process class into a hash function.</p>
7+
<p>Such flow is often used in code backdoors to detect running processes and compare them to an obfuscated list of antivirus processes to avoid detection. Solorigate is one example that uses this mechanism.</p>
8+
</overview>
9+
10+
<recommendation>
11+
<p>Any findings from this rule are only intended to indicate suspicious code that shares similarities with known portions of code used for the Solorigate attack. There is no certainty that the code is related or that the code is part of any attack.</p>
12+
<p>For more information about Solorigate, please visit https://aka.ms/solorigate. </p>
13+
</recommendation>
14+
15+
</qhelp>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @name ProcessName to hash function flow
3+
* @description Flow from a function retrieving process name to a hash function.
4+
* @kind path-problem
5+
* @tags security
6+
* solorigate
7+
* @problem.severity warning
8+
* @precision medium
9+
* @id cs/backdoor/process-name-to-hash-function
10+
*/
11+
12+
import csharp
13+
import DataFlow::PathGraph
14+
import experimental.code.csharp.Cryptography.NonCryptographicHashes
15+
16+
class DataFlowFromMethodToHash extends TaintTracking::Configuration {
17+
DataFlowFromMethodToHash() { this = "DataFlowFromMethodNameToHashFunction" }
18+
19+
/**
20+
* Holds if `source` is a relevant data flow source.
21+
*/
22+
override predicate isSource(DataFlow::Node source) { isSuspiciousPropertyName(source.asExpr()) }
23+
24+
/**
25+
* Holds if `sink` is a relevant data flow sink.
26+
*/
27+
override predicate isSink(DataFlow::Node sink) { isGetHash(sink.asExpr()) }
28+
}
29+
30+
predicate isGetHash(Expr arg) {
31+
exists(MethodCall mc |
32+
(
33+
mc.getTarget().getName().matches("%Hash%") or
34+
mc.getTarget().getName().regexpMatch("Md[4-5]|Sha[1-9]{1,3}")
35+
) and
36+
mc.getAnArgument() = arg
37+
)
38+
or
39+
exists(Callable callable, Parameter param, Call call |
40+
isCallableAPotentialNonCryptographicHashFunction(callable, param) and
41+
call = callable.getACall() and
42+
arg = call.getArgumentForParameter(param)
43+
)
44+
}
45+
46+
predicate isSuspiciousPropertyName(PropertyRead pr) {
47+
pr.getTarget().getQualifiedName() = "System.Diagnostics.Process.ProcessName"
48+
}
49+
50+
from DataFlow::PathNode src, DataFlow::PathNode sink, DataFlowFromMethodToHash conf
51+
where conf.hasFlow(src.getNode(), sink.getNode())
52+
select src.getNode(), src, sink,
53+
"The hash is calculated on the process name $@, may be related to a backdoor. Please review the code for possible malicious intent.",
54+
sink.getNode(), "here"

0 commit comments

Comments
 (0)