From 7fcf189c46bc74743cf5c732c64337cf73b5015c Mon Sep 17 00:00:00 2001 From: "Landen M. Martin" Date: Fri, 21 Aug 2026 13:38:01 -0500 Subject: [PATCH 1/2] Add initial implementation of pynasm.py Implement a Python to NASM compiler for 64-bit architecture. --- pynasm.py => app.py | 401 ++++++++++++++++++++++++-------------------- 1 file changed, 222 insertions(+), 179 deletions(-) rename pynasm.py => app.py (59%) diff --git a/pynasm.py b/app.py similarity index 59% rename from pynasm.py rename to app.py index 9eea7c1..7f59974 100644 --- a/pynasm.py +++ b/app.py @@ -1,35 +1,49 @@ ''' - Python3 to nasm 64bits - @sha0coder +File: app.py +Author: @sha0coder, @Landen-Martin +Description: Python3 to nasm 64bits - Disclaimer!! dont use this to create malicious payloads. +Disclaimer!!! Never use this to create malicious payloads! ''' - import sys import ast nasm = [] lbl = 1 -regs64 = ['rax','rbx','rcx','rdx','rsi','rdi', 'rbp','rsp','r8','r9','r10','r11','r12','r13','r14','r15'] +regs64 = ['rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi', 'rbp', 'rsp', + 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15'] + +save_regs = ['rbx', 'rcx', 'rdx', 'rsi', 'rdi', + 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15'] +locals_map = {} +local_slot = 0 -def unimplemented(msg): # rust style +def unimplemented(msg): # rust style print(f'UNIMPLEMENTED: {msg}') sys.exit(1) +def find_locals(node): + """grab all assigned names that arent regs""" + found = [] + for n in ast.walk(node): + if isinstance(n, ast.Assign): + for t in n.targets: + if isinstance(t, ast.Name) and t.id not in regs64 and t.id not in found: + found.append(t.id) + return found class visit_functions(ast.NodeVisitor): def __init__(self): pass - def visit_Import(self, node): global nasm, lbl for alias in node.names: try: - code = open(alias.name+'.py','r').read() + code = open(alias.name + '.py', 'r').read() except: unimplemented('only import .py in base folder, compile from py''s folder') tree = ast.parse(code) @@ -38,30 +52,43 @@ def visit_Import(self, node): self.generic_visit(node) - def visit_FunctionDef(self, node): - global nasm + global nasm, locals_map, local_slot nasm.append(f'\n{node.name}:') + + # x64 dont have pushad, do it manualy + for r in save_regs: + nasm.append(f' push {r}') + nasm.append(f' push rbp') nasm.append(f' mov rbp, rsp') nasm.append(f' sub rsp, 32') - i = 16 + + locals_map = {} + local_slot = 0 + + for lv in find_locals(node): + local_slot += 8 + locals_map[lv] = local_slot + + if local_slot > 0: + nasm.append(f' sub rsp, {local_slot}') + + i = 16 for arg in node.args.args: nasm.append(f' mov {arg.arg}, [rbp+{i}]') i += 8 self.generic_visit(node) - def visit_Pass(self, node): global nasm nasm.append(' nop') self.generic_visit(node) - def visit_Call(self, node): global nasm - - if isinstance(node.func, ast.Name): # built-ins + + if isinstance(node.func, ast.Name): # built-ins if node.func.id == 'range': return elif node.func.id == 'push': @@ -81,7 +108,7 @@ def visit_Call(self, node): l = len(node.args) if l >= 1: arg = node.args[0] - if isinstance(arg, ast.Constant): # constants + if isinstance(arg, ast.Constant): # constants arg = arg.value elif isinstance(arg, ast.Name): # vars arg = arg.id @@ -89,7 +116,7 @@ def visit_Call(self, node): nasm.append(f' mov rcx, {arg}') if l >= 2: arg = node.args[1] - if isinstance(arg, ast.Constant): # constants + if isinstance(arg, ast.Constant): # constants arg = arg.value elif isinstance(arg, ast.Name): # vars arg = arg.id @@ -97,7 +124,7 @@ def visit_Call(self, node): nasm.append(f' mov rdx, {arg}') if l >= 3: arg = node.args[2] - if isinstance(arg, ast.Constant): # constants + if isinstance(arg, ast.Constant): # constants arg = arg.value elif isinstance(arg, ast.Name): # vars arg = arg.id @@ -105,7 +132,7 @@ def visit_Call(self, node): nasm.append(f' mov r8, {arg}') if l >= 4: arg = node.args[3] - if isinstance(arg, ast.Constant): # constants + if isinstance(arg, ast.Constant): # constants arg = arg.value elif isinstance(arg, ast.Name): # vars arg = arg.id @@ -113,7 +140,7 @@ def visit_Call(self, node): nasm.append(f' mov r9, {arg}') if l > 4: for arg in reversed(node.args[4:]): - if isinstance(arg, ast.Constant): # constants + if isinstance(arg, ast.Constant): # constants arg = arg.value elif isinstance(arg, ast.Name): # vars arg = arg.id @@ -121,10 +148,8 @@ def visit_Call(self, node): nasm.append(f' call {node.func.id}') return - - for arg in reversed(node.args): - if isinstance(arg, ast.Constant): # constants + if isinstance(arg, ast.Constant): # constants nasm.append(f' push {arg.value}') elif isinstance(arg, ast.Name): # vars nasm.append(f' push {arg.id}') @@ -134,7 +159,6 @@ def visit_Call(self, node): if fname.startswith('api_'): nasm.append(f' apicall {fname}') else: - nasm.append(f' call {fname}') elif isinstance(node.func, ast.Attribute): @@ -147,7 +171,6 @@ def visit_Call(self, node): self.generic_visit(node) - ''' def visit_arguments(self, node): global nasm @@ -165,10 +188,14 @@ def visit_Return(self, node): if node.value.id != 'rax': nasm.append(f' mov rax, {node.value.id}') else: - unimplemented('return '+node.value) + unimplemented('return ' + node.value) nasm.append(' leave') - nasm.append(' ret') + # popad manual, rax stays for return value + for r in reversed(save_regs): + nasm.append(f' pop {r}') + + nasm.append(' ret') def visit_While(self, node): global nasm, lbl @@ -183,7 +210,7 @@ def visit_While(self, node): if str(node.test.value) == 'True': nasm.append(f' jmp {lbl_while}') else: - unimplemented('value'+str(node.test.value)) + unimplemented('value' + str(node.test.value)) elif isinstance(node.test, ast.Compare): @@ -215,156 +242,170 @@ def visit_While(self, node): elif isinstance(ops, ast.NotEq): nasm.append(f' jne {lbl_while}') else: - unimplemented("while operator "+str(node.test.ops)) - - else: - unimplemented('while variant '+str(node.test)) - + unimplemented("while operator " + str(node.test.ops)) + else: + unimplemented('while variant ' + str(node.test)) def visit_For(self, node): global nasm, lbl if isinstance(node.target, ast.Name): reg = node.target.id if isinstance(node.iter, ast.Call) and \ - isinstance(node.iter.func, ast.Name) and \ - node.iter.func.id == 'range': - args = node.iter.args - if len(args) == 1: - # range(stop) - range_min = 0 - if isinstance(args[0], ast.Constant): - range_max = args[0].value - else: - range_max = args[0].id - range_step = 1 - elif len(args) == 2: - # range(start, stop) - if isinstance(args[0], ast.Constant): - range_min = args[0].value - else: - range_min = args[0].id - if isinstance(args[1], ast.Constant): - range_max = args[1].value - else: - range_max = args[1].id - range_step = 1 - elif len(args) == 3: - # range(start, stop, step) - if isinstance(args[0], ast.Constant): - range_min = args[0].value - else: - range_min = args[0].id - if isinstance(args[1], ast.Constant): - range_max = args[1].value - else: - range_max = args[1].id - if isinstance(args[2], ast.Constant): - range_step= args[2].value - else: - range_step = args[2].id - nasm.append(f' mov {reg}, {range_min}') - for_lbl = f'for{lbl}' - lbl += 1 - nasm.append(f'{for_lbl}:') - self.generic_visit(node) - nasm.append(f' add {reg}, {range_step}') - nasm.append(f' cmp {reg}, {range_max}') - nasm.append(f' jle {for_lbl}') + isinstance(node.iter.func, ast.Name) and \ + node.iter.func.id == 'range': + + args = node.iter.args + if len(args) == 1: + # range(stop) + range_min = 0 + if isinstance(args[0], ast.Constant): + range_max = args[0].value + else: + range_max = args[0].id + range_step = 1 + elif len(args) == 2: + # range(start, stop) + if isinstance(args[0], ast.Constant): + range_min = args[0].value + else: + range_min = args[0].id + if isinstance(args[1], ast.Constant): + range_max = args[1].value + else: + range_max = args[1].id + range_step = 1 + elif len(args) == 3: + # range(start, stop, step) + if isinstance(args[0], ast.Constant): + range_min = args[0].value + else: + range_min = args[0].id + if isinstance(args[1], ast.Constant): + range_max = args[1].value + else: + range_max = args[1].id + if isinstance(args[2], ast.Constant): + range_step = args[2].value + else: + range_step = args[2].id + nasm.append(f' mov {reg}, {range_min}') + for_lbl = f'for{lbl}' + lbl += 1 + nasm.append(f'{for_lbl}:') + self.generic_visit(node) + nasm.append(f' add {reg}, {range_step}') + nasm.append(f' cmp {reg}, {range_max}') + nasm.append(f' jle {for_lbl}') else: unimplemented('only for range is suported') else: unimplemented('complex for') - def visit_If(self, node): global nasm, lbl #nasm.append( ast.unparse(node.test) ) if isinstance(node.test, ast.Compare) and \ - len(node.test.ops) == 1 and \ - len(node.test.comparators) == 1: - left = node.test.left - op = node.test.ops[0] - right = node.test.comparators[0] - - cmpsb = False - if isinstance(left, ast.Call) and isinstance(right, ast.Call): - if left.func.id == 'str' and right.func.id == 'str': - left = left.args[0].id - right = right.args[0].id - cmpsb = True - else: - unimplemented('weird if + call') + len(node.test.ops) == 1 and \ + len(node.test.comparators) == 1: + + left = node.test.left + op = node.test.ops[0] + right = node.test.comparators[0] + + cmpsb = False + if isinstance(left, ast.Call) and isinstance(right, ast.Call): + if left.func.id == 'str' and right.func.id == 'str': + left = left.args[0].id + right = right.args[0].id + cmpsb = True + else: + unimplemented('weird if + call') - if cmpsb: - nasm.append(f' repe cmpsb') - else: - if isinstance(left, ast.Constant): - left = left.value - else: - left = left.id - if isinstance(right, ast.Constant): - right = right.value - else: - right = right.id - nasm.append(f' mov rsi, {str(left)}') - nasm.append(f' mov rdi, {str(right)}') - nasm.append(' cmp rsi, rdi') - - label_if = f'if{lbl}' - lbl += 1 - if isinstance(op, ast.Gt): - nasm.append(f' jg {label_if}') - elif isinstance(op, ast.Lt): - nasm.append(f' jl {label_if}') - elif isinstance(op, ast.LtE): - nasm.append(f' jle {label_if}') - elif isinstance(op, ast.GtE): - nasm.append(f' jge {label_if}') - elif isinstance(op, ast.Eq): - nasm.append(f' je {label_if}') - elif isinstance(op, ast.NotEq): - nasm.append(f' jne {label_if}') - else: - unimplemented("if operator "+str(node.test.op)) + if cmpsb: + nasm.append(f' repe cmpsb') + else: + if isinstance(left, ast.Constant): + left = left.value + else: + left = left.id + if isinstance(right, ast.Constant): + right = right.value + else: + right = right.id + nasm.append(f' mov rsi, {str(left)}') + nasm.append(f' mov rdi, {str(right)}') + nasm.append(' cmp rsi, rdi') + + label_if = f'if{lbl}' + lbl += 1 + if isinstance(op, ast.Gt): + nasm.append(f' jg {label_if}') + elif isinstance(op, ast.Lt): + nasm.append(f' jl {label_if}') + elif isinstance(op, ast.LtE): + nasm.append(f' jle {label_if}') + elif isinstance(op, ast.GtE): + nasm.append(f' jge {label_if}') + elif isinstance(op, ast.Eq): + nasm.append(f' je {label_if}') + elif isinstance(op, ast.NotEq): + nasm.append(f' jne {label_if}') + else: + unimplemented("if operator " + str(node.test.op)) + + if node.orelse: + label_else = f'else{lbl}' + lbl += 1 + label_endif = f'endif{lbl}' + lbl += 1 + nasm.append(f' jmp {label_else}') + nasm.append(f'\n{label_if}:') + for b in node.body: + self.visit(b) + nasm.append(f' jmp {label_endif}') + nasm.append(f'\n{label_else}:') + for e in node.orelse: + self.visit(e) + nasm.append(f'\n{label_endif}:') + return + label_noif = f'endif{lbl}' + lbl += 1 + + nasm.append(f' jmp {label_noif}') + nasm.append(f'\n{label_if}:') + for b in node.body: + self.visit(b) + nasm.append(f'\n{label_noif}:') - if node.orelse: - label_else = f'else{lbl}' - lbl += 1 - label_endif = f'endif{lbl}' - lbl += 1 - nasm.append(f' jmp {label_else}') - nasm.append(f'\n{label_if}:') - for b in node.body: - self.visit(b) - nasm.append(f' jmp {label_endif}') - nasm.append(f'\n{label_else}:') - for e in node.orelse: - self.visit(e) - nasm.append(f'\n{label_endif}:') - return - - label_noif = f'endif{lbl}' - lbl += 1 - - nasm.append(f' jmp {label_noif}') - nasm.append(f'\n{label_if}:') - self.generic_visit(node) - nasm.append(f'\n{label_noif}:') - - else: unimplemented('complex if') - def visit_Assign(self, node): global nasm, lbl for target in node.targets: + if target.id in locals_map: + slot = locals_map[target.id] + if isinstance(node.value, ast.Constant): + nasm.append(f' mov rax, {node.value.value}') + nasm.append(f' mov [rbp-{slot}], rax') + elif isinstance(node.value, ast.Name): + if node.value.id in locals_map: + nasm.append(f' mov rax, [rbp-{locals_map[node.value.id]}]') + nasm.append(f' mov [rbp-{slot}], rax') + else: + nasm.append(f' mov [rbp-{slot}], {node.value.id}') + elif isinstance(node.value, ast.Call): + self.generic_visit(node) + nasm.append(f' mov [rbp-{slot}], rax') + return + continue + if isinstance(node.value, ast.Constant): if node.value.value == 0: nasm.append(f' xor {target.id}, {target.id}') @@ -378,6 +419,27 @@ def visit_Assign(self, node): nasm.append(f'lbl{lbl}:') nasm.append(f' pop {target.id}') lbl += 1 + elif isinstance(node.value, ast.List): + # put bytes on stack, reg points to first byte + data = b'' + for e in node.value.elts: + if isinstance(e, ast.Constant): + data += bytes([e.value & 0xff]) + else: + unimplemented('array elem') + + while len(data) % 8: + data += b'\x00' + + # push qwords in reverse so first bytes end up at rsp + for i in range(len(data) - 8, -8, -8): + chunk = data[i:i+8] + val = int.from_bytes(chunk, 'little') + nasm.append(f' mov rax, 0x{val:016x}') + nasm.append(f' push rax') + + nasm.append(f' mov {target.id}, rsp') + elif isinstance(node.value, ast.Name): if node.value.id == 'PEB': nasm.append(f' xor rdi, rdi') @@ -390,12 +452,11 @@ def visit_Assign(self, node): if node.value.value.id == 'mem': if isinstance(node.value.slice, ast.BinOp): left = node.value.slice.left.id - if isinstance(node.value.slice.op, ast.Add): op1 = '+' elif isinstance(node.value.slice.op, ast.Sub): - op1 = '-' + op1 = '-' elif isinstance(node.value.slice.op, ast.Mult): op1 = '*' elif isinstance(node.value.slice.op, ast.Div): @@ -403,7 +464,6 @@ def visit_Assign(self, node): else: unimplemented('weird memory access operation') - if isinstance(node.value.slice.right, ast.Constant): right = node.value.slice.right.value nasm.append(f' mov {target.id}, [{left}{op1}{right}]') @@ -418,17 +478,16 @@ def visit_Assign(self, node): right_left = node.value.slice.right.left.id else: unimplemented('bad assign, has to be like [reg+reg*num]') - + if isinstance(node.value.slice.right.right, ast.Constant): right_right = node.value.slice.right.right.value else: unimplemented('bad assign, has to be like [reg+reg*num]') - if isinstance(node.value.slice.right.op, ast.Add): op2 = '+' elif isinstance(node.value.slice.right.op, ast.Sub): - op2 = '-' + op2 = '-' elif isinstance(node.value.slice.right.op, ast.Mult): op2 = '*' elif isinstance(node.value.slice.right.op, ast.Div): @@ -436,12 +495,10 @@ def visit_Assign(self, node): else: unimplemented('weird memory access operation2') - nasm.append(f' mov {target.id}, [{left}{op1}{right_left}{op2}{right_right}]') else: unimplemented('weird assign') - elif isinstance(node.value.slice, ast.Name): reg = node.value.slice.id nasm.append(f' mov {target.id}, [{reg}]') @@ -450,20 +507,16 @@ def visit_Assign(self, node): else: unimplemented('array') - elif isinstance(node.value, ast.Call): # asign call: ebx = somthing() + elif isinstance(node.value, ast.Call): # asign call: ebx = somthing() self.generic_visit(node) nasm.append(f' mov {target.id}, rax') return - else: - unimplemented('assign '+node.value) - + unimplemented('assign ' + node.value) self.generic_visit(node) - - def visit_AugAssign(self, node): global nasm reg = node.target.id @@ -474,7 +527,6 @@ def visit_AugAssign(self, node): else: unimplemented(node.value) - if isinstance(node.op, ast.Add): nasm.append(f' add {reg}, {val}') elif isinstance(node.op, ast.Sub): @@ -492,25 +544,16 @@ def visit_AugAssign(self, node): else: unimplemented(node.op) - - - - - -def main(pyfile): +def main(): global nasm + pyfile = sys.argv[1] tree = ast.parse(open(pyfile).read()) - - visitor = visit_functions() visitor.visit(tree) - nasmfile = pyfile.replace('.py','.nasm') - open(nasmfile,'w').write('; python compiled with pynasm\n\nBITS 64\n\ncall main\njmp end\n'+'\n'.join(nasm)+'\n\nend:') - - - -main(sys.argv[1]) - + nasmfile = pyfile.replace('.py', '.nasm') + open(nasmfile, 'w').write('; python compiled with pynasm\n\nBITS 64\n\ncall main\njmp end\n' + '\n'.join(nasm) + '\n\nend:') +if __name__ == "__main__": + main() From 573019f21a640192899f76cbf5bcfd5fd5adae85 Mon Sep 17 00:00:00 2001 From: "Landen M. Martin" Date: Fri, 21 Aug 2026 13:44:13 -0500 Subject: [PATCH 2/2] Add checksum and classify functions in check-class.py Implement checksum and classify functions for data processing. --- examples/check-class.py | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/check-class.py diff --git a/examples/check-class.py b/examples/check-class.py new file mode 100644 index 0000000..8b14062 --- /dev/null +++ b/examples/check-class.py @@ -0,0 +1,42 @@ + +def checksum(rcx, rdx): + # rcx = buf, rdx = len + rax = 0 + rbx = 0 + + while rbx < rdx: + rsi = mem[rcx+rbx] + rax += rsi + rbx += 1 + + return rax + +def classify(rcx): + tag = 0 # local var, gets a stack slot + + if rcx < 0x10: + tag = 1 + elif rcx > 0xf0: + tag = 3 + else: + tag = 2 + + return rcx + +def main(): + # byte array on the stack, rax -> first byte + rax = [0x1a, 0x3b, 0xff, 0x00] + rcx = rax # buf + rdx = 4 # len + + r9 = checksum(rcx, rdx) + + # rcx still points at the array, callee saved the regs for us + r8 = mem[rcx+1] + + # elif chain + r10 = classify(r8) + + rax = r9 + rax += r10 + return rax