exploiting-integer-overflow-vulnerabilities
Methodology for finding and exploiting integer overflow, underflow, truncation, and signedness bugs in native code during authorized engagements, focusing on how wrapped arithmetic in size/length calculations leads to undersized allocations, oversized copies, length-check bypasses, and downstream heap/stack overflows.
适合你,如果你在授权测试中需要发现并利用整数溢出漏洞。
npx oh-my-skill add xalgord/xalgorix/exploiting-integer-overflow-vulnerabilitiescurl -fsSL https://oh-my-skill.com/install.sh | bash -s -- xalgord/xalgorix/exploiting-integer-overflow-vulnerabilitiesnpx oh-my-skill verify xalgord/xalgorix/exploiting-integer-overflow-vulnerabilities怎么用
技能原文 SKILL.md
Exploiting Integer Overflow Vulnerabilities
When to Use
- During authorized assessments of native code (C/C++/Rust/Go) where attacker-controlled integers feed size, length, count, index, or offset calculations.
- When an allocation size is computed from arithmetic (
count * elem_size,len - header,size + padding) that can wrap, producing an undersized buffer later written with the logical (un-wrapped) size — a classic integer-overflow-to-heap-overflow chain. - When a length/bounds check uses a narrow type (e.g. 1-byte size field) that can be overflowed to bypass validation while the real copy uses a larger value.
- When signed/unsigned confusion causes a negative input to be treated as a huge positive size.
Critical: Concepts/Steps Most Often Missed
- The overflow itself is rarely the impact — find the dangerous downstream use. An int wrap is only exploitable when the wrapped value sizes an allocation/copy/index. Always trace the tainted integer to a
malloc,memcpy,read, loop bound, or array index. - Truncation across type widths.
uint32_t product = (uint32_t)(count * elem_size)can wrap to a tiny value while a separatesize_t to_copy = count * elem_sizekeeps the full value — allocation is small, copy is huge. Watch every cast to a narrower type. - Underflow on subtraction is just as dangerous.
size_t payload = total_len - HEADERwithtotal_len < HEADERunderflows to a near-SIZE_MAXvalue, turning a "shrink" into an unbounded copy. - Signedness. A signed
intread withscanf("%d")then cast tounsignedmakes negatives become large positives, defeatingif (x > LIMIT)checks. - Alignment/rounding re-introduces overflow after an initial guard. Custom allocators that pass an initial wrap check then do
total += (8 - total) % totalcan wrap again for values nearSIZE_MAX, yielding a tiny chunk (e.g. CVE-2025-54957). - Go/Rust wrap silently in release/normal builds. Go has no overflow panic by default; instrument with go-panikint; Rust wraps in release unless
overflowing_*/checked arithmetic is used.
How to CONFIRM
Instrument and observe the wrap directly. In C, compile a probe with -fsanitize=integer/UBSan (-fsanitize=signed-integer-overflow,unsigned-integer-overflow) to get a runtime report at the exact operation. In a debugger, set a breakpoint after the size computation and inspect the register/variable: confirm the allocation size is small (e.g. 32) while the copy length is huge (e.g. 0xffffffffffffff f8). The exploit is confirmed when the undersized buffer's neighbor (e.g. is_admin flag, chunk metadata) is overwritten — verify the target field changed and the gated/privileged path executes.
Workflow
Step 1: Map Attacker-Controlled Integers to Size/Index Sinks
# Find suspicious arithmetic feeding allocations/copies grep -nE "malloc|calloc|memcpy|memmove|alloca|\\bread\\b" ./src/*.c # Look for: size = a*b; size = a+b; size = a-b; (uint32_t)(...); short/int casts
Reverse the size computation and the subsequent use; note the type widths at each step (8/16/32/64-bit, signed vs. unsigned).
Step 2: Compute the Wrapping Input
// 8-bit wrap: 255 + 1 == 0 // 32-bit truncation: (uint32_t)(0x100000000 * 1) == 0 -> alloc tiny, copy huge // underflow: (size_t)(8 - 16) == 0xfffffffffffffff8
Reference max values: uint8_t 255, uint16_t 65535, uint32_t 4294967295, uint64_t 18446744073709551615. Pick the input that drives the allocation size small while the copy/loop length stays large (or vice-versa).
Step 3: Trigger the Undersized Allocation + Oversized Write
from pwn import *
io = process('./int_ovf_heap_priv')
# count=2^32 truncates to 0 -> alloc32 = 32, but copy uses full size_t product
io.sendlineafter(b"Entry count: ", b"4294967296")
io.sendlineafter(b"Entry size: ", b"1")
io.recvuntil(b">> Send bundle payload on stdin (EOF to finish)...")
# offset_to_sess = 48; overflow 48 bytes then flip is_admin
payload = b"A"*48 + p32(1)
io.send(payload)
io.shutdown("send") # deliver EOF to stop the read loop
print(io.recvall(timeout=5).decode(errors="ignore")) # expect admin + FLAG
The underflow variant: send Total packet length: 8 (< HEADER 16) so payload_len = 8 - 16 underflows to a huge value, letting a bounded reader walk past the 24-byte gap and flip is_admin.
Step 4: Escalate the Downstream Corruption
Depending on what the undersized buffer is adjacent to:
- Data-oriented: flip an adjacent flag/struct field (e.g.
is_admin) — no control-flow hijack needed (PAC/CFI-safe). - Heap metadata: overwrite the next chunk's size/
fdand pivot to a heap-exploitation primitive (tcache/fastbin). - Stack: an undersized stack buffer + oversized copy becomes a stack overflow (offset/length-check bypass) — chain to ret2win/ROP.
- Length-check bypass: a 1-byte size field overflowed so a 260-byte input passes a "length 4" check, then overflows.
Key Concepts
| Concept | Description | |---------|-------------| | Overflow (wrap) | Result exceeds type max and wraps to a small value (e.g. 255+1 -> 0). | | Underflow | Unsigned subtraction below 0 wraps to near SIZE_MAX (e.g. 8-16 -> 0xff...f8). | | Truncation | Storing a wide value in a narrower type drops high bits ((uint32_t)0x100000000 -> 0). | | Signedness confusion | A signed negative reinterpreted as a large unsigned, bypassing > LIMIT checks. | | Undersized allocation | Wrapped size allocates a tiny buffer while a copy uses the full logical size. | | Rounding re-wrap | Alignment math (total += (8-total)%total) wraps again after an initial guard. | | Length-check bypass | Overflowing a small size field so an oversized input passes validation. |
Tools & Systems
| Tool | Purpose | |------|---------| | UBSan / -fsanitize=integer | Pinpoints the exact overflowing operation at runtime in C/C++ probes. | | gdb + pwndbg/GEF | Inspect computed sizes vs. copy lengths, watch the adjacent target field change. | | pwntools | Drive prompts, send wrapping inputs, p32/p64, shutdown('send') for EOF, process/remote IO. | | z3 | Solve for inputs satisfying modular-arithmetic constraints (e.g. a*x mod 2^64 == b). | | go-panikint | Forked Go toolchain that panics on wrap; surfaces silent Go overflow in fuzzing/CI. | | radare2 / Ghidra | Reverse size computations, identify type widths and the dangerous sink. | | AFL++/libFuzzer | Fuzz size/offset parsers to discover wrap-triggering inputs. |
Common Scenarios
Scenario 1: Multiply truncation -> heap overflow
count*elem_size truncated to 32 bits allocates 32 bytes, but the copy uses the full 64-bit product, overflowing into an adjacent session struct and flipping is_admin to gain privileges.
Scenario 2: Subtraction underflow -> unbounded copy
payload_len = total_len - HEADER with total_len < HEADER underflows; a bounded reader still writes far past the small buffer, overwriting a neighboring flag.
Scenario 3: 1-byte length field bypass
Only 1 byte stores a password length; overflowing it makes the program believe the length is 4 while it is actually 260, bypassing the bounds check and overflowing the stack buffer (chain to ret2win).
Scenario 4: Allocator rounding re-wrap (CVE-2025-54957)
A custom bump allocator passes an initial overflow guard, then total += (8 - total) % total wraps for values near SIZE_MAX, returning a tiny chunk; the caller writes payload_length attacker bytes past it in a deterministic per-frame slab.
Output Format
## Integer Overflow Finding **Vulnerability**: Integer overflow/truncation -> undersized allocation -> heap overflow (CWE-190 / CWE-680) **Severity**: Critical (privilege escalation / RCE) — depends on adjacent target **Binary**: ./bundle_importer (parsing of count/elem_size) ### Root Cause uint32_t product = (uint32_t)(count * elem_size); // truncates size_t to_copy = count * elem_size; // full value used in copy Input count=2^32, elem_size=1 -> alloc=32, copy=4294967296. ### Trigger & Proof Sent 48 'A' + p32(1); overwrote session.is_admin (offset 48); privileged path executed (FLAG printed). ### Impact Heap buffer overflow corrupting an adjacent privilege flag -> authorization bypass. ### Recommendation 1. Use checked/saturating arithmetic (__builtin_mul_overflow, ckd_add) for all size math. 2. Validate count/size ranges before allocation; reject when product exceeds SIZE_MAX or a sane cap. 3. Use the SAME size value for allocation and copy; avoid narrowing casts in size paths. 4. Compile with -fsanitize=integer in CI and fuzz size/offset parsers.