Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: R2Qiling with refactored memory and de-flatten plugin #1244

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions examples/extensions/r2/deflat_r2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

import sys

sys.path.append('..')

from qiling.const import QL_VERBOSE
from qiling.extensions.r2 import R2Qiling as Qiling



if __name__ == "__main__":
# a program obfuscated by OLLVM control flow graph flatten, which should print 4 when argv[1] is 1
# see source code at examples/src/linux/fla_test.c
ql = Qiling(['rootfs/x86_linux/bin/test_fla_argv', '1'], 'rootfs/x86_linux', verbose=QL_VERBOSE.DEFAULT)
ctx = ql.save()
r2 = ql.r2
# now we can use r2 parsed symbol name instead of address to get function
fcn = r2.get_fcn('target_function')
# de-flatten the target function, ql code will be patched
r2.deflat(fcn)
# run the de-flattened program, it should print 4 as expected
ql.run()
# get a r2-like interactive shell to reverse engineering target_function
r2.shell('target_function')
# run `pdf` in r2 shell to print disassembly of target_function
# we should see many patched NOP instructions

print('restore the original program')
ql.restore(ctx)
r2 = ql.r2
# the program is still obfuscated
r2.shell('target_function')
8 changes: 4 additions & 4 deletions examples/extensions/r2/hello_r2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,18 @@
import sys
sys.path.append('..')

from qiling import Qiling
from qiling.const import QL_VERBOSE
from qiling.extensions.r2 import R2
from qiling.extensions.r2 import R2Qiling as Qiling


def func(ql: Qiling, *args, **kwargs):
ql.os.stdout.write(b"=====hooked main=====!\n")
return

def my_sandbox(path, rootfs):
ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DISASM)
ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEFAULT)
# QL_VERBOSE.DISASM will be monkey-patched when r2 is available
r2 = R2(ql)
r2 = ql.r2

# search bytes sequence using ql.mem.search
addrs = ql.mem.search(b'llo worl') # return all matching results
Expand All @@ -35,6 +34,7 @@ def my_sandbox(path, rootfs):
ql.hook_address(func, r2.functions['main'].offset)
# enable trace powered by r2 symsmap
# r2.enable_trace()
r2.bt(0x401906)
ql.run()

if __name__ == "__main__":
elicn marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
37 changes: 37 additions & 0 deletions examples/src/linux/fla_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* Build Instructions:
git clone [email protected]:heroims/obfuscator.git -b llvm-9.0
mkdir build-ollvm && cd build-ollvm
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -G Ninja ../obfuscator/
ninja
./bin/clang -m32 -mllvm -fla fla_test.c -o test_fla_argv
*/
#include <stdio.h>
#include <stdlib.h>

unsigned int target_function(unsigned int n)
{
unsigned int mod = n % 4;
unsigned int result = 0;

if (mod == 0) result = (n | 0xBAAAD0BF) * (2 ^ n);

else if (mod == 1) result = (n & 0xBAAAD0BF) * (3 + n);

else if (mod == 2) result = (n ^ 0xBAAAD0BF) * (4 | n);

else result = (n + 0xBAAAD0BF) * (5 & n);

return result;
}

int main(int argc, char **argv) {
int n;
if (argc < 2) {
n = 0;
} else {
n = atoi(argv[1]);
}
int val = target_function(n);
printf("%d\n", val);
return 0;
}
1 change: 1 addition & 0 deletions qiling/extensions/r2/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .r2 import R2
from .r2q import R2Qiling
75 changes: 75 additions & 0 deletions qiling/extensions/r2/callstack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from dataclasses import dataclass
from typing import Iterator, Optional


@dataclass
class CallStack:
"""Linked Frames
See https://github.com/angr/angr/blob/master/angr/state_plugins/callstack.py
"""
addr: int
sp: int
bp: int
name: str = None # 'name + offset'
next: Optional['CallStack'] = None

def __iter__(self) -> Iterator['CallStack']:
"""
Iterate through the callstack, from top to bottom
(most recent first).
"""
i = self
while i is not None:
yield i
i = i.next

def __getitem__(self, k):
"""
Returns the CallStack at index k, indexing from the top of the stack.
"""
orig_k = k
for i in self:
if k == 0:
return i
k -= 1
raise IndexError(orig_k)

def __len__(self):
"""
Get how many frames there are in the current call stack.

:return: Number of frames
:rtype: int
"""

o = 0
for _ in self:
o += 1
return o

def __repr__(self):
"""
Get a string representation.

:return: A printable representation of the CallStack object
:rtype: str
"""
return "<CallStack (depth %d)>" % len(self)

def __str__(self):
return "Backtrace:\n" + "\n".join(f"Frame {i}: [{f.name}] {f.addr:#x} sp={f.sp:#x}, bp={f.bp:#x}" for i, f in enumerate(self))

def __eq__(self, other):
if not isinstance(other, CallStack):
return False

if self.addr != other.addr or self.sp != other.sp or self.bp != other.bp:
return False

return self.next == other.next

def __ne__(self, other):
return not (self == other)

def __hash__(self):
return hash(tuple((c.addr, c.sp, c.bp) for c in self))
Loading