Skip to content
This repository has been archived by the owner on Oct 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #108 from joernio/claudiu/ghidra-env
Browse files Browse the repository at this point in the history
Add getenvToStrcpy ghidra query
  • Loading branch information
ursachec authored Sep 26, 2021
2 parents 702efe8 + 831d7b3 commit 78f44b5
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import io.shiftleft.semanticcpg.language._
import io.shiftleft.dataflowengineoss.language._
import io.shiftleft.dataflowengineoss.queryengine.EngineContext

object MainArgsToStrcpy extends QueryBundle {
object UserInputIntoDangerousFunctions extends QueryBundle {

implicit val resolver: ICallResolver = NoResolve

Expand All @@ -24,9 +24,30 @@ object MainArgsToStrcpy extends QueryBundle {
score = 4,
withStrRep({ cpg =>
def source = cpg.method.fullName("main").parameter
def sink = cpg.call.methodFullName("strcpy").argument
def sink = cpg.method.fullName("strcpy").parameter.index(2)
sink.reachableBy(source).l
}),
tags = List(QueryTags.badfn)
)

@q
def getenvToStrcpy()(implicit context: EngineContext): Query =
Query.make(
name = "getenv-to-strcpy",
author = Crew.claudiu,
title = "`getenv` fn arguments used in strcpy source buffer",
description =
"""
|User-input ends up in source buffer argument of strcpy, which might overflow the destination buffer.
|""".stripMargin,
score = 4,
withStrRep({ cpg =>
def source =
cpg.call.methodFullName("getenv").cfgNext.isCall.argument(2)
def sink = cpg.method.fullName("strcpy").parameter.index(2)
sink.reachableBy(source).l
}),
tags = List(QueryTags.badfn)
)

}
18 changes: 18 additions & 0 deletions src/test/resources/testbinaries/buf2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// gcc -fno-stack-protector -z execstack -no-pie -o buf2 buf2.c
int main(int argc, char *argv[]) {
const char* inEnv = getenv("BUF2IN");
if (inEnv == NULL) {
printf("BUF2IN environment variable not set.");
return -1;
}

char c[6];
strcpy(c, inEnv);
printf("First argument is: %s\n", c);
return 0;
}

Binary file added src/test/resources/testbinaries/buf2.exe
Binary file not shown.
18 changes: 18 additions & 0 deletions src/test/resources/testbinaries/buf2_neg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// gcc -fno-stack-protector -z execstack -no-pie -o buf2_neg buf2_neg.c
int main(int argc, char *argv[]) {
const char* inEnv = getenv("BUF2IN");
if (inEnv == NULL) {
printf("BUF2IN environment variable not set.");
return -1;
}

char c[6];
strcpy(c, "NOTHING");
printf("First argument is: %s\n", c);
return 0;
}

Binary file added src/test/resources/testbinaries/buf2_neg.exe
Binary file not shown.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.joern.scanners.ghidra

import io.joern.suites.GhidraQueryTestSuite

class UserInputIntoDangerousFunctionsTests extends GhidraQueryTestSuite {
override def queryBundle = UserInputIntoDangerousFunctions

"mainArgsToStrcpy query" when {
def query = queryBundle.mainArgsToStrcpy()
"executed on CPG for binary with dataflow between `main` fn args and `strcpy` source argument" should {
"find the `main` function among the tracking points returned" in {
buildCpgForBin("buf1.exe")
val results = methodNamesForMatchedPoints(query)
results shouldBe Set("main")
}
}
}

"getenvToStrcpy query" when {
def query = queryBundle.getenvToStrcpy()

"executed on CPG for binary call to `strcpy`, but no call to `getenv`" should {
"return an empty set of matched method names" in {
buildCpgForBin("buf1.exe")
val results = methodNamesForMatchedPoints(query)
results shouldBe Set()
}
}

"executed on CPG for binary call to `strcpy`, and call to `getenv`, but no dataflow between them" should {
"return an empty set of matched method names" in {
buildCpgForBin("buf2_neg.exe")
val results = methodNamesForMatchedPoints(query)
results shouldBe Set()
}
}

"executed on CPG for binary with dataflow between `getenv` return value and `strcpy` source argument" should {
"find main function with data flow between getenv and strcpy" in {
buildCpgForBin("buf2.exe")
val results = methodNamesForMatchedPoints(query)
results shouldBe Set("main")
}
}
}
}
14 changes: 8 additions & 6 deletions src/test/scala/io/joern/suites/GhidraQueryTestSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.shiftleft.codepropertygraph.generated.nodes
import io.shiftleft.console.{Query, QueryBundle}
import io.shiftleft.utils.ProjectRoot
import io.shiftleft.semanticcpg.language._
import overflowdb.traversal.iterableToTraversal

class GhidraQueryTestSuite extends DataFlowBinToCpgSuite {
val argumentProvider = new QDBArgumentProvider(3)
Expand All @@ -22,21 +23,22 @@ class GhidraQueryTestSuite extends DataFlowBinToCpgSuite {

def allQueries = QueryUtil.allQueries(queryBundle, argumentProvider)

def findMatchingMethodParam(query: Query): Set[String] = {
def findMatchingCalls(query: Query): Set[String] = {
query(cpg)
.flatMap(_.evidence)
.collect { case methodParam: nodes.MethodParameterIn => methodParam }
.collect { case call: nodes.Call => call }
.method
.name
.toSetImmutable
}

def findMatchingCalls(query: Query): Set[String] = {
def methodNamesForMatchedPoints(query: Query): Set[String] = {
nodes.MethodParameterIn
query(cpg)
.flatMap(_.evidence)
.collect { case call: nodes.Call => call }
.method
.name
.collect {
case cfgNode: nodes.CfgNode => cfgNode.method.name
}
.toSetImmutable
}
}

0 comments on commit 78f44b5

Please sign in to comment.