Skip to content

Commit

Permalink
Fix assemble command including docs and tests (#694)
Browse files Browse the repository at this point in the history
Fixes the documentation and help messages for the assemble command. Furthermore the assemble command now works for all architectures (and modes, endianness) that are supported by both GDB and keystone (so not the ones that are only supported by one of them). Also this PR includes one test for each of the possible combinations of architecture, mode and endianness.

These are all now supported targets for this command:

Available architectures/modes (with endianness):
- ARM
  * ARM     (little, big)
  * THUMB   (little, big)
  * ARMV8   (little, big)
  * THUMBV8 (little, big)
- ARM64
  * AARCH64 (little)
- MIPS
  * MIPS32  (little, big)
  * MIPS64  (little, big)
- PPC
  * PPC32   (big)
  * PPC64   (little, big)
- SPARC
  * SPARC32 (little, big)
  * SPARC64 (big)
- SYSTEMZ
  * SYSTEMZ (little, big)
- X86
  * 16      (little)
  * 32      (little)
  * 64      (little)
  • Loading branch information
theguy147 committed Aug 23, 2021
1 parent dcfa6f2 commit 6300e75
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 31 deletions.
62 changes: 55 additions & 7 deletions docs/commands/assemble.md
@@ -1,8 +1,8 @@
## Command assemble ##

If you have installed [`keystone`](http://www.keystone-engine.org/), then `gef` will provide
a convenient command to assemble native instructions directly to opcodes of the
architecture you are currently debugging.
If you have installed [`keystone`](http://www.keystone-engine.org/), then `gef`
will provide a convenient command to assemble native instructions directly to
opcodes of the architecture you are currently debugging.

Call it via `assemble` or its alias `asm`:

Expand All @@ -12,8 +12,56 @@ gef➤ asm [INSTRUCTION [; INSTRUCTION ...]]

![gef-assemble](https://i.imgur.com/ShuPF6h.png)

Using the `-l LOCATION` option, `gef` will write the assembly code generated by
`keystone` directly to the memory location specified. This makes it extremely
convenient to simply overwrite opcodes.
By setting the `--arch ARCH` and `--mode MODE` the target platform for the
assembly can be changed. Available architectures and modes can be displayed
with `--list-archs`.

![gef-assemble-overwrite](https://i.imgur.com/CvJv36q.png)
```
gef➤ asm --list-archs
Available architectures/modes (with endianness):
- ARM
* ARM (little, big)
* THUMB (little, big)
* ARMV8 (little, big)
* THUMBV8 (little, big)
- ARM64
* AARCH64 (little)
- MIPS
* MIPS32 (little, big)
* MIPS64 (little, big)
- PPC
* PPC32 (big)
* PPC64 (little, big)
- SPARC
* SPARC32 (little, big)
* SPARC64 (big)
- SYSTEMZ
* SYSTEMZ (little, big)
- X86
* 16 (little)
* 32 (little)
* 64 (little)
```

```
gef➤ asm --arch x86 --mode 32 [INSTRUCTION [; INSTRUCTION ...]]
gef➤ asm --arch arm [INSTRUCTION [; INSTRUCTION ...]]
```

To choose the endianness use `--endian ENDIANNESS` (by default, `little`):

```
gef➤ asm --endian big [INSTRUCTION [; INSTRUCTION ...]]
```

Using the `--overwrite-location LOCATION` option, `gef` will write the assembly
code generated by `keystone` directly to the memory location specified. This
makes it extremely convenient to simply overwrite opcodes.

![gef-assemble-overwrite](https://i.imgur.com/BsbGXNC.png)

Another convenient option is `--as-shellcode` which outputs the generated
shellcode as an escaped python string. It can then easily be used in your
python scripts.

![gef-assemble-shellcode](https://i.imgur.com/E2fpFuH.png)
91 changes: 71 additions & 20 deletions gef.py
Expand Up @@ -3319,7 +3319,28 @@ def get_keystone_arch(arch=None, mode=None, endian=None, to_string=False):
keystone = sys.modules["keystone"]
if (arch, mode, endian) == (None, None, None):
return get_generic_running_arch(keystone, "KS", to_string)
return get_generic_arch(keystone, "KS", arch, mode, endian, to_string)

if arch in ["ARM64", "SYSTEMZ"]:
modes = [None]
elif arch == "ARM" and mode == "ARMV8":
modes = ["ARM", "V8"]
elif arch == "ARM" and mode == "THUMBV8":
modes = ["THUMB", "V8"]
else:
modes = [mode]
a = arch
if not to_string:
mode = 0
for m in modes:
arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string)
mode |= _mode
else:
mode = ""
for m in modes:
arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string)
mode += "|{}".format(_mode)
mode = mode[1:]
return arch, mode


def get_unicorn_registers(to_string=False):
Expand Down Expand Up @@ -7353,18 +7374,21 @@ class AssembleCommand(GenericCommand):
"""Inline code assemble. Architecture can be set in GEF runtime config. """

_cmdline_ = "assemble"
_syntax_ = "{:s} [-a ARCH] [-m MODE] [-e] [-s] [-l LOCATION] instruction;[instruction;...instruction;])".format(_cmdline_)
_syntax_ = "{:s} [-h] [--list-archs] [--mode MODE] [--arch ARCH] [--overwrite-location LOCATION] [--endian ENDIAN] [--as-shellcode] instruction;[instruction;...instruction;])".format(_cmdline_)
_aliases_ = ["asm",]
_example_ = "\n{0:s} -a x86 -m 32 nop ; nop ; inc eax ; int3\n{0:s} -a arm -m arm add r0, r0, 1".format(_cmdline_)

valid_arch_modes = {
"ARM": ["ARM", "THUMB"],
"ARM64": ["ARM", "THUMB", "V5", "V8", ],
"MIPS": ["MICRO", "MIPS3", "MIPS32", "MIPS32R6", "MIPS64",],
"PPC": ["PPC32", "PPC64", "QPX",],
"SPARC": ["SPARC32", "SPARC64", "V9",],
"SYSTEMZ": ["32",],
"X86": ["16", "32", "64"],
# Format: ARCH = [MODES] with MODE = (NAME, HAS_LITTLE_ENDIAN, HAS_BIG_ENDIAN)
"ARM": [("ARM", True, True), ("THUMB", True, True),
("ARMV8", True, True), ("THUMBV8", True, True)],
"ARM64": [("AARCH64", True, False)],
"MIPS": [("MIPS32", True, True), ("MIPS64", True, True)],
"PPC": [("PPC32", False, True), ("PPC64", True, True)],
"SPARC": [("SPARC32", True, True), ("SPARC64", False, True)],
"SYSTEMZ": [("SYSTEMZ", True, True)],
"X86": [("16", True, False), ("32", True, False),
("64", True, False)]
}
valid_archs = valid_arch_modes.keys()
valid_modes = [_ for sublist in valid_arch_modes.values() for _ in sublist]
Expand All @@ -7385,18 +7409,34 @@ def pre_load(self):

def usage(self):
super().usage()
gef_print("\nAvailable architectures/modes:")
# for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h
for arch in self.valid_arch_modes:
gef_print(" - {} ".format(arch))
gef_print(" * {}".format(" / ".join(self.valid_arch_modes[arch])))
gef_print("")
self.list_archs()
return

@parse_arguments({"instructions": ["",]}, {"--mode": "", "--arch": "", "--overwrite-location": 0, "--big-endian": True, "--as-shellcode": True, })
def list_archs(self):
gef_print("Available architectures/modes (with endianness):")
# for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h
for arch in self.valid_arch_modes:
gef_print("- {}".format(arch))
for mode, le, be in self.valid_arch_modes[arch]:
if le and be:
endianness = "little, big"
elif le:
endianness = "little"
elif be:
endianness = "big"
gef_print(" * {:<7} ({})".format(mode, endianness))
return

@parse_arguments({"instructions": [""]}, {"--mode": "", "--arch": "", "--overwrite-location": 0, "--endian": "little", "--list-archs": True, "--as-shellcode": True})
def do_invoke(self, argv, *args, **kwargs):
arch_s, mode_s, endian_s = self.get_setting("default_architecture"), self.get_setting("default_mode"), ""

args = kwargs["arguments"]
if args.list_archs:
self.list_archs()
return

if not args.instructions:
err("No instruction given.")
return
Expand All @@ -7407,18 +7447,29 @@ def do_invoke(self, argv, *args, **kwargs):

if args.arch:
arch_s = args.arch
arch_s = arch_s.upper()

if args.mode:
mode_s = args.mode
mode_s = mode_s.upper()

if args.big_endian:
if args.endian == "big":
endian_s = "big"
endian_s = endian_s.upper()

if arch_s not in self.valid_arch_modes:
raise AttributeError("invalid arch '{}'".format(arch_s))

valid_modes = self.valid_arch_modes[arch_s]
try:
mode_idx = [m[0] for m in valid_modes].index(mode_s)
except ValueError:
raise AttributeError("invalid mode '{}' for arch '{}'".format(mode_s, arch_s))

if arch_s.upper() not in self.valid_archs or mode_s.upper() not in self.valid_modes:
raise AttributeError("invalid arch/mode")
if endian_s == "little" and not valid_modes[mode_idx][1] or endian_s == "big" and not valid_modes[mode_idx][2]:
raise AttributeError("invalid endianness '{}' for arch/mode '{}:{}'".format(endian_s, arch_s, mode_s))

# this is fire a ValueError if the arch/mode/endianess are invalid
arch, mode = get_keystone_arch(arch=arch_s.upper(), mode=mode_s.upper(), endian=endian_s.upper())
arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=endian_s)
insns = [x.strip() for x in " ".join(args.instructions).split(";") if x]
info("Assembling {} instruction(s) for {}:{}".format(len(insns), arch_s, mode_s))

Expand Down
26 changes: 22 additions & 4 deletions tests/runtests.py
Expand Up @@ -312,10 +312,28 @@ def test_cmd_memory_reset(self):
def test_cmd_keystone_assemble(self):
valid_cmds = [
"assemble nop; xor eax, eax; syscall",
"assemble --arch arm --mode arm add r0, r1, r2",
"assemble --arch mips --mode mips32 add $v0, 1",
"assemble --arch sparc --mode sparc32 set 0, %o0",
"assemble --arch arm64 --mode arm add x29, sp, 0; mov w0, 0; ret"
"assemble --arch arm --mode arm add r0, r1, r2",
"assemble --arch arm --mode arm --endian big add r0, r1, r2",
"assemble --arch arm --mode thumb add r0, r1, r2",
"assemble --arch arm --mode thumb --endian big add r0, r1, r2",
"assemble --arch arm --mode armv8 add r0, r1, r2",
"assemble --arch arm --mode armv8 --endian big add r0, r1, r2",
"assemble --arch arm --mode thumbv8 add r0, r1, r2",
"assemble --arch arm --mode thumbv8 --endian big add r0, r1, r2",
"assemble --arch arm64 --mode aarch64 add x29, sp, 0; mov w0, 0; ret",
"assemble --arch mips --mode mips32 add $v0, 1",
"assemble --arch mips --mode mips32 --endian big add $v0, 1",
"assemble --arch mips --mode mips64 add $v0, 1",
"assemble --arch mips --mode mips64 --endian big add $v0, 1",
"assemble --arch ppc --mode ppc32 --endian big ori 0, 0, 0",
"assemble --arch ppc --mode ppc64 ori 0, 0, 0",
"assemble --arch ppc --mode ppc64 --endian big ori 0, 0, 0",
"assemble --arch sparc --mode sparc32 set 0, %o0",
"assemble --arch sparc --mode sparc32 --endian big set 0, %o0",
"assemble --arch sparc --mode sparc64 --endian big set 0, %o0",
"assemble --arch x86 --mode 16 mov ax, 0x42",
"assemble --arch x86 --mode 32 mov eax, 0x42",
"assemble --arch x86 --mode 64 mov rax, 0x42",
]
for cmd in valid_cmds:
res = gdb_start_silent_cmd(cmd)
Expand Down

0 comments on commit 6300e75

Please sign in to comment.