DalvikBytecodeAnalysis/src/main/scala/analysis/Environment.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
}
}
}
}
}