http://www.cotyrmiller.com > rants > Combining 2 powers, Python & Assembly (MacOS)
Combining 2 powers, Python & Assembly on MacOS (X)
There are two insanely powerful languages each one for a completely different reason than the other. On one hand you have
python, a language witch is interpreted making it slow and is so high level that the amount of abstraction means you have to access
to hardware. However, this provides the benefit of being able to wright vast amounts of code painlessly and quickly. Because of this,
python is often my first choice for doing anything, however sometimes its abstraction can get in the way.
On the other end of the stick, you have assembly code, Assembly code is extremely powerful, it can preform any operation
that a computer is able to preform as you are basically writing machine code it’s self. It also has the ability to run very fast, very
efficient and very compact as there is no middle man software, it runs straight on the hardware itself. The problem with assembly
code (Specifically when it comes to modern x86 or x86_64 architecture) is that the code tends to be fairly complicated and specially
on macOS it is very difficult to find good 64bit documentation… witch is a big problem as apple is rumored to be phasing out 32bit
support in the near future.
So, let’s build something that can play to both languages strengths, why choose one when we could use both at the
same time!?
So for today I decided the way I was going to go about this is very simple. We’ll start in python, and call an assembler
application and pass some arguments to it, or least a single byte string, modify the data in the assembly application, that we’ll just
add one to it then we’ll capture the output (in python) from the assembler application.
Let’s start with writing the assembler code. So since we’ll be collecting arguments as our input in the assembler code then we need
to understand how MacOS/Linux passes console arguments.
This is pretty straight forward, whenever you launch a program from the terminal, the system takes all the the arguments the file name
of the application and puts each one in it’s own string accessible to your application. It then pushes the address of those strings and
the total argument or string count onto the stack. Also note that the file name is the first argument, so you will always have at least
1 argument.
RSP = 64bit stack pointer
RSP [ +0 | +8 | +16 ]
/ | \
Number of. | Address of first
Arguments | argument string.
|
Address of file name string
So the first thing we will do is check to see if we have 2 arguments, if we have more or less we will just ignore it and close the
application. While as not something you would not want to do (you’d probably want to provide an error on exit) it will be fine for the
purpose of this demo.
start:
; check if we have the correct number of arguments
mov rax, [rsp+0]
cmp rax, 2
je .contine
; if not exit.
.exit:
mov rax, ints.exit
mov rdi, 0
syscall
Next if our code has made it that far, we’ll grab the single byte from our string add one to it and store it in a more local buffer
so it’s less of a pain to deal with.
.contine:
; grab byte from string.
mov rsi, [rsp+16]
lodsb
; add 1 to it and store it in our buffer.
inc al
mov rdi, place
stosb
Then we’ll follow it up by printing our modified data to the terminal so that python can capture it.
mov rsi, place
mov rdx, 1
mov rax, ints.write ; rax = write command.
mov rdi, 1 ; stdout
syscall ; write it to terminal.
jmp .exit ; go to exit.
(For full source code see below).
Alright now that we have our assembler code we can assemble it and we can run it as
Macintosh:pythonasm cotyrmiller$ ./bitflip e
f
And just like that, we have taken in the letter “e” as an argument, and our assembly code has dumped out the letter “f”! Just
note that this is raw, if you send “9” you will get “:” as it is the next char available as we are changing the literal numerical value of
the data. So now that we’ve completed the ASM code it’s time to work on our python code. Fortunately most of the real work is done.
Let’s just create our header, we’ll need to ensure that the code runs as python (MacOS will attempt to run it as a bash script by
default) this is super simple as we just need to point at the python interpreter in a comment at the top of the file and we will need to
import the subproccess library.
#!/usr/bin/env python
import subprocess
The subprocces library is what we’ll use to execute and retrieve output from our assembly application.
stringToAdd = "8"
a = subprocess.check_output(["./bitflip", stringToAdd]) # open the asm application as “./bitflip 8” and capture it’s output in ‘a’
print a # Print the captured output.
And… that’s it… That’s our python code. A whole whopping 5 lines… Running this will return and print
a “9”. And here is the total build and run process of all of our work.
Macintosh:pythonasm cotyrmiller$ ./build
Macintosh:pythonasm cotyrmiller$ ./pigo.pi
9
Macintosh:pythonasm cotyrmiller$
THAT’S IT! We are done! We now have now successfully combined assembly code and python code
together… Two opposites… Two giants… married together into a powerful ball of… something… You can
download the full work environment from code to build scripts bellow.
Happy coding - Coty R Miller
Download compressed ZIP of full source code: rantspythonasm.zip