Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ee26e1b

Browse files
author
Your Name
committedAug 30, 2024·
Initial version
0 parents  commit ee26e1b

File tree

12 files changed

+1391
-0
lines changed

12 files changed

+1391
-0
lines changed
 
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
name: Build on tag
3+
4+
on:
5+
push:
6+
tags:
7+
- '*'
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-20.04
12+
13+
steps:
14+
- uses: actions/checkout@v2
15+
16+
- uses: actions/setup-node@v2
17+
with:
18+
node-version: '15'
19+
registry-url: 'https://npm.pkg.github.com'
20+
scope: '@worksbutnottested'
21+
always-auth: true
22+
23+
- name: Download dependencies
24+
run: |
25+
npm install --ignore-scripts
26+
env:
27+
NODE_AUTH_TOKEN: ${{secrets.NPM_PUBLISH}}
28+
29+
- name: Install dependencies
30+
run: |
31+
npm rebuild && npm run prepare --if-present
32+
33+
- name: Build & publish
34+
run: |
35+
npm run build
36+
npm publish
37+
env:
38+
NODE_AUTH_TOKEN: ${{secrets.NPM_PUBLISH}}
39+
40+
- name: Create Release
41+
id: create_release
42+
uses: actions/create-release@v1
43+
env:
44+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
with:
46+
tag_name: ${{ github.ref }}
47+
release_name: Release ${{ github.ref }}
48+
draft: false
49+
prerelease: false

‎.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/node_modules/
2+
/dist/

‎README.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# stalker-coverage
2+
Stalker coverage is a small TypeScript module designed to be used with [FRIDA](https://frida.re/) to generate coverage information in [DynamoRio DRCOV](https://dynamorio.org/dynamorio_docs/page_drcov.html) format. This information can then be loaded into IDA using the [lighthouse](https://github.com/gaasedelen/lighthouse) plugin or Ghidra using the [Dragondance](https://github.com/0ffffffffh/dragondance) plugin.
3+
4+
# Example
5+
## Source Code
6+
The following C code will be used as our example:
7+
```c
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
11+
long square(long x)
12+
{
13+
return x * x;
14+
}
15+
16+
int main(int argc, char *argv[])
17+
{
18+
if (argc < 2) {
19+
printf("Give me an argument!\n");
20+
return 1;
21+
}
22+
23+
long x = strtol(argv[1], NULL, 0);
24+
long sq_x = square(x);
25+
printf("%ld squared is %ld\n", x, sq_x);
26+
return 0;
27+
}
28+
```
29+
We first compile the code as follows (note the use of the `-rdynamic` flag to allow FRIDA to more readily find the function):
30+
```bash
31+
$ gcc -rdynamic -o test test.c
32+
```
33+
## Project
34+
We can then start using the [frida-agent-example](https://github.com/oleavr/frida-agent-example) as a template project and install the `stalker-coverage` module:
35+
```bash
36+
$ npm install
37+
$ npm install --save stalker-coverage
38+
```
39+
## Using Stalker-Coverage
40+
We can then interact with this module using the following typescript code:
41+
```typescript
42+
import { Coverage } from "../node_modules/stalker-coverage/dist/coverage";
43+
/*
44+
* This sample replaces the 'main' function of the target application with one which starts
45+
* collecting coverage information, before calling the original 'main' function. Once the
46+
* original 'main' function returns, coverage collection is stopped. This coverage
47+
* information is written into a file which can then be directly loaded into IDA lighthouse
48+
* or Ghidra Dragondance.
49+
*/
50+
51+
/*
52+
* The address of the symbol 'main' which is to be used as the start and finish point to
53+
* collect coverage information.
54+
*/
55+
const mainAddress = DebugSymbol.fromName("main").address;
56+
57+
/**
58+
* The main module for the program for which we will collect coverage information (we will
59+
* not collect coverage information for any library dependencies).
60+
*/
61+
const mainModule = Process.enumerateModules()[0];
62+
63+
/*
64+
* A NativeFunction type for the 'main' function which will be used to call the original
65+
* function.
66+
*/
67+
const mainFunctionPointer = new NativeFunction(
68+
mainAddress,
69+
"int",
70+
["int", "pointer"],
71+
{ traps : "all"});
72+
73+
/*
74+
* A function to be used to replace the 'main' function. This function will start collecting
75+
* coverage information before calling the original 'main' function. Once this function
76+
* returns, the coverage collection will be stopped and flushed. Note that we cannot use
77+
* Interceptor.attach here, since this interferes with Stalker (which is used to provide the
78+
* coverage data).
79+
*/
80+
const mainReplacement = new NativeCallback(
81+
(argc, argv) => {
82+
const coverageFileName = `${mainModule.path}.dat`;
83+
const coverageFile = new File(coverageFileName, "wb+");
84+
85+
const coverage = Coverage.start({
86+
moduleFilter: (m) => Coverage.mainModule(m),
87+
onCoverage: (coverageData) => {
88+
coverageFile.write(coverageData);
89+
},
90+
threadFilter: (t) => Coverage.currentThread(t),
91+
});
92+
93+
const ret = mainFunctionPointer(argc, argv) as number;
94+
95+
coverage.stop();
96+
coverageFile.close();
97+
98+
return ret;
99+
},
100+
"int",
101+
["int", "pointer"]);
102+
103+
/*
104+
* Replace the 'main' function with our replacement function defined above.
105+
*/
106+
Interceptor.replace(mainAddress, mainReplacement);
107+
```
108+
We can build our project using the following command:
109+
```
110+
$ npm run build
111+
```
112+
## Running
113+
First we must download the FRIDA gadget from [here](https://github.com/frida/frida/releases) (be sure to rename it `frida-gadget.so`) and create a configuration file called `frida-gadget.config`
114+
```json
115+
{
116+
"interaction": {
117+
"type": "script",
118+
"path": "./_agent.js"
119+
}
120+
}
121+
```
122+
123+
Now we can run our application and collect the coverage information as follows:
124+
```
125+
$ LD_PRELOAD=./frida-gadget.so ./test 123
126+
123 squared is 15129
127+
```
128+
Coverage data should be stored along-side our test application in `test.dat`. This file can then de directly loaded into ghidra using the [Dragondance](https://github.com/0ffffffffh/dragondance) plugin.
129+
130+
## Screenshots
131+
We can then see the resulting coverage data inside of ghidra using the [Dragondance](https://github.com/0ffffffffh/dragondance) plugin.
132+
![Coverage1.png](https://github.com/WorksButNotTested/stalker-coverage/raw/master/img/Coverage1.png)
133+
![Coverage2.png](https://github.com/WorksButNotTested/stalker-coverage/raw/master/img/Coverage2.png)

‎img/Coverage1.png

596 KB
Loading

‎img/Coverage2.png

569 KB
Loading

‎lib/coverage.ts

Lines changed: 466 additions & 0 deletions
Large diffs are not rendered by default.

‎lib/options.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
interface ICoverageOptions {
2+
/**
3+
* Function to determine which modules should be included in the coverage information.
4+
*
5+
* @param module The module information
6+
* @returns True if the module is to be included in the coverage output, false otherwise.
7+
*/
8+
moduleFilter(module: Module): boolean;
9+
10+
/**
11+
* Callback which periodically receives raw DynamoRio DRCOV format coverage data. This data can be written directly
12+
* to file (or otherwise sent elsewhere for egress) and then loaded directly into IDA lighthouse or Ghidra
13+
* Dragondance.
14+
*
15+
* @param coverageData The raw coverage data
16+
*/
17+
onCoverage(coverageData: ArrayBuffer): void;
18+
19+
/**
20+
* Function to determine which threads should be included in the coverage information.
21+
*
22+
* @param module The thread information
23+
* @returns True if the thread is to be included in the coverage output, false otherwise.
24+
*/
25+
threadFilter(thread: ThreadDetails): boolean;
26+
}
27+
28+
export { ICoverageOptions as CoverageOptions };

‎lib/session.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
interface ICoverageSession {
2+
/**
3+
* Function to stop coverage
4+
*
5+
*/
6+
stop(): void;
7+
}
8+
9+
export { ICoverageSession as CoverageSession };

‎package-lock.json

Lines changed: 407 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "@worksbutnottested/stalker-coverage",
3+
"version": "2.0.0",
4+
"description": "Stalker coverage library",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"files": [
8+
"/dist/"
9+
],
10+
"repository": {
11+
"type": "git",
12+
"url": "git@github.com:worksbutnottested/stalker-coverage.git"
13+
},
14+
"publishConfig": {
15+
"registry": "https://npm.pkg.github.com/@worksbutnottested"
16+
},
17+
"scripts": {
18+
"prepare": "npm run build",
19+
"lint": "tslint -p tslint.json",
20+
"build": "tsc"
21+
},
22+
"devDependencies": {
23+
"@types/frida-gum": "^16.1.0",
24+
"@types/node": "^14.0.14",
25+
"typescript": "^4.0.2",
26+
"typescript-tslint-plugin": "^0.5.5",
27+
"tslint": "^6.1.3"
28+
}
29+
}

‎tsconfig.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"compilerOptions": {
3+
"target": "esnext",
4+
"lib": ["esnext"],
5+
"strict": true,
6+
"module": "commonjs",
7+
"esModuleInterop": true,
8+
"declaration": true,
9+
"outDir": "./dist",
10+
"plugins": [
11+
{
12+
"name": "typescript-tslint-plugin"
13+
}
14+
]
15+
},
16+
"include": [
17+
"lib/**/*"
18+
]
19+
}

‎tslint.json

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
{
2+
"rules": {
3+
"adjacent-overload-signatures": true,
4+
"ban-types": {
5+
"options": [
6+
["Object", "Avoid using the `Object` type. Did you mean `object`?"],
7+
[
8+
"Function",
9+
"Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
10+
],
11+
["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"],
12+
["Number", "Avoid using the `Number` type. Did you mean `number`?"],
13+
["String", "Avoid using the `String` type. Did you mean `string`?"],
14+
["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"]
15+
]
16+
},
17+
"ban-ts-ignore": true,
18+
"member-access": {
19+
"options": ["check-accessor", "check-constructor", "check-parameter-property"]
20+
},
21+
"member-ordering": {
22+
"options": {
23+
"order": "statics-first",
24+
"alphabetize": true
25+
}
26+
},
27+
"no-any": true,
28+
"no-empty-interface": true,
29+
"no-for-in": true,
30+
"no-import-side-effect": true,
31+
"no-inferrable-types": { "options": ["ignore-params"] },
32+
"no-internal-module": true,
33+
"no-magic-numbers": true,
34+
"no-namespace": true,
35+
"no-non-null-assertion": true,
36+
"no-reference": true,
37+
"no-restricted-globals": true,
38+
"no-this-assignment": true,
39+
"no-var-requires": true,
40+
"only-arrow-functions": true,
41+
"prefer-for-of": true,
42+
"prefer-readonly": true,
43+
"promise-function-async": true,
44+
"typedef": {
45+
"options": [
46+
"call-signature",
47+
"parameter",
48+
"property-declaration"
49+
]
50+
},
51+
"typedef-whitespace": {
52+
"options": [
53+
{
54+
"call-signature": "nospace",
55+
"index-signature": "nospace",
56+
"parameter": "nospace",
57+
"property-declaration": "nospace",
58+
"variable-declaration": "nospace"
59+
},
60+
{
61+
"call-signature": "onespace",
62+
"index-signature": "onespace",
63+
"parameter": "onespace",
64+
"property-declaration": "onespace",
65+
"variable-declaration": "onespace"
66+
}
67+
]
68+
},
69+
"unified-signatures": true,
70+
"await-promise": true,
71+
"ban-comma-operator": true,
72+
"curly": true,
73+
"forin": true,
74+
"function-constructor": true,
75+
"label-position": true,
76+
"no-arg": true,
77+
"no-async-without-await": true,
78+
"no-bitwise": true,
79+
"no-conditional-assignment": true,
80+
"no-console": true,
81+
"no-construct": true,
82+
"no-debugger": true,
83+
"no-duplicate-super": true,
84+
"no-duplicate-switch-case": true,
85+
"no-duplicate-variable": { "options": ["check-parameters"] },
86+
"no-dynamic-delete": true,
87+
"no-empty": true,
88+
"no-eval": true,
89+
"no-floating-promises": true,
90+
"no-for-in-array": true,
91+
"no-implicit-dependencies": true,
92+
"no-inferred-empty-object-type": true,
93+
"no-invalid-template-strings": true,
94+
"no-misused-new": true,
95+
"no-null-keyword": true,
96+
"no-null-undefined-union": true,
97+
"no-object-literal-type-assertion": true,
98+
"no-promise-as-boolean": true,
99+
"no-return-await": true,
100+
"no-shadowed-variable": true,
101+
"no-string-literal": true,
102+
"no-string-throw": true,
103+
"no-sparse-arrays": true,
104+
"no-submodule-imports": true,
105+
"no-tautology-expression": true,
106+
"no-unbound-method": true,
107+
"no-unnecessary-class": { "options": ["allow-empty-class"] },
108+
"no-unsafe-any": true,
109+
"no-unsafe-finally": true,
110+
"no-unused-expression": true,
111+
"no-var-keyword": true,
112+
"no-void-expression": true,
113+
"prefer-conditional-expression": true,
114+
"radix": true,
115+
"restrict-plus-operands": true,
116+
"static-this": true,
117+
"strict-boolean-expressions": true,
118+
"strict-string-expressions": true,
119+
"strict-comparisons": true,
120+
"strict-type-predicates": true,
121+
"switch-default": true,
122+
"triple-equals": true,
123+
"unnecessary-constructor": true,
124+
"use-default-type-parameter": true,
125+
"use-isnan": true,
126+
"cyclomatic-complexity": true,
127+
"eofline": true,
128+
"indent": { "options": ["spaces"] },
129+
"invalid-void": true,
130+
"linebreak-style": { "options": "LF" },
131+
"max-classes-per-file": { "options": 1 },
132+
"max-file-line-count": { "options": 1000 },
133+
"max-line-length": {
134+
"options": { "limit": 120 }
135+
},
136+
"no-default-export": true,
137+
"no-default-import": true,
138+
"no-duplicate-imports": true,
139+
"no-irregular-whitespace": true,
140+
"no-mergeable-namespace": true,
141+
"no-parameter-reassignment": true,
142+
"no-require-imports": true,
143+
"no-trailing-whitespace": true,
144+
"object-literal-sort-keys": true,
145+
"prefer-const": true,
146+
"trailing-comma": {
147+
"options": {
148+
"esSpecCompliant": true,
149+
"multiline": "always",
150+
"singleline": "never"
151+
}
152+
},
153+
"align": {
154+
"options": ["parameters", "arguments", "statements", "elements", "members"]
155+
},
156+
"array-type": { "options": "array-simple" },
157+
"arrow-parens": true,
158+
"arrow-return-shorthand": { "options": "multiline" },
159+
"binary-expression-operand-order": true,
160+
"callable-types": true,
161+
"class-name": true,
162+
"comment-format": { "options": ["check-space", "check-uppercase"] },
163+
"comment-type": { "options": ["singleline", "multiline", "doc", "directive"] },
164+
"completed-docs": true,
165+
"deprecation": true,
166+
"encoding": true,
167+
"file-name-casing": { "options": "camel-case" },
168+
"import-spacing": true,
169+
"increment-decrement": true,
170+
"interface-name": true,
171+
"interface-over-type-literal": true,
172+
"jsdoc-format": { "options": "check-multiline-start" },
173+
"match-default-export-name": true,
174+
"new-parens": true,
175+
"newline-before-return": true,
176+
"newline-per-chained-call": true,
177+
"no-angle-bracket-type-assertion": true,
178+
"no-boolean-literal-compare": true,
179+
"no-consecutive-blank-lines": true,
180+
"no-parameter-properties": true,
181+
"no-redundant-jsdoc": true,
182+
"no-reference-import": true,
183+
"no-unnecessary-callback-wrapper": true,
184+
"no-unnecessary-initializer": true,
185+
"no-unnecessary-qualifier": true,
186+
"no-unnecessary-type-assertion": true,
187+
"number-literal-format": true,
188+
"object-literal-key-quotes": { "options": "consistent-as-needed" },
189+
"object-literal-shorthand": true,
190+
"one-line": {
191+
"options": [
192+
"check-catch",
193+
"check-else",
194+
"check-finally",
195+
"check-open-brace",
196+
"check-whitespace"
197+
]
198+
},
199+
"one-variable-per-declaration": true,
200+
"ordered-imports": {
201+
"options": {
202+
"grouped-imports": true,
203+
"import-sources-order": "case-insensitive",
204+
"named-imports-order": "case-insensitive",
205+
"module-source-path": "full"
206+
}
207+
},
208+
"prefer-function-over-method": true,
209+
"prefer-method-signature": true,
210+
"prefer-object-spread": true,
211+
"prefer-switch": true,
212+
"prefer-template": true,
213+
"prefer-while": true,
214+
"quotemark": {
215+
"options": ["double", "avoid-escape", "avoid-template"]
216+
},
217+
"return-undefined": true,
218+
"semicolon": { "options": ["always"] },
219+
"space-before-function-paren": {
220+
"options": {
221+
"anonymous": "never",
222+
"asyncArrow": "always",
223+
"constructor": "never",
224+
"method": "never",
225+
"named": "never"
226+
}
227+
},
228+
"space-within-parens": { "options": 0 },
229+
"switch-final-break": true,
230+
"type-literal-delimiter": true,
231+
"unnecessary-bind": true,
232+
"unnecessary-else": true,
233+
"variable-name": { "options": ["ban-keywords", "check-format", "require-const-for-all-caps"] },
234+
"whitespace": {
235+
"options": [
236+
"check-branch",
237+
"check-decl",
238+
"check-operator",
239+
"check-module",
240+
"check-separator",
241+
"check-type",
242+
"check-typecast",
243+
"check-preblock",
244+
"check-type-operator",
245+
"check-rest-spread"
246+
]
247+
}
248+
}
249+
}

0 commit comments

Comments
 (0)
Please sign in to comment.