Skip to content

Commit f7da7bb

Browse files
authored
Merge pull request #12 from shaymargolis/add-different-arches-option
Add different arches option
2 parents 31ac3c1 + 9a55236 commit f7da7bb

13 files changed

Lines changed: 266 additions & 65 deletions

File tree

.github/workflows/python-app.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ jobs:
3333
flake8 . --count --max-complexity=10 --max-line-length=127 --statistics
3434
- name: Test with pytest
3535
run: |
36-
pytest
36+
pytest --compiler-arch=mipsbe
37+
pytest --compiler-arch=mipsle

shellblocks/compiler_arch.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class CompilerArch():
2+
def __init__(self):
3+
pass
4+
5+
def compile_primitive(self, src_path: str) -> [str]:
6+
raise NotImplementedError()
7+
8+
def compile_step(self, src_paths: [str], base_address: int) -> [str]:
9+
raise NotImplementedError()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from enum import Enum
2+
from shellblocks.compiler_archs.mips import CompilerArchMIPSBE, CompilerArchMIPSLE
3+
from shellblocks.compiler_arch import CompilerArch
4+
5+
6+
class CompilerArchOption(Enum):
7+
MIPSBE = "mipsbe"
8+
MIPSLE = "mipsle"
9+
10+
11+
def compiler_arch_to_object(arch: CompilerArchOption) -> CompilerArch:
12+
if arch == CompilerArchOption.MIPSBE:
13+
return CompilerArchMIPSBE()
14+
elif arch == CompilerArchOption.MIPSLE:
15+
return CompilerArchMIPSLE()
16+
17+
raise NotImplementedError()
18+
19+
20+
__all__ = [
21+
CompilerArchMIPSBE, CompilerArchMIPSLE,
22+
]

shellblocks/compiler_archs/gcc.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from shellblocks.compiler_arch import CompilerArch
2+
3+
4+
class CompilerArchGCC(CompilerArch):
5+
def __init__(self):
6+
super().__init__()
7+
8+
self.compiler_path = self.get_compiler_path()
9+
10+
def get_compiler_path(self):
11+
raise NotImplementedError()
12+
13+
def get_ldscript_path(self):
14+
raise NotImplementedError()
15+
16+
def get_gcc_flags(self):
17+
return [
18+
"-nostdlib",
19+
"-ffreestanding",
20+
]
21+
22+
def compile_primitive(self, src_path: str) -> [str]:
23+
return [
24+
self.compiler_path,
25+
*self.get_gcc_flags(),
26+
"-c", src_path,
27+
"-o", "final.o",
28+
"-O3"
29+
]
30+
31+
def compile_step(self, src_paths: [str], base_address: int) -> [str]:
32+
ldscript_loc = self.get_ldscript_path()
33+
34+
return [
35+
self.compiler_path,
36+
*src_paths,
37+
"-o", "shellcode.elf",
38+
*self.get_gcc_flags(),
39+
f"-Wl,--section-start=.text={hex(base_address)}",
40+
f"-Wl,-T{ldscript_loc}"
41+
]

shellblocks/compiler_archs/mips.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from shellblocks.compiler_archs.gcc import CompilerArchGCC
2+
from shellblocks.utils import sources_location
3+
4+
5+
class CompilerArchMIPS(CompilerArchGCC):
6+
def __init__(self):
7+
super().__init__()
8+
9+
self.compiler_path = self.get_compiler_path()
10+
11+
def get_gcc_flags(self):
12+
return super().get_gcc_flags() + [
13+
"-mno-shared",
14+
]
15+
16+
def get_ldscript_path(self):
17+
return (sources_location / "shellcode_ldscript.ld").as_posix()
18+
19+
20+
class CompilerArchMIPSBE(CompilerArchMIPS):
21+
def get_compiler_path(self):
22+
return "mips-linux-gnu-gcc-9"
23+
24+
25+
class CompilerArchMIPSLE(CompilerArchMIPS):
26+
def get_gcc_flags(self):
27+
return super().get_gcc_flags() + [
28+
"-EL",
29+
]
30+
31+
def get_compiler_path(self):
32+
return "mips-linux-gnu-gcc-9"

shellblocks/shellcode_primitive.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import shutil
22
from pathlib import Path
33

4+
from shellblocks.compiler_arch import CompilerArch
45
from shellblocks.utils import check_call_print, sources_location
56

67

@@ -37,7 +38,7 @@ def generate_header_file(self, path: Path):
3738
def header_requirements(self):
3839
return {}
3940

40-
def generate(self, path: Path):
41+
def generate(self, path: Path, compiler: CompilerArch):
4142
for source in self.sources:
4243
source_src = sources_location / source
4344
source_dst = path / source
@@ -46,15 +47,10 @@ def generate(self, path: Path):
4647

4748
self.generate_header_file(path)
4849

49-
check_call_print([
50-
"mips-linux-gnu-gcc-9",
51-
"-nostdlib",
52-
"-ffreestanding",
53-
"-mno-shared",
54-
"-c", self.sources[0],
55-
"-o", "final.o",
56-
"-O3"
57-
], cwd=path.as_posix())
50+
check_call_print(
51+
compiler.compile_primitive(self.sources[0]),
52+
cwd=path.as_posix()
53+
)
5854

5955
check_call_print([
6056
"objcopy",

shellblocks/shellcode_step.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
from pathlib import Path
33

44

5+
from shellblocks.compiler_arch import CompilerArch
56
from shellblocks.shellcode_primitive import ShellcodePrimitive
6-
from shellblocks.utils import check_call_print, sources_location
7+
from shellblocks.utils import check_call_print
78

89

910
class ShellcodeStep:
@@ -13,7 +14,7 @@ def __init__(self, nickname: str, base_address: int, primitives: [ShellcodePrimi
1314
self.primitives = primitives
1415
self.max_len = max_len
1516

16-
def generate(self, build_dir: Path):
17+
def generate(self, build_dir: Path, compiler: CompilerArch):
1718
# Create build dir
1819
try:
1920
shutil.rmtree(build_dir.as_posix())
@@ -29,20 +30,14 @@ def generate(self, build_dir: Path):
2930
p_build_dir = build_dir / p.nickname
3031
p_build_dir.mkdir()
3132

32-
out_file = p.generate(p_build_dir)
33+
out_file = p.generate(p_build_dir, compiler)
3334
out_files.append(out_file.as_posix())
3435

3536
# Join all primitives to final shellcode
36-
ldscript_loc = (sources_location / "shellcode_ldscript.ld").as_posix()
37-
check_call_print([
38-
"mips-linux-gnu-gcc-9",
39-
*out_files,
40-
"-o", "shellcode.elf",
41-
"-nostdlib",
42-
"-ffreestanding",
43-
f"-Wl,--section-start=.text={hex(self.base_address)}",
44-
f"-Wl,-T{ldscript_loc}"
45-
], cwd=build_dir.as_posix())
37+
check_call_print(
38+
compiler.compile_step(out_files, self.base_address),
39+
cwd=build_dir.as_posix()
40+
)
4641

4742
check_call_print([
4843
"objcopy",

shellblocks/test_arch_helper.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from shellblocks.compiler_archs import CompilerArchOption
2+
3+
4+
class ArchHelper:
5+
def __init__(self, compiler_arch_option):
6+
self.compiler_arch_option = compiler_arch_option
7+
8+
9+
class MIPSHelper(ArchHelper):
10+
def __init__(self, compiler_arch_option):
11+
super().__init__(compiler_arch_option)
12+
13+
assert compiler_arch_option in [
14+
CompilerArchOption.MIPSBE,
15+
CompilerArchOption.MIPSLE,
16+
]
17+
18+
def get_jump_hook_bytes(self, jump_hook_goto):
19+
EXPECTED_HOOK = [
20+
0x3c020000 + (jump_hook_goto >> 16),
21+
0x24420000 + (jump_hook_goto & 0xffff),
22+
0x00400008,
23+
0x00000000,
24+
]
25+
26+
if CompilerArchOption.MIPSBE == self.compiler_arch_option:
27+
return b"".join(map(lambda x: x.to_bytes(4, 'big'), EXPECTED_HOOK))
28+
elif CompilerArchOption.MIPSLE == self.compiler_arch_option:
29+
return b"".join(map(lambda x: x.to_bytes(4, 'little'), EXPECTED_HOOK))
30+
else:
31+
raise NotImplementedError()
32+
33+
def get_ret_bytes(self):
34+
val = 0x03e00008 # "jr $ra" in MIPS
35+
36+
if CompilerArchOption.MIPSBE == self.compiler_arch_option:
37+
return val.to_bytes(4, 'big')
38+
elif CompilerArchOption.MIPSLE == self.compiler_arch_option:
39+
return val.to_bytes(4, 'little')
40+
else:
41+
raise NotImplementedError()

tests/conftest.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,62 @@
44

55
from pathlib import Path
66

7+
from unicorn import (
8+
Uc,
9+
UC_ARCH_MIPS,
10+
UC_MODE_32,
11+
UC_MODE_BIG_ENDIAN,
12+
UC_MODE_LITTLE_ENDIAN,
13+
)
14+
15+
16+
from shellblocks.test_arch_helper import MIPSHelper
17+
18+
from shellblocks.compiler_archs import CompilerArchOption, compiler_arch_to_object
19+
20+
21+
def pytest_addoption(parser):
22+
choices = [e.value for e in CompilerArchOption]
23+
24+
parser.addoption(
25+
"--compiler-arch",
26+
type=str,
27+
choices=choices,
28+
default=CompilerArchOption.MIPSBE.value,
29+
help="The architecture to compile to"
30+
)
31+
32+
33+
@pytest.fixture
34+
def compiler_arch_option(request):
35+
return CompilerArchOption(request.config.getoption("--compiler-arch"))
36+
37+
38+
@pytest.fixture
39+
def compiler_arch(compiler_arch_option):
40+
return compiler_arch_to_object(compiler_arch_option)
41+
42+
43+
@pytest.fixture
44+
def arch_helper(compiler_arch_option):
45+
if compiler_arch_option in [CompilerArchOption.MIPSBE, CompilerArchOption.MIPSLE]:
46+
return MIPSHelper(compiler_arch_option)
47+
else:
48+
raise NotImplementedError("Arch unimplemented error!")
49+
50+
51+
@pytest.fixture
52+
def get_mu(compiler_arch_option):
53+
def get_mu_instance():
54+
if CompilerArchOption.MIPSBE == compiler_arch_option:
55+
return Uc(UC_ARCH_MIPS, UC_MODE_32 | UC_MODE_BIG_ENDIAN)
56+
elif CompilerArchOption.MIPSLE == compiler_arch_option:
57+
return Uc(UC_ARCH_MIPS, UC_MODE_32 | UC_MODE_LITTLE_ENDIAN)
58+
else:
59+
raise NotImplementedError("Arch unimplemented error!")
60+
61+
return get_mu_instance
62+
763

864
@pytest.fixture(scope='function')
965
def temp_dir_path():

tests/test_goto.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import pytest
22

3-
from unicorn import Uc, UC_ARCH_MIPS, UC_MODE_32, UC_MODE_BIG_ENDIAN
43
from unicorn.mips_const import UC_MIPS_REG_PC
54

65
from shellblocks.shellcode_step import ShellcodeStep
@@ -16,7 +15,7 @@
1615
(0xbc000000, 0xbcf00010),
1716
(0x91000000, 0x91000118),
1817
])
19-
def test_goto_sanity(temp_dir_path, goto_page_and_address):
18+
def test_goto_sanity(get_mu, temp_dir_path, compiler_arch, goto_page_and_address):
2019
# Generate shellcode
2120
# ------------------
2221
shellcode_address = 0xbfc00000
@@ -31,13 +30,13 @@ def test_goto_sanity(temp_dir_path, goto_page_and_address):
3130
0x1000
3231
)
3332

34-
out_file = step.generate(temp_dir_path / step.nickname)
33+
out_file = step.generate(temp_dir_path / step.nickname, compiler_arch)
3534
shellcode = out_file.read_bytes()
3635

3736
# Try to run shellcode
3837
# --------------------
3938

40-
mu = Uc(UC_ARCH_MIPS, UC_MODE_32 | UC_MODE_BIG_ENDIAN)
39+
mu = get_mu()
4140
mu.mem_map(shellcode_address, 0x2000)
4241

4342
# write machine code to be emulated to memory
@@ -54,7 +53,7 @@ def test_goto_sanity(temp_dir_path, goto_page_and_address):
5453
(0xbcf00010),
5554
(0x91000118),
5655
])
57-
def test_goto_is_pic(temp_dir_path, shellcode_run_addr):
56+
def test_goto_is_pic(get_mu, temp_dir_path, compiler_arch, shellcode_run_addr):
5857
# Generate shellcode
5958
# ------------------
6059
shellcode_address = 0xbfc00000
@@ -71,13 +70,13 @@ def test_goto_is_pic(temp_dir_path, shellcode_run_addr):
7170
0x1000
7271
)
7372

74-
out_file = step.generate(temp_dir_path / step.nickname)
73+
out_file = step.generate(temp_dir_path / step.nickname, compiler_arch)
7574
shellcode = out_file.read_bytes()
7675

7776
# Try to run shellcode
7877
# --------------------
7978

80-
mu = Uc(UC_ARCH_MIPS, UC_MODE_32 | UC_MODE_BIG_ENDIAN)
79+
mu = get_mu()
8180
mu.mem_map(shellcode_run_sector, 0x2000)
8281

8382
# write machine code to be emulated to memory

0 commit comments

Comments
 (0)