Skip to main content

Investigate an Application with an Objective-C Frontend

This article will walk you through investigating an application with an Objective-C frontend using Ocular.

Sample Application

For the examples to follow, we will be working with the Mach-O Browser application, whose repository you can clone via GitHub.

// clone the project
git clone https://github.com/dcsch/macho-browser.git

// navigate into the working directory and build
cd macho-browser

// build
xcodebuild OTHER_CFLAGS=-flto OTHER_LDFLAGS=-flto CODE_SIGN_IDENTITY="-" DEVELOPMENT_TEAM="" -configuration Debug

// navigate into the folder with the build files
cd "Build/Mach-O Browser.build/Debug/Mach-O Browser.build/Objects-normal/x86_64/"

Once you've successfully built macho-browser, you can transform it to a Code Property Graph (CPG):

/Users/<username>/.shiftleft/ocular/llvm2cpg.sh --output=/tmp/macho.cpg.bin.zip *.o

If you're not using the default install location for Ocular, you may need to change /.shiftleft/ocular to the appropriate path.

Once you have the CPG, start Ocular:

sl ocular

When prompted, load the CPG you generated:

ocular> importCpg("/tmp/macho.cpg.bin.zip")

Run workspace to ensure that your CPG has been loaded. If loaded is true, you're ready to proceed with your investigative queries.

ocular> workspace
res0: Workspace =
_________________________________________________________________
| name | overlays | loaded|
|================================================================|
| macho.cpg.bin.zip | semanticcpg(l),dataflow(l) | true |

Investigative Queries

The following examples will walk you through the basics of querying the CPG to gather information about your application.

  • Identify functions with more than 4 parameters: cpg.method.where(_.parameter.size > 4).l

  • Sort methods by the number of callers and exclude the first 1000 results: val methodList = cpg.method.map(x => (x.start.callIn.size, x.name)).l.sorted.reverse.take(1000)

  • Filter for methods that have callers: methodList.filter(_._1>0).sorted

  • Filter for methods that do not have callers: methodList.filter(_._1==0)

  • Get all of the hard-coded literals in the code: cpg.literal.code.l

  • Get all types and associated properties/members in the code: val typesList = cpg.typeDecl.map { t => (t.name, t.start.member.name.l) }.l

  • Identify the call site to malloc where the first argument contains an arithmetic expression: cpg.call("malloc").filter(_.argument (1).arithmetics ).l

Querying for Method-Specific Information

Let's say that you're interested in a class called LoadCommand and how it is used. You can declare variables containing the class name for easy reuse:

val className="LoadCommand"

You can pick a class and observe its methods:

cpg.typeDecl.name(className).method.fullName.p.sorted

The result will look something like the following:

result: List[String] = List(
"+[LoadCommand loadCommandWithData:offset:]",
"-[LoadCommand .cxx_destruct]",
...
)

You can get a list of all Objective-C classes (the convention is that class names start with an uppercase later):

cpg.typeDecl.name("[A-Z][A-Za-z]*").name.p.sorted

res11: List[String] = List(
"ANY",
"AlignmentFormatter",
"CGPoint",
"NSObject",
...
)

You can find where a method is called:

cpg.method.fullNameExact("-[LoadCommand initWithData:offset:]").caller.fullName.p

result: List[String] = List("+[LoadCommand loadCommandWithData:offset:]")

Get subclasses in it's type hierarchy:

cpg.typeDecl.name(className).derivedTypeDecl.name.p

result: List[String] = List("SymbolTableLoadCommand", "SegmentLoadCommand")

Get superclasses in its type hierarchy:

cpg.typeDecl.name("LoadCommand").baseTypeDecl.name.p

result: List[String] = List("NSObject")

Get all of the NSObject subclasses:

cpg.typeDecl.name("NSObject").derivedTypeDeclTransitive.name.p

result: List[String] = List(
"Symbol",
"Section",
...
)

Example: Protecting Against the Billion Laughs Attack

iOS offers you two SDK options for parsing XML: NSXMLParser and libxml2.

If you're using NSXMLParser, you should enable the shouldResolveExternalEntities property to protect yourself against the Billion Laughs Attack:

// Verify that the NSXMLParser has been imported and shouldResolveExternalEntities has been enabled
cpg.typeDecl.name(".*NSXMLParser.*").member.name(".*shouldResolveExternalEntities.*").l

// [OR] Verify that, if shouldResolveExternalEntities is set, it is associated to the type NSXMLParser
cpg.method.name(".*shouldResolveExternalEntities.*").where(_.astParentFullName.contains("NSXMLParser")).l