GDB has a x/i
command (and also disassemble
) which allows to view the instructions at a given address.
How can we do the reverse – assemble an instruction and write to a given address?
Reverse Engineering Stack Exchange is a question and answer site for researchers and developers who explore the principles of a system through analysis of its structure, function, and operation. It only takes a minute to sign up.
Sign up to join this communityGDB has a x/i
command (and also disassemble
) which allows to view the instructions at a given address.
How can we do the reverse – assemble an instruction and write to a given address?
GDB doesn't seem to have any native command to assemble instructions. But we can use its Python scripting facility to create a custom command, which would prepare an assembly source file, feed it to an assembler, and write the resulting bytes to the inferior's address space.
Here's an example python script (which you can paste into your .gdbinit
) for x86/x86_64, using FASM as the assembler:
python
from tempfile import mkstemp
import subprocess
import os
class Assemble(gdb.Command):
"""Assemble an instruction
Usage: assemble 0xADDRESS instruction to assemble
"""
def __init__(self):
super(Assemble, self).__init__("assemble", gdb.COMMAND_DATA, gdb.COMPLETE_NONE, True)
def invoke(self, arg, from_tty):
argPieces=arg.split()
try:
# addresses like those resulting from $pc can have trailing
# junk, which we'll clear here by conversion to long
address=gdb.execute('printf "%#lx",'+argPieces[0], to_string=True)
except:
print("Failed to parse address")
return
instruction=" ".join(argPieces[1:])
bits=format(8*int(gdb.parse_and_eval("sizeof(void*)")),"d")
fd,srcPath=mkstemp(prefix="gdb_asm_")
src=os.fdopen(fd, 'w')
src.write("use%s\norg %s\n%s\n" % (bits, address, instruction))
src.close()
try:
subprocess.check_output(["fasm",srcPath], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as err:
print("Assembler failed with exit code", err.returncode)
print("Output:", err.output)
os.remove(srcPath)
return
os.remove(srcPath)
binPath=srcPath+".bin"
binaryFile=open(binPath, 'rb')
offset=0
while True:
byteStr=binaryFile.read(1)
if not byteStr:
break
byte=byteStr[0]
if not isinstance(byte, int): # compatibility with python 2
byte=ord(byte)
gdb.execute("set *(unsigned char*)("+address+'+'+
format(offset,'d')+")="+format(byte,"#02x"))
offset+=1
binaryFile.close()
os.remove(binPath)
if offset==0:
print("Assembler output an empty file")
return
gdb.execute("x/i "+address)
Assemble()
end
Then you'd use it like
gdb -q -ex starti /bin/true
Reading symbols from /bin/true...(no debugging symbols found)...done.
Starting program: /bin/true
Program stopped.
0xf7fdd800 in _start () from /lib/ld-linux.so.2
(gdb) disas
Dump of assembler code for function _start:
=> 0xf7fdd800 <+0>: mov eax,esp
0xf7fdd802 <+2>: call 0xf7fe2160 <_dl_start>
(gdb) assemble $pc+2 mov ebp, [esi+235+edx*2]
0xf7fdd802 <_start+2>: mov ebp,DWORD PTR [esi+edx*2+0xeb]
if you have gdb version > 7.7 installing gef with python support claims to leverage keystone for in place assembly
it appears to be pythonified gdbinit (peda pwndbg workalike)
the screen shots page has some nice displays
compile code
command
Introduced around 7.9, it allows code compilation and injection at the current location, documentation: https://sourceware.org/gdb/onlinedocs/gdb/Compiling-and-Injecting-Code.html
I have given a minimal runnable example at: https://stackoverflow.com/questions/5480868/how-to-call-assembly-in-gdb/31709579#31709579
I can't find out how to inject at a specific location, but if you are serious about this, patching this feature in extending compile code
functionality might be the way to go.
It is possible but very complicated!! Regardless gdb, you can do the following: