Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
neil-wu committed Jul 15, 2020
0 parents commit ade89f1
Show file tree
Hide file tree
Showing 20 changed files with 4,208 additions and 0 deletions.
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(The MIT License)

Copyright (c) 2020 neilwu (https://github.com/neil-wu)

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
### FridaHookSwiftAlamofire

A frida tool that capture GET/POST HTTP requests of iOS Swift library 'Alamofire' and disable SSL Pinning.

### Features

* Capture and print GET/POST HTTP requests in Alamofire
* Kill SSL Pinning in Alamofire
* Swift runtime interop in Frida (Support Swift Foundation URL/Data/String)
* Support Swift 5.*
* Demo code for calling swift runtime function `Foundation.Data._bridgeToObjectiveC()`

![demo](./doc/demo.png)


#### Usage

1. build frida-agent (npm install && npm run watch)
2. ./run.sh or frida -UF -l ./frida-agent/_agent.js

#### License

MIT

Binary file added doc/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions frida-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/_agent.js
/node_modules
19 changes: 19 additions & 0 deletions frida-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### How to compile & load

```sh
$ git clone git://github.com/oleavr/frida-agent-example.git
$ cd frida-agent-example/
$ npm install
$ frida -U -f com.example.android --no-pause -l _agent.js
```

### Development workflow

To continuously recompile on change, keep this running in a terminal:

```sh
$ npm run watch
```

And use an editor like Visual Studio Code for code completion and instant
type-checking feedback.
32 changes: 32 additions & 0 deletions frida-agent/agent/HookAFServerTrust.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { log } from "./logger";

function attach() {
try {
// Disable Alamofire ServerTrust policy
// SessionDelegate func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation
// Alamofire.SessionDelegate.attemptServerTrustAuthentication(with: __C.NSURLAuthenticationChallenge) -> (disposition: __C.NSURLSessionAuthChallengeDisposition, credential: __C.NSURLCredential?, error: Alamofire.AFError?)
let func_attemptServerTrust = Module.getExportByName(null, '$s9Alamofire15SessionDelegateC32attemptServerTrustAuthentication4withSo36NSURLSessionAuthChallengeDispositionV11disposition_So15NSURLCredentialCSg10credentialAA7AFErrorOSg5errortSo019NSURLAuthenticationK0C_tF'); // remove prefix _
log(`[HookAFServerTrust] hook func_attemptServerTrust ${func_attemptServerTrust}`);
Interceptor.attach(func_attemptServerTrust, {
onLeave(retval:InvocationReturnValue) {
// force set retval to 0x1 to enable .performDefaultHandling

let val = retval.toInt32();
if (val != 0x1) {
log(`[HookAFServerTrust] attemptServerTrustAuthentication retval ${retval}, reset to 0x1`);
let fakeret = new NativePointer(0x1)
retval.replace(fakeret)
}
}
});

} catch (e) {
log(`[HookAFServerTrust] fail to hook attemptServerTrustAuthentication !, ${e}`);
}
}

export {
attach,
}


57 changes: 57 additions & 0 deletions frida-agent/agent/HookAFSessionDelegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { log } from "./logger";
import { SDSwiftDataStorage } from "./SDSwiftDataStorage";
import * as SDNetDump from "./SDNetDump";
import * as SwiftRuntime from "./SwiftRuntime";

function enterFuncUrlSessionDidReceive(this: InvocationContext, args: InvocationArguments) {
// String is parsed by value
let ptr1 = args[0]; //NSURLSession
let ptr2 = args[1]; //NSURLSessionDataTask
let rangePtr = args[2];
let dataStoragePtr = args[3]; // Foundation.__DataStorage <-> Swift.Data


const session = new ObjC.Object(ptr1); //NSURLSession
const sessionDataTask = new ObjC.Object(ptr2); //NSURLSessionDataTask

const request = sessionDataTask.currentRequest(); //NSURLRequest
const dataLen = sessionDataTask.response().expectedContentLength()
//log(`1112-> ${request} > ${request.URL().absoluteString()}`)

let output:string = SDNetDump.dumpRequest(request);


//log(`rangePtr = ${ rangePtr }, dataStoragePtr=${dataStoragePtr}`);
//log(`dataLen=${dataLen}`);

let sdata = new SDSwiftDataStorage(dataStoragePtr);
//log(` ${ sdata.bytesPtr.readCString() }`);

let sdataStr = sdata.bytesPtr.readCString(dataLen); // parse the response data, default as string

output += "\n";
output += SDNetDump.intent + `>>> ${sdataStr}`;
log(`${output}`)

//----
// you can also use the following function to print Data.
//SwiftRuntime.swiftDataBridgeToObjectiveCByPtr(rangePtr, dataStoragePtr);

}

function attach() {
try {
//Alamofire.SessionDelegate.urlSession(_: __C.NSURLSession, dataTask: __C.NSURLSessionDataTask, didReceive: Foundation.Data) -> ()
const func_urlSessionDidReceive = Module.getExportByName(null, '$s9Alamofire15SessionDelegateC03urlB0_8dataTask10didReceiveySo12NSURLSessionC_So0i4DataF0C10Foundation0J0VtF');
log(`[HookAFSessionDelegate] func_urlSession ${func_urlSessionDidReceive}`);
Interceptor.attach(func_urlSessionDidReceive, { onEnter: enterFuncUrlSessionDidReceive});
} catch (e) {
log(`[HookAFSessionDelegate] fail to hook Alamofire.SessionDelegate !, ${e}`);
}

}

export {
attach,
}

52 changes: 52 additions & 0 deletions frida-agent/agent/HookDataTaskWithRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { log } from "./logger";
import * as Util from "./Util";
import * as SDNetDump from "./SDNetDump";



function enterFuncDataTaskWithRequest(this: InvocationContext, args: InvocationArguments) {
//const ptr = args[0];
const ptr2 = args[2];
const rqst = new ObjC.Object(ptr2); // rqst=NSMutableURLRequest
let rqstDesc = SDNetDump.dumpRequest(rqst);
// https://github.com/theart42/hack.lu/blob/master/IOS/Notes/02-HTTPS/00-https-hooks.md

let ptr3 = args[3];
if (ptr3.toInt32() <= 0) {
var str:string = rqstDesc;
str += "\n";
str += SDNetDump.intent + "(completionHandler empty)";
log(`${str}`)
return;
}

var completionHandler = new ObjC.Block(args[3]);
var origCompletionHandlerBlock = completionHandler.implementation;

completionHandler.implementation = function(data, response, error){
var str:string = rqstDesc;
str += "\n";
str += SDNetDump.dumpRspWith(data, response, error);
log(`${rqstDesc}`);
return origCompletionHandlerBlock(data, response, error);
}
}


function attach() {
const hookDataTask = Util.getOCMethodName('NSURLSession', '- dataTaskWithRequest:completionHandler:');
log(`hook NSURLSession ${hookDataTask.implementation}`);

Interceptor.attach(hookDataTask.implementation, {
onEnter : enterFuncDataTaskWithRequest,
});

}



export {
attach,
}


64 changes: 64 additions & 0 deletions frida-agent/agent/HookURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { log } from "./logger";
import * as Util from "./Util";
import {SDSwiftLargeString, SDSwiftSmallString} from "./SDSwiftString";

function isSmallString(abcdeeee: UInt64):boolean {
let abcd = abcdeeee.shr(4).and(0xF);
let isSmall = abcd.and(0x2).valueOf() > 0;
return isSmall;
}

function enterFuncDataTaskWithRequest(this: InvocationContext, args: InvocationArguments) {
// String is parsed by value
let ptr1 = args[0];
let ptr2 = args[1];

//log(`ptr ${ptr1}, ${ptr1.toString()}, ${ptr2.toString()} `);

let ptr1hex = '0x' + ptr1.toString(16);
let ptr2hex = '0x' + ptr2.toString(16);

let ptr1value = new UInt64(ptr1hex);
let ptr2value = new UInt64(ptr2hex);
let smallObject = ptr2value.and(0xFF); // the last byte

// first, try parse smallstring
if (isSmallString(smallObject)) {
let smallStr = new SDSwiftSmallString(ptr1hex, ptr2hex);
log(`[Foundation.URL.init] a=${smallStr.desc()}`)
if (Util.isPrintableString(smallStr.strValue)) { //TODO: filter special char
log(`[Foundation.URL.init] ${smallStr.desc()}`)
return;
}

}

// Large String
const countAndFlagsBitsPtr = args[0]; // 8 bytes(_countAndFlagsBits)
const objectPtr = args[1]; // 8 bytes(_object)

let countAndFlagsBits = new UInt64('0x' + countAndFlagsBitsPtr.toString(16))
let object = new UInt64('0x' + objectPtr.toString(16));
//log(`[Foundation.URL.init] arg ptr=${countAndFlagsBitsPtr} ,${objectPtr} -> ${objectPtr.toString(16)}`);
//log(`countAndFlagsBits=0x${countAndFlagsBits.toString(16) } , object=0x${object.toString(16) }`);

let largeStr = new SDSwiftLargeString(countAndFlagsBits, object);
log(`[Foundation.URL.init] ${largeStr.desc()}`)
}


function attach() {
try {
// s10Foundation3URLV6stringACSgSSh_tcfC ---> Foundation.URL.init(string: __shared Swift.String) -> Foundation.URL?
let func_Foundation_URL_init = Module.getExportByName(null, '$s10Foundation3URLV6stringACSgSSh_tcfC'); // remove prefix _
console.log('func_Foundation_URL_init', func_Foundation_URL_init)
Interceptor.attach(func_Foundation_URL_init, { onEnter: enterFuncDataTaskWithRequest });
} catch (e) {
log(`[HookURL] fail to hook swift Foundation.URL.init !, ${e}`);
}
}

export {
attach,
}

42 changes: 42 additions & 0 deletions frida-agent/agent/SDNetDump.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {colorfulStr, LogColor} from "./logger"

export const intent:string = " ";
export const newline:string = "\n";

function dumpRequest(rqst:ObjC.Object):string {
// rqst=NSMutableURLRequest
// https://developer.apple.com/documentation/foundation/nsmutableurlrequest?language=objc
let urlstr = rqst.URL().absoluteString();
let method = rqst.HTTPMethod().toString(); // NSString
let bodyData = rqst.HTTPBody();
let allHTTPHeaderFields = rqst.allHTTPHeaderFields().toString() as string;

var str:string = "";
let redMethod = colorfulStr(`[${method}]`, LogColor.Red);
str += `${redMethod} ${urlstr}`;
if (allHTTPHeaderFields && allHTTPHeaderFields.length > 0) {
str += newline;
str += intent + `[Header] ${allHTTPHeaderFields.replace(newline, "")}`;
}
// NSData to NSString
if (bodyData) {
var bodydataStr = ObjC.classes.NSString.alloc().initWithData_encoding_(bodyData, 4);
str += newline;
str += intent + "[Body] " + bodydataStr;
}
return str;
}

function dumpRspWith(data:any, response:any, error:any):string {
let rsp = new ObjC.Object(response);
var dataNSString = ObjC.classes.NSString.alloc().initWithData_encoding_(data, 4);

let str = intent + `>>> ${dataNSString}`;
return str;
}

export {
dumpRequest,
dumpRspWith,
}

34 changes: 34 additions & 0 deletions frida-agent/agent/SDSwiftDataStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

export class SDSwiftDataStorage {
// https://github.com/apple/swift-corelibs-foundation/blob/60fb6984c95b989bb25b3af26accd3a2dc2e2240/Sources/Foundation/Data.swift#L82
// Swift DataStorage is a class type.
// Foundation.__DataStorage
// https://github.com/TannerJin/Swift-MemoryLayout/blob/master/Swift/Class.swift
__dataStoragePtr: NativePointer;
bytesPtr: NativePointer
length: UInt64
capacity: UInt64

constructor(ptr: NativePointer) {
/*
----Swift Class Memory Layout----
var isa: objc_class* (8 bytes)
var refCount: UInt64 (8 bytes)
[properties]
*/
this.__dataStoragePtr = ptr;

let tmpptr = ptr.add(8 + 8);
this.bytesPtr = new NativePointer( tmpptr.readU64() );

tmpptr = tmpptr.add(8);
this.length = tmpptr.readU64();

tmpptr = tmpptr.add(8);
this.capacity = tmpptr.readU64();
}

desc():string {
return `<Swift.DataStorage, bytesPtr=${this.bytesPtr}, length='${this.length}'>`;
}
}
Loading

0 comments on commit ade89f1

Please sign in to comment.