335 lines
8.9 KiB
Scala
335 lines
8.9 KiB
Scala
package analysis
|
|
|
|
import com.microsoft.z3._
|
|
import dex.DexType.{ObjectType, PrimitiveType, VoidType}
|
|
import dex.{DexApp, DexClass, DexField, DexMethod}
|
|
import domain.{AbstractDomain, AbstractDomainsCollection}
|
|
|
|
/**
|
|
* Environment for the analysis.
|
|
*/
|
|
class Environment(
|
|
val ctx: Context,
|
|
val domains: AbstractDomainsCollection,
|
|
val app: DexApp,
|
|
val preAnalysisResult: PreAnalysisResult
|
|
) {
|
|
|
|
val z3Obj = new Z3Object(ctx, domains)
|
|
|
|
/**
|
|
* Abstract registers.
|
|
*/
|
|
object R {
|
|
|
|
/**
|
|
* Index of the ret register.
|
|
*/
|
|
val retIdx: Int = -1
|
|
|
|
/**
|
|
* Approximation of a register.
|
|
*/
|
|
class RApprox(
|
|
val numApprox: BoolExpr,
|
|
val referenceApprox: BoolExpr,
|
|
val taintApprox: BoolExpr
|
|
) {}
|
|
|
|
/**
|
|
* Register at a program point.
|
|
*/
|
|
class RAtPP(
|
|
private val regCount: Int,
|
|
val numbers: Int => domains.primitiveDomain.Element,
|
|
val references: Int => domains.referenceDomain.Element,
|
|
val taints: Int => domains.taintDomain.Element,
|
|
) {
|
|
|
|
/**
|
|
* Approximate the register to top (taint excluded).
|
|
* @param i index of the register.
|
|
* @return a register approximation.
|
|
*/
|
|
def top(i: Int): RApprox =
|
|
new RApprox(
|
|
domains.primitiveDomain.approx(
|
|
domains.primitiveDomain.top,
|
|
this.numbers(i),
|
|
),
|
|
domains.referenceDomain.approx(
|
|
domains.referenceDomain.top,
|
|
this.references(i),
|
|
),
|
|
ctx.mkTrue()
|
|
)
|
|
|
|
/**
|
|
* Approximate a register to another.
|
|
* @param i index of the register that must be approximated.
|
|
* @param j index of the register that must approximates.
|
|
* @param r register at the desired program point.
|
|
* @return a register approximation.
|
|
*/
|
|
def <=(i: Int, j: Int)(r: RAtPP): RApprox =
|
|
new RApprox(
|
|
domains.primitiveDomain.approx(
|
|
this.numbers(i),
|
|
r.numbers(j)
|
|
),
|
|
domains.referenceDomain.approx(
|
|
this.references(i),
|
|
r.references(j)
|
|
),
|
|
domains.taintDomain.approx(
|
|
this.taints(i),
|
|
r.taints(j)
|
|
)
|
|
)
|
|
|
|
/**
|
|
* Approximate all the registers between two program points.
|
|
* @param r registers at the desider program point.
|
|
* @return registers approximations.
|
|
*/
|
|
def <=(r: RAtPP): List[RApprox] =
|
|
(-1 until this.regCount)
|
|
.map(i => this.<=(i, i)(r))
|
|
.toList
|
|
|
|
/**
|
|
* Approximate all the registers between two program points except for the register at the given index.
|
|
* @param n index of the register to exclude.
|
|
* @param r registers at the desider program point.
|
|
* @return registers approximations.
|
|
*/
|
|
def <=(n: Int)(r: RAtPP): List[RApprox] =
|
|
(-1 until this.regCount)
|
|
.filter(i => i != n)
|
|
.map(i => this.<=(i, i)(r))
|
|
.toList
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the registers at the given program points.
|
|
* @param pp the program point.
|
|
* @return the registers.
|
|
*/
|
|
def apply(pp: PP): RAtPP =
|
|
new RAtPP(
|
|
pp.m.registersCount,
|
|
rTypeAtPP(pp, domains.primitiveDomain, "R-prim"),
|
|
rTypeAtPP(pp, domains.referenceDomain, "R-ref"),
|
|
rTypeAtPP(pp, domains.taintDomain, "R-taint")
|
|
)
|
|
|
|
/**
|
|
* Returns a function that maps a register index to the given domain Element.
|
|
* @param pp program point of the registers.
|
|
* @param domain the chosen domain.
|
|
* @param prefix prefix to distinguish between Elements of different domains.
|
|
* @tparam E the domain Element type.
|
|
* @return function that maps the index to the Element.
|
|
*/
|
|
private def rTypeAtPP[E](
|
|
pp: PP,
|
|
domain: AbstractDomain,
|
|
prefix: String
|
|
): Int => E =
|
|
(i) =>
|
|
ctx
|
|
.mkApp(
|
|
ctx.mkFuncDecl(
|
|
s"$prefix-${pp.c.name}-${pp.m.name}-${pp.pc}",
|
|
Array[Sort](ctx.mkIntSort()),
|
|
domain.sort
|
|
),
|
|
ctx.mkInt(i)
|
|
)
|
|
.asInstanceOf[E]
|
|
|
|
/**
|
|
* Convert a list of registers approximations into a list of Z3 constraints.
|
|
*/
|
|
implicit def toBoolExpr(approxMap: List[RApprox]): List[BoolExpr] =
|
|
approxMap
|
|
.flatMap(v => toBoolExpr(v))
|
|
|
|
/**
|
|
* Convert a register approximation into a list of Z3 constraints.
|
|
*/
|
|
implicit def toBoolExpr(approx: RApprox): List[BoolExpr] =
|
|
List(approx.numApprox, approx.referenceApprox, approx.taintApprox)
|
|
|
|
}
|
|
|
|
/**
|
|
* Abstract heap.
|
|
*/
|
|
object H {
|
|
|
|
/**
|
|
* Returns the heap that maps references to objects.
|
|
*/
|
|
def obj(): FuncDecl = {
|
|
val z3Obj = new Z3Object(ctx, domains)
|
|
ctx.mkFuncDecl(
|
|
"H",
|
|
Array[Sort](ctx.mkStringSort()),
|
|
z3Obj.sort
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Abstract static heap.
|
|
*/
|
|
object SH {
|
|
|
|
/**
|
|
* Returns the class field value.
|
|
* @param dexClass the class.
|
|
* @param dexField the field.
|
|
* @return a tuple where the first element is the value and the second the taint.
|
|
*/
|
|
def apply(dexClass: DexClass, dexField: DexField): (Expr, Expr) = {
|
|
(
|
|
dexField.dexType match {
|
|
case PrimitiveType() =>
|
|
const(dexClass, dexField, domains.primitiveDomain.sort, "SH-prim")
|
|
case ObjectType(_) =>
|
|
const(dexClass, dexField, domains.referenceDomain.sort, "SH-ref")
|
|
},
|
|
const(dexClass, dexField, domains.taintDomain.sort, "SH-taint")
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Returns the constant that represent the class field.
|
|
* @param dexClass the class.
|
|
* @param dexField the field.
|
|
* @param sort the type of the constant domain.
|
|
* @param prefix prefix to distinguish between the same constant in different domains.
|
|
* @return the constant.
|
|
*/
|
|
private def const(
|
|
dexClass: DexClass,
|
|
dexField: DexField,
|
|
sort: Sort,
|
|
prefix: String
|
|
): Expr = {
|
|
ctx.mkConst(s"$prefix-${dexClass.name}-${dexField.name}", sort)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method return value.
|
|
*/
|
|
object M {
|
|
|
|
/**
|
|
* Value returned by the given class method.
|
|
* @param dexClass the class.
|
|
* @param dexMethod the method.
|
|
* @return the value.
|
|
*/
|
|
def apply(dexClass: DexClass, dexMethod: DexMethod): Option[Expr] = {
|
|
dexMethod.returnType match {
|
|
case PrimitiveType() =>
|
|
Option(
|
|
typeReturn(
|
|
dexClass,
|
|
dexMethod,
|
|
domains.primitiveDomain.sort,
|
|
"M-prim"
|
|
)
|
|
)
|
|
case ObjectType(_) =>
|
|
Option(
|
|
typeReturn(
|
|
dexClass,
|
|
dexMethod,
|
|
domains.referenceDomain.sort,
|
|
"M-ref"
|
|
)
|
|
)
|
|
case VoidType() => Option.empty
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method result in the given domain.
|
|
*/
|
|
private def typeReturn(
|
|
dexClass: DexClass,
|
|
dexMethod: DexMethod,
|
|
sort: Sort,
|
|
prefix: String
|
|
): Expr = {
|
|
ctx.mkConst(
|
|
s"$prefix-${dexClass.name}-${dexMethod.name}",
|
|
sort
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Method invocation helper.
|
|
*/
|
|
object Dispatch {
|
|
|
|
/**
|
|
* Returns a map where the key is the class that has the given
|
|
* method, either because it implements it or because it is inherited,
|
|
* and the value is the corresponding class with the method implementation.
|
|
*/
|
|
def apply(dexMethod: String,
|
|
definingClass: String): Map[DexClass, DexClass] = {
|
|
dispatchAux(dexMethod, definingClass).toMap
|
|
}
|
|
|
|
/**
|
|
* Tail recursive search of method implementations.
|
|
*/
|
|
private def dispatchAux(
|
|
dexMethod: String,
|
|
definingClass: String): Seq[(DexClass, DexClass)] = {
|
|
val defClass = app.classes.find(c => c.name == definingClass)
|
|
val impl: Option[DexClass] =
|
|
defClass match {
|
|
case Some(c) => firstImpl(dexMethod, c)
|
|
case None => Option.empty
|
|
}
|
|
val impls = app.classes
|
|
.filter(c => c.superClass == definingClass)
|
|
.flatMap(
|
|
c => dispatchAux(dexMethod, c.name)
|
|
)
|
|
.toList
|
|
impl match {
|
|
case Some(c) => (defClass.get -> c) :: impls
|
|
case None => impls
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the first implementation (if present) of the given method when invoked to the given class.
|
|
*/
|
|
private def firstImpl(dexMethod: String,
|
|
dexClass: DexClass): Option[DexClass] = {
|
|
dexClass.methods.find(m => m.name == dexMethod) match {
|
|
case Some(_) => Option(dexClass)
|
|
case None =>
|
|
app.classes.find(c => c.name == dexClass.superClass) match {
|
|
case Some(c) => firstImpl(dexMethod, c)
|
|
case None => Option.empty
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|