Skip to content

Commit 0e3a695

Browse files
committed
static_files binary patching: add asm support
1 parent 5ccb3cc commit 0e3a695

File tree

3 files changed

+114
-13
lines changed

3 files changed

+114
-13
lines changed

docs/schema_doc.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,8 @@ Offset in the file to patch, from the start of the file
11381138

11391139
|||
11401140
|-|-|
1141-
|__Type__|string|
1141+
|__Type__|string or null|
1142+
|__Default__|`null`|
11421143

11431144
Hex string of bytes to write at the offset
11441145

@@ -1150,6 +1151,40 @@ DEADBEEF
11501151
90 90
11511152
```
11521153

1154+
##### `static_files.<string>.<type=binary_patch>.asm` Assembly code to write (runs through keystone)
1155+
1156+
|||
1157+
|-|-|
1158+
|__Type__|string or null|
1159+
|__Default__|`null`|
1160+
1161+
Assembly code to write at the offset. This will be assembled and written to the file.
1162+
1163+
```yaml
1164+
nop
1165+
```
1166+
1167+
```yaml
1168+
'mov r0, #0xdeadbeef'
1169+
```
1170+
1171+
##### `static_files.<string>.<type=binary_patch>.mode` Assembly mode
1172+
1173+
|||
1174+
|-|-|
1175+
|__Type__|string or null|
1176+
|__Default__|`null`|
1177+
1178+
What mode to use for assembly with asm.
1179+
1180+
```yaml
1181+
arm
1182+
```
1183+
1184+
```yaml
1185+
thumb
1186+
```
1187+
11531188
## `plugins` Plugins
11541189

11551190

src/penguin/gen_image.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pathlib import Path
88
from subprocess import check_output
99
from random import randint
10+
import keystone
1011

1112
import click
1213

@@ -234,7 +235,7 @@ def _symlink_modify_guestfs(g, file_path, file):
234235
g.chmod(0o777, linkpath)
235236

236237

237-
def _modify_guestfs(g, file_path, file, project_dir):
238+
def _modify_guestfs(g, file_path, file, project_dir, config):
238239
"""
239240
Given a guestfs handle, a file path, and a file dict, perform the specified action on the guestfs filesystem.
240241
If the action is unsupported or fails, we'll print details and raise an exception.
@@ -282,7 +283,7 @@ def _modify_guestfs(g, file_path, file, project_dir):
282283
new_file = file
283284
new_file["host_path"] = m
284285
new_file_path = str(Path(folder, Path(m).name))
285-
_modify_guestfs(g, new_file_path, new_file, project_dir)
286+
_modify_guestfs(g, new_file_path, new_file, project_dir, config)
286287
return
287288

288289
try:
@@ -389,12 +390,56 @@ def _modify_guestfs(g, file_path, file, project_dir):
389390
elif action == "binary_patch":
390391
file_offset = file.get("file_offset")
391392
bytes_hex = file.get("hex_bytes")
393+
asm = file.get("asm")
394+
395+
if bool(bytes_hex) == bool(asm):
396+
raise ValueError("Exactly one of 'hex_bytes' or 'asm' must be specified for binary_patch")
397+
398+
if asm:
399+
arch_map = {
400+
"armel": getattr(keystone, "KS_ARCH_ARM"),
401+
"aarch64": getattr(keystone, "KS_ARCH_ARM64"),
402+
"mipsel": getattr(keystone, "KS_ARCH_MIPS"),
403+
"mipseb": getattr(keystone, "KS_ARCH_MIPS"),
404+
"mips64el": getattr(keystone, "KS_ARCH_MIPS"),
405+
"mips64eb": getattr(keystone, "KS_ARCH_MIPS"),
406+
"intel64": getattr(keystone, "KS_ARCH_X86"),
407+
}
408+
mode_map = {
409+
"aarch64": getattr(keystone, "KS_MODE_LITTLE_ENDIAN") | getattr(keystone, "KS_MODE_64"),
410+
"mipsel": getattr(keystone, "KS_MODE_MIPS32") | getattr(keystone, "KS_MODE_LITTLE_ENDIAN"),
411+
"mipseb": getattr(keystone, "KS_MODE_MIPS32") | getattr(keystone, "KS_MODE_BIG_ENDIAN"),
412+
"mips64el": getattr(keystone, "KS_MODE_MIPS64") | getattr(keystone, "KS_MODE_LITTLE_ENDIAN"),
413+
"mips64eb": getattr(keystone, "KS_MODE_MIPS64") | getattr(keystone, "KS_MODE_BIG_ENDIAN"),
414+
"intel64": getattr(keystone, "KS_MODE_64"),
415+
}
416+
arch = config["core"]["arch"]
417+
ks_arch = arch_map.get(arch)
418+
if ks_arch is None:
419+
raise ValueError(f"Unsupported arch: {arch}")
420+
421+
# Handle ARM mode selection
422+
if arch == "armel":
423+
user_mode = file.get("mode", "arm")
424+
if user_mode == "thumb":
425+
ks_mode = getattr(keystone, "KS_MODE_THUMB") | getattr(keystone, "KS_MODE_LITTLE_ENDIAN")
426+
else:
427+
ks_mode = getattr(keystone, "KS_MODE_ARM") | getattr(keystone, "KS_MODE_LITTLE_ENDIAN")
428+
else:
429+
ks_mode = mode_map.get(arch)
430+
if ks_mode is None:
431+
raise ValueError(f"Unsupported mode for arch: {arch}")
392432

393-
# Convert hex string to bytes
394-
patch_bytes = bytes.fromhex(bytes_hex.replace(" ", ""))
395-
target_path = file_path
433+
ks = keystone.Ks(ks_arch, ks_mode)
434+
encoding, _ = ks.asm(asm)
435+
patch_bytes = bytes(encoding)
436+
else:
437+
patch_bytes = bytes.fromhex(bytes_hex.replace(" ", ""))
396438

397-
# Open the file and patch it
439+
if file_offset is None:
440+
raise ValueError("binary_patch requires file_offset")
441+
442+
target_path = file_path
398443
p = g.adjust_path(target_path)
399444
if not os.path.isfile(p):
400445
raise FileNotFoundError(f"Target file for binary_patch not found: {target_path}")
@@ -477,15 +522,15 @@ def resolve_symlink_path(g, path):
477522
# resolved_file_path = resolve_symlink_path(g, file_path)
478523
# resolved_file_path = os.path.dirname(resolved_file_path) + '/' + os.path.basename(file_path)
479524
if resolved_file_path := file_path:
480-
_modify_guestfs(g, resolved_file_path, file, project_dir)
525+
_modify_guestfs(g, resolved_file_path, file, project_dir, config)
481526

482527
# Next, we'll do any move_from operations
483528
move_from_files = {k: v for k, v in files.items() if v["type"] == "move_from"}
484529
sorted_move_from_files = sorted(
485530
move_from_files.items(), key=lambda x: len(files[x[0]])
486531
)
487532
for file_path, file in sorted_move_from_files:
488-
_modify_guestfs(g, file_path, file, project_dir)
533+
_modify_guestfs(g, file_path, file, project_dir, config)
489534

490535
# Now we'll do everything, except symlinks
491536
sorted_files = {
@@ -502,7 +547,7 @@ def resolve_symlink_path(g, path):
502547
# resolved_file_path = file_path
503548
# if resolved_file_path != file_path:
504549
# print(f"WARNING: Resolved file path {file_path} to {resolved_file_path}")
505-
_modify_guestfs(g, resolved_file_path, file, project_dir)
550+
_modify_guestfs(g, resolved_file_path, file, project_dir, config)
506551

507552
# Create symlinks after everything else because guestfs requires destination to exist
508553
# move_from_files = {k: v for k, v in files.items() if v["type"] == "symlink"}
@@ -513,7 +558,7 @@ def resolve_symlink_path(g, path):
513558
move_from_files.items(), key=lambda x: len(files[x[0]]["target"])
514559
)
515560
for file_path, file in sorted_move_from_files:
516-
_modify_guestfs(g, file_path, file, project_dir)
561+
_modify_guestfs(g, file_path, file, project_dir, config)
517562

518563
# Sanity checks. Does guest still have a /bin/sh? Is there a /igloo directory?
519564
if (

src/penguin/penguin_config/structure.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -724,14 +724,35 @@ class LibInject(BaseModel):
724724
),
725725
(
726726
"hex_bytes",
727-
str,
727+
Optional[str],
728728
Field(
729+
default=None,
729730
title="Bytes to write (hex string)",
730731
description="Hex string of bytes to write at the offset",
731732
examples=["DEADBEEF", "90 90"],
732733
),
733734
),
734-
),
735+
(
736+
"asm",
737+
Optional[str],
738+
Field(
739+
default=None,
740+
title="Assembly code to write (runs through keystone)",
741+
description="Assembly code to write at the offset. This will be assembled and written to the file.",
742+
examples=["nop", "mov r0, #0xdeadbeef"],
743+
),
744+
),
745+
(
746+
"mode",
747+
Optional[str],
748+
Field(
749+
default=None,
750+
title="Assembly mode",
751+
description="What mode to use for assembly with asm.",
752+
examples=["arm", "thumb"],
753+
),
754+
),
755+
)
735756
),
736757
),
737758
)

0 commit comments

Comments
 (0)