bytemancy3
⚒️ Bytemancy 3
| Category | Author |
|---|---|
| ⚒️ Binary Exploitation | LT ‘syreal’ Jones |
Challenge Prompt
Can you conjure the right bytes? The program’s source code can be downloaded here and the compiled spellbook binary can be downloaded here.
Additional details will be available after launching your challenge instance.
Problem Type
- Python
- pwntools
Solve
We are given the program source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import os
import random
import select
import sys
from typing import Optional
from pwn import ELF, p32
BANNER = "⊹──────[ BYTEMANCY-3 ]──────⊹"
BINARY_PATH = os.path.join(os.path.dirname(__file__), "spellbook")
QUESTION_COUNT = 3
SPELLBOOK_FUNCTIONS = [
"ember_sigil",
"glyph_conflux",
"astral_spark",
"binding_word",
]
def read_exact_bytes(expected_len: int) -> Optional[bytes]:
"""Read a fixed number of bytes from stdin, trimming a trailing newline."""
stdin_buffer = sys.stdin.buffer
buf = stdin_buffer.read(expected_len)
if not buf or len(buf) < expected_len:
return None
# Discard trailing newlines only if more bytes are immediately available
try:
stdin_fileno = sys.stdin.fileno()
except (AttributeError, ValueError, OSError):
stdin_fileno = None
if stdin_fileno is not None:
while True:
readable, _, _ = select.select([stdin_fileno], [], [], 0)
if not readable:
break
peek = stdin_buffer.peek(1)[:1]
if peek in (b"\n", b"\r"):
stdin_buffer.read(1)
continue
break
return buf
def main():
try:
elf = ELF(BINARY_PATH, checksec=False)
except FileNotFoundError:
print("The spellbook is missing!")
return
flag = open("./flag.txt", "r").read().strip()
while True:
try:
print(BANNER)
print("☍⟐☉⟊☽☈⟁⧋⟡☍⟐☉⟊☽☈⟁⧋⟡☍⟐☉⟊☽☈⟁⧋⟡☍⟐")
print()
print("I will name four procedures hidden inside spellbook.")
print(
f"Each round, send me their *raw* 4-byte addresses "
f"in little-endian form. {QUESTION_COUNT} correct answers unlock the flag."
)
print()
print("☍⟐☉⟊☽☈⟁⧋⟡☍⟐☉⟊☽☈⟁⧋⟡☍⟐☉⟊☽☈⟁⧋⟡☍⟐")
print('⊹─────────────⟡─────────────⊹')
selections = random.sample(SPELLBOOK_FUNCTIONS, QUESTION_COUNT)
success = True
for idx, symbol in enumerate(selections, 1):
target_addr = elf.symbols[symbol]
expected_bytes = p32(target_addr)
print(
f"[{idx}/{QUESTION_COUNT}] Send the 4-byte little-endian "
f"address for procedure '{symbol}'."
)
print("==> ", end='', flush=True)
user_bytes = read_exact_bytes(len(expected_bytes))
if user_bytes is None:
print("\nI needed four bytes, traveler.")
success = False
break
if user_bytes != expected_bytes:
print("\nThose aren't the right runes.")
success = False
break
if success:
print(flag)
break
print()
print("The aether rejects your incantation. Try again.\n")
except EOFError:
break
except Exception as exc:
print(exc)
break
if __name__ == "__main__":
main()
This time we need to send the program the exact raw 4 byte address in little-endian of 3 random spells from the spellbook app.
To get the locations we need to feed the app, we can use:
1
objdump -t spellbook
Near the bottom of the output we can see the 4 functions we need addresses for:
To solve this, we will need to use pwntools.
If you don’t have pwntools you will need to install it, it doens’t come with Kali by default.
You can install using:
1
2
3
sudo apt update
pipx install pwntools
pipx ensurepath
Connecting to the machine we can see it gives us a random spell from the spellbook, so we can write a pwntools script to look for that and then send the right location:
For our script we will use the below. This will read in our prompt and look for the matching spellbook item before seding the location:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
HOST = 'green-hill.picoctf.net'
PORT = 49782
p = remote(HOST, PORT)
responses = {
b"ember_sigil": p32(0x08049176),
b"glyph_conflux": p32(0x0804919a),
b"binding_word": p32(0x080491e3),
b"astral_spark": p32(0x080491c1)
}
for _ in range(3):
data = p.recvuntil(b'==> ')
for prompt, payload in responses.items():
if prompt in data:
p.send(payload)
break
print(p.recvall().decode(errors="ignore"))
p32 packs a 32-bit integer and since we didn’t specify, it uses little endian. If we needed big endian, we could specify that like this:
Make sure you use your host and port from the challenge instance you are given:
We can run the program and when we do that, we are presented with the flag!