I have originally written this post for blackbunny‘s blog.
Some definitions
As stated in Triton’s home page:
Triton is a dynamic binary analysis (DBA) framework. It provides internal components like a Dynamic Symbolic Execution (DSE) engine, a Taint Engine, AST representations of the x86 and the x86-64 instructions set semantics, SMT simplification passes, a SMT Solver Interface and, the last but not least, Python bindings. Based on these components, you are able to build program analysis tools, automate reverse engineering and perform software verification.
That might sound gibberish for some of you. So let’s cover these definitions first.
AST representation
An AST (Abstract Syntax Tree) is a tree representation of the structure of some code.
Example, the AST representing this instruction:
1 |
add eax, ebx |
Could be the following:
As explained here, all of Triton’s expressions are on the SSA form.
Meaning that each time a register/flag is modified, a new reference to the new value will be created.
1 2 |
Instruction: add rax, rdx Expression: ref!41 = (bvadd ((_ extract 63 0) ref!40) ((_ extract 63 0) ref!39)) |
In this example from Triton’s documentation, ref!41
is the new reference to rax and ref!40
is the reference to the previous one.
This allows to easily build “nested” expressions. (new expression making references to previously generated ones) and provides the ability to get the expression of a register/flag at each execution point. See here for more details.
AST can be useful in order to manipulate an instructions and also to translate them to another language such as SMTLib language in order to feed an SMT Solver.
SMT Solvers
SMT Solvers such as Z3 are used in many applications such as software verification, constraint solving, security etc…
What one needs to know, in order to understand this post, is that SMT Solvers are supposed to provide a solution verifying a set of constraints.
Let’s provide a few constraints for Z3 to solve:
1 2 3 4 5 6 7 8 |
from z3 import * x = Real('x') y = Real('y') s = Solver() s.add(x + 3 * y == 50, x + y == 20) print(s.check()) print(s.model()) |
Which will give the following output:
1 2 |
sat [y = 15, x = 5] |
In this simple example, two constraints are provided to z3:
1 2 |
x + 3 * y == 50 x + y == 20 |
And as you can see Z3 was able to find a solution satisfying these constraints.
Z3 is also able to simplify expressions, which can come in handy in case of obfuscated code.
Here’s an example:
1 2 3 4 5 6 |
from z3 import * x = Real('x') y = Real('y') s = Solver() print simplify(x + 3 * y - 4 * y - 3 * (y + x / 3)) |
Output:
1 |
-4*y |
Now imagine combining all these concepts in order to analyse and exploit binaries.
This is what Triton is able to do.
The Triton/Pintool (Python) API
Triton provides different DBA mechanisms (through symbolic execution or through tracing win Pin)
In this first post I’ll also be covering the Pin tracing feature coupled with a few symbolic execution features.
First things first, let’s dissect the instruction count script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/usr/bin/env python2 ## -*- coding: utf-8 -*- from pintool import * from triton import * count = 0 def mycb(inst): global count count += 1 def fini(): print count if __name__ == '__main__': setArchitecture(ARCH.X86_64) startAnalysisFromEntry() insertCall(mycb, INSERT_POINT.BEFORE) insertCall(fini, INSERT_POINT.FINI) runProgram() |
This tiny script is quite self explanatory.
One can find a full description off all triton/pintool bindings here but here is a short description of the ones we’ll be using:
setArchitecture:
Setup an architecture (ARCH.X86_64 or ARCH.X86)
startAnalysisFromEntry:
Request Pin to start tracing from the executable’s entrypoint.
Internaly this is just setting the startAnalysisFromEntry option (there is no c++ function associated with this function)
insertCall: this function provides means to add python callbacks as Pin’s routine, instruction and syscall callbacks (among other things).
runProgram: This function is a wrapper to PIN_StartProgram. This function basically execve’s to the program we want to analyse. It will never return.
With regards to above mentioned descriptions, one can quickly understand that the mycb callback will be called before each called instruction, and the number of instructions executed will be displayed by the fini callback at the end of the program’s execution.
Other important bindings we’ll use in this post are:
addCallback: Provides callbacks to Triton whenever it will need a concrete memory, a concrete register or when it will need to perform a symbolic simplification.
getPathConstraints:
This function holds some of the magic that will make the CrackMe’s resolution the easiest possible.
It provides a list of all path constraints that allowed the execution to reach the current execution point. Meaning the equations/symbolic expressions of each control flow instructions (je, jne, ja etc..) preceding the current execution point.
convertMemoryToSymbolicVariable: This function converts a memory address to a symbolic variable (the same kind of variables used with Z3) these variables will be the ones Z3 will provide models/possible solutions.
takeSnapshot/restoreSnapshot: these function allow us to take a whole machine context (cpu and memory) and restore it. This can help to simulate the restart of the execution (remember runProgram never returns).
The binary
The binary we are going to analyse is a crackme I wrote based on a challenge I’ve payed with recently.
With radare2 or IDA we can quickly see the mess before success (_095C_):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
<0x4005b6> if false -> f t <- if true .------' '--. | | | | [_06ea_] [_0703_] f t .-------' '-. | | | | [_070c_] [_0716_] f t .-------' '-. | | | | [_071f_] [_0729_] f t .-------' '-. | | | | [_073c_] [_0746_] f t .---' | | | | | [_0755_] | v | '----. '. | | | | [_0759_] f t .----'.' | | | | [_0774_] | v | '----. '. | | | | [_0778_] f t .----'.' | | | | [_0793_] | v | '----. '. | | | | [_0797_] f t .----'.' | | | | [_07af_] | v | '----. '. | | | | [_07b3_] f t .----'.' | | | | [_07d3_] | v | '----. '. | | | | [_07d7_] f t .----'.' | | | | [_07f7_] | v | '----. '. | | | | [_07fb_] f t .----'.' | | | | [_081b_] | v | '----. '. | | | | [_081f_] f t .----'.' | | | | [_0853_] | v | '----. '. | | | | [_0857_] f t .----'.' | | | | [_0878_] | v | '----. '. | | | | [_087c_] f t .----'.' | | | | [_08b9_] | v | '----. '. | | | | [_08bd_] f t .----'.' | | | | [_08fa_] | v | '--. .' | | | | [_08fe_] f t .-----' '. | | | | [_0920_] | t f | .------' '------. | | | | | | | [_095c_] [_0952_] |
A quick look at the binary shows us that each of the checks are only depending on argv[1]:
variables used for the checks are located in rbp-0x12, rbp-0x11, rbp-0x10, rbp-0xf, rbp-0xe, rbp-0xd
and each of these variables are only depending on argv[1]. Example with this snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
| | =------------------------------------= | 0x400759 | | movzx eax, byte [rbp - local_11h] | << depends only on argv[1] | xor al, byte [rbp - local_eh] | << depends only on argv[1] | movzx eax, al | | add eax, 0x11 | | sub eax, dword [rbp - local_8h] | << constant value 2 | movzx edx, byte [rbp - local_11h] | << depends only on argv[1] | add edx, 0xa | | cmp eax, edx | | je 0x400778 ;[h] | =------------------------------------= f t .-------' '-------------------. | | | | =-------------------------------= | | 0x400774 | | | add dword [rbp - local_4h], 1 | | =-------------------------------= | v | '-------. .-------------------' | | | | =------------------------------------= | 0x400778 | | movzx eax, byte [rbp - local_10h] | << depends only on argv[1] | xor al, byte [rbp - local_fh] | << depends only on argv[1] | movzx edx, al | | movzx eax, byte [rbp - local_10h] | << depends only on argv[1] | add edx, eax | | movzx eax, byte [rbp - local_fh] | << depends only on argv[1] | add eax, 2 | | cmp edx, eax | | je 0x400797 ;[i] | =------------------------------------= f t .-------' '-------------------. | | | | =-------------------------------= | | 0x400793 | | | add dword [rbp - local_4h], 1 | | =-------------------------------= | |
One can also see that the binary expects an 11 bytes password as only argv[1] offsets from 0 to 0xa are used. (see main’s first basic block)
We can also spot two kinds of checks: the ones leading to the end of the program’s execution (call to exit) and those which might lead to the incrementation of a variable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
| cmp eax, 0xa1 | | je 0x400746 ;[f] | =------------------------------------= f t .------------------------' '--. | | | | =-------------------------= =-----------------------------------= | 0x40073c | | 0x400746 | | mov edi, 1 | | movzx eax, byte [rbp - local_12h] | | call sym.imp.exit ;[c] | | xor al, byte [rbp - local_dh] | =-------------------------= | movzx eax, al | | cmp eax, dword [rbp - local_8h] | | je 0x400759 ;[g] | =-----------------------------------= f t .-------------' '-------------. | | | | =-------------------------------= | | 0x400755 | | | add dword [rbp - local_4h], 1 | | =-------------------------------= | v | '-------. .-------------------' | | |
We’ll definitely want to avoid prematurely stoping the program so the first kind of checks should not lead us to the exit calls.
Regarding the ohter checks (leading to the vriable incrementation) one can see cases where je is used and cases where jne is used.
But how to know which path to take ? the true or the false ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
| | =-----------------------------------= | 0x4007b3 | | mov rax, qword [rbp - local_30h] | | add rax, 8 | | mov rax, qword [rax] | | add rax, 4 | | movzx eax, byte [rax] | | movsx edx, al | | movzx eax, byte [rbp - local_dh] | | xor eax, edx | | cmp eax, 0x2f | --->> | je 0x4007d7 ;[k] | =-----------------------------------= f t .--------' '------------------. | | | | =-------------------------------= | | 0x4007d3 | | | add dword [rbp - local_4h], 1 | | =-------------------------------= | v | '--------. .------------------' | | | | =-----------------------------------= | 0x4007d7 | | movzx edx, byte [rbp - local_11h] | | movzx eax, byte [rbp - local_fh] | | sub edx, eax | | movzx eax, byte [rbp - local_dh] | | sub edx, eax | | mov eax, edx | | mov edx, eax | | shr edx, 0x1f | | add eax, edx | | sar eax, 1 | | cmp eax, dword [rbp - local_4h] | --->> | jne 0x4007fb ;[l] | =-----------------------------------= f t .--------' '------------------. | | |
The answer is simple: each check is a value comparison (“equality check”), meaning that one value would lead to a path, and the rest would lead to another path.
As the binary is verifying a password we can safely assume that the correct paths are the ones verifying the comparison (Z=1).
This means that all the following basic blocks need to be avoided:
0x40070c, 0x40071f, 0x40073c, 0x400755, 0x400774, 0x400793, 0x4007af, 0x4007d3, 0x40081b, 0x400853, 0x400878, 0x4008b9, 0x400952
And all the these other blocks need to be taken:
0x4007f7, 0x4008fa
These are all the infomation we need in order to solve the CrackMe. Yes, that’s all we need. There is no need to understand all the obscure calculations done in each block.
Time for some magic !
Here is the script I wrote in order to solve this challenge:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
#!/usr/bin/env python2 ## -*- coding: utf-8 -*- from triton import * from ast import * from pintool import * snapshot_done = False argv1 = 0 symVarConstraints = [] PASSWORD_SIZE = 11 def superAnd(constraints): pathConstraints_and = ast.equal(bvtrue(), bvtrue()) for i in range(len(constraints)): pathConstraints_and = ast.land(pathConstraints_and, constraints[i]) return (pathConstraints_and) def model2string(model): s = str() for i in range(PASSWORD_SIZE): try :s += chr(model[i].getValue()) except: pass return s def inject(address, data): for index, char in enumerate(data): setCurrentMemoryValue(address + index, ord(char)) def static_vars(**kwargs): def decorate(func): for k in kwargs: setattr(func, k, kwargs[k]) return func return decorate ENTRY = 0x4005b6 # basic blocks to avoid (reflecting password errors) avoid = [ 0x40070c, 0x40071f, 0x40073c, 0x400755, 0x400774, 0x400793, 0x4007af, 0x4007d3, 0x40081b, 0x400853, 0x400878, 0x4008b9, 0x400952] # basic blocks to take (reflecting password match) take = [0x4007f7, 0x4008fa] @static_vars(last_injected = "", numMandatoryPaths = 0) def before_symproc(instruction): pathConstraints = [] ins_addr = instruction.getAddress() if ins_addr in take: before_symproc.numMandatoryPaths = before_symproc.numMandatoryPaths + 1 if (ins_addr in avoid) or\ ((ins_addr == 0x4007fb) and (before_symproc.numMandatoryPaths != 1)) or\ ((ins_addr == 0x4008fe) and (before_symproc.numMandatoryPaths !=2) ): before_symproc.numMandatoryPaths = 0 print "[+] Wrong password" pco = getPathConstraints() for pc in pco: if pc.isMultipleBranches(): for branch in pc.getBranchConstraints(): # filter out branch constraints which lead to addresses we want # to avoid if branch['dstAddr'] in avoid: pathConstraints.append(lnot(branch['constraint'])) # force taking mandatory branches if branch['dstAddr'] in take: pathConstraints.append((branch['constraint'])) # Get a model that will not go through previous bad password basic # blocks and verifiying that all inputs are printable (see symVarConstraints # creation) full_constraint = superAnd(symVarConstraints + pathConstraints) model = getModel(ast.assert_(full_constraint)) string = model2string(model) before_symproc.last_injected = string print "[+] Possible solution : \"%s\" (%s)"\ % (string, string.encode('hex')) string += "\x00" print "[+] Injecting it, and restoring snapshot" inject(argv1, string) clearPathConstraints() restoreSnapshot() if ins_addr == 0x40095c: print "[+] Good password: ", before_symproc.last_injected disableSnapshot() clearPathConstraints() setCurrentRegisterValue(REG.RIP, 0x40096b) def before(inst): global snapshot_done global argv1 ins_addr = inst.getAddress() if inst.getAddress() == ENTRY: if not snapshot_done: # On 64 bits archs rdi id arc and rsi is argv rsi = getCurrentRegisterValue(REG.RSI) argv1 = getCurrentMemoryValue(rsi + 8, CPUSIZE.REG) offset = 0 # The binary is expecting a filname size of 11 bytes as the checks are # done on arv[1][0] to argv[1][10] # So we need to symbolize 9 bytes starting from &agrv[0][0] # Then we need to symbolize these 9 bytes while (offset < PASSWORD_SIZE): setCurrentMemoryValue(argv1 + offset, ord("_")) # symbolize current input (argv[1][offset]) symvar = convertMemoryToSymbolicVariable(MemoryAccess(argv1 + offset, CPUSIZE.BYTE)) # Inputs must be printable, so we add that constraint to each one # of the inputs: symVarConstraints.append(ast.bvuge(variable(symvar), bv(0x20, 8))) symVarConstraints.append(ast.bvule(variable(symvar), bv(0x7E, 8))) # Go get next input offset += 1 # End it with a null char for strlen to work properly setCurrentMemoryValue(argv1 + offset, ord('\0')) print "[+] Symbolized %d bytes of memory at 0x%x" % (offset, argv1) print "[+] Taking snapshot" takeSnapshot() snapshot_done = True def constantFolding(node): if node.isSymbolized(): return node return ast.bv(node.evaluate(), node.getBitvectorSize()) if __name__ == '__main__': # Define the architecture setArchitecture(ARCH.X86_64) enableSymbolicOptimization(OPTIMIZATION.ALIGNED_MEMORY, True) enableSymbolicOptimization(OPTIMIZATION.ONLY_ON_SYMBOLIZED, True) # Start the symbolic analysis from the 'main' function startAnalysisFromAddress(ENTRY) # Add callbacks addCallback(constantFolding, CALLBACK.SYMBOLIC_SIMPLIFICATION) insertCall(before, INSERT_POINT.BEFORE) insertCall(before_symproc, INSERT_POINT.BEFORE_SYMPROC) # Run the instrumentation - Never returns runProgram() |
Basically this script checks if we are executing wrong basic blocks, requests for a model in order to avoid these basic blocks, patches argv[1] and restarts the program until it finds the correct password validating all path constraints.
Let’s focus on the two most important callbacks:
before:
This function is only doing something at the entrypoint, it is symbolizing 11 bytes from argv[1]:
1 2 3 4 5 6 7 8 9 10 |
while (offset < PASSWORD_SIZE): setCurrentMemoryValue(argv1 + offset, ord("_")) # symbolize current input (argv[1][offset]) symvar = convertMemoryToSymbolicVariable(MemoryAccess(argv1 + offset, CPUSIZE.BYTE)) # Inputs must be printable, so we add that constraint to each one # of the inputs: symVarConstraints.append(ast.bvuge(variable(symvar), bv(0x20, 8))) symVarConstraints.append(ast.bvule(variable(symvar), bv(0x7E, 8))) # Go get next input offset += 1 |
It is also building a list (symVarConstraints) which will hold all symbolic variables constraints.
In our case, the constraints are simple: argv[1] must only contain printable characters (from 0x20 to 0x7E)
This is done with ast.bvuge(variable(symvar), bv(0x20, 8))
and ast.bvule(variable(symvar), bv(0x7E, 8))
ast.bvuge
being an unsigned comparison (Greater or Equal) of two bit vectors
and ast.bvule
being an unsigned comparison (Less than or Equal) of two bit vectors
so these two expressions may be translated to
- symvar need to be greater or equal to 0x20.
- symvar need to be less or equal to 0x7E.
before_symproc:
This function is the one doing the magic:
first it is checking wether the program is executing the avoid
addresses or if it missed the take
addresses:
1 2 3 4 5 6 |
if ins_addr in take: before_symproc.numMandatoryPaths = before_symproc.numMandatoryPaths + 1 if (ins_addr in avoid) or\ ((ins_addr == 0x4007fb) and (before_symproc.numMandatoryPaths != 1)) or\ ((ins_addr == 0x4008fe) and (before_symproc.numMandatoryPaths !=2) ): |
Then, in the case we actually are executing an address we want to avoid, or in the case we missed an address we actually wanted to take, it is building a pathConstraints list where all branches leading to avoid
addresses have there path contraint negated (with ast.lnot). And all branches leading to take
addresses are stored as is.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
before_symproc.numMandatoryPaths = 0 print "[+] Wrong password" pco = getPathConstraints() for pc in pco: if pc.isMultipleBranches(): for branch in pc.getBranchConstraints(): # filter out branch constraints which lead to addresses we want # to avoid if branch['dstAddr'] in avoid: pathConstraints.append(lnot(branch['constraint'])) # force taking mandatory branches if branch['dstAddr'] in take: pathConstraints.append((branch['constraint'])) |
Finally, still in the wrong cases, the script builds a full constraint (full_constraint
) and-ing (with superAnd) the symvar constraints (printable) and all the previous path constraints.
this full_constraints
is then provided to z3 through Triton’s getModel API in order to get a possible solution. Then, we just replace argv[1] with the new model, we clear all path constraints and restore the snapshot (restart the program):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Get a model that will not go through previous bad password basic # blocks and verifiying that all inputs are printable (see symVarConstraints # creation) full_constraint = superAnd(symVarConstraints + pathConstraints) model = getModel(ast.assert_(full_constraint)) string = model2string(model) before_symproc.last_injected = string print "[+] Possible solution : \"%s\" (%s)"\ % (string, string.encode('hex')) string += "\x00" print "[+] Injecting it, and restoring snapshot" inject(argv1, string) clearPathConstraints() restoreSnapshot() |
Here is what we get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
$ triton solve.py ./CrackMe _ [+] Symbolized 11 bytes of memory at 0x7ffe0a329f8b [+] Taking snapshot [+] Wrong password [+] Possible solution : "@@@ @@@@@ @" (4040402040404040402040) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "B*@!o@@A H;" (422a40216f40404120483b) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "@@'@_H.@r\O" (404027405f482e40725c4f) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "`@@`?1P@p`3" (604040603f315040706033) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "@B!Ag7!9|;:" (40422141673721397c3b3a) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "rxCRB0|/aq/" (7278435242307c2f61712f) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "afNQq$<Dpa"" (61664e5171243c44706122) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "5F C:$BBh; " (354620433a244242683b20) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "On)C"XdBygB" (4f6e294322586442796742) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "\@"[(0|V|5U" (5c40225b28307c567c3555) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "CD"t0 Rod=$" (434422743020526f643d24) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "@r!t0NRock!" (40722174304e526f636b21) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "Tr't0NRoik5" (54722774304e526f696b35) [+] Injecting it, and restoring snapshot [+] Wrong password [+] Possible solution : "Tr!t0NRock5" (54722174304e526f636b35) [+] Injecting it, and restoring snapshot [+] Good password: Tr!t0NRock5 |
Magic isn’t it ?
All related files can be found here. Enjoy !
Thanks for the guide. I found it very interesting and wanted to try to follow it.
Unfortunately I’m having a lot of trouble getting pintool to work. I first tried to install pin-3.0-76991-gcc-linux.tar.gz, but Triton wouldn’t build with it (some error about libpindwarf.so not found). Then installed the older version, pin-2.14-71313-gcc.4.4.7-linux.tar.gz, after which Triton builds and installs fine.
But when I do “triton solve.py CrackMe” it complains about “E: 4.4 is not a supported linux release”. Apparently my Linux kernel is too new. I guess I should set up an older VM to make this work.
Which distribution and version are you using?
I wonder if this could work with e.g. unicorn emulator instead of Intel’s proprietary tool.
It is not recommended but you can have Triton working on a 4.x kernel, see here

On another hand, I’m running Triton on a Debian 8.5 (kernel 3.16).
Regarding using unicorn for emulation, I guess this is possible, but needs to be implemented. Triton provides an interface to plug whatever tracer you need:
Thanks. I’ve reinstalled the environment on Ubuntu Trusty 14.04 (3.13.0-88-generic), 64 bit. No more complaints about kernel version. However, I get an error in solve.py now:
$ triton solve.py ./CrackMe
Traceback (most recent call last):
File “solve.py”, line 106, in before
setCurrentMemoryValue(argv1 + offset, ord(“_”))
TypeError: tracer::pintool::context::setCurrentMemoryValue(): Page not writable.
The Triton example (./triton ./src/examples/pin/ir.py /usr/bin/id) works, however.
You need to provide at least one parameter to the binary:
triton solve.py ./CrackMe _
I have temporarily updated the script in order to raise an exception if the argument is missing.
Woohoo, got it to work! Thanks.
Did have to change
from ast import *
tofrom triton.ast import *
but I guess that’s because I use the git version of triton.Hey,
I found a copy of this blog here http://blackbunny.io/solving-a-crack-me-with-triton-and-pin-a-k-a-the-lazy-way/. Is there any relation between you and them?
Yes, I originaly wrote this article for blackbunny’s blog.