4.5 KiB
Przepełnienie całkowite
{{#include ../banners/hacktricks-training.md}}
Podstawowe informacje
W sercu przepełnienia całkowitego leży ograniczenie narzucone przez rozmiar typów danych w programowaniu komputerowym oraz interpretację danych.
Na przykład, 8-bitowa liczba całkowita bez znaku może reprezentować wartości od 0 do 255. Jeśli spróbujesz przechować wartość 256 w 8-bitowej liczbie całkowitej bez znaku, zostanie ona zawinięta do 0 z powodu ograniczenia pojemności. Podobnie, dla 16-bitowej liczby całkowitej bez znaku, która może przechowywać wartości od 0 do 65,535, dodanie 1 do 65,535 spowoduje zawinięcie wartości z powrotem do 0.
Ponadto, 8-bitowa liczba całkowita ze znakiem może reprezentować wartości od -128 do 127. Dzieje się tak, ponieważ jeden bit jest używany do reprezentacji znaku (dodatniego lub ujemnego), pozostawiając 7 bitów do reprezentacji wartości bezwzględnej. Najbardziej ujemna liczba jest reprezentowana jako -128 (binarne 10000000), a najbardziej dodatnia liczba to 127 (binarne 01111111).
Maksymalne wartości
Dla potencjalnych luk w zabezpieczeniach w sieci bardzo interesujące jest poznanie maksymalnych wspieranych wartości:
{{#tabs}} {{#tab name="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"}}
#include <stdio.h>
#include <limits.h>
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}}
Przykłady
Czyste przepełnienie
Wydrukowany wynik będzie równy 0, ponieważ przepełniliśmy char:
#include <stdio.h>
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
Rozważ sytuację, w której podpisana liczba całkowita jest odczytywana z wejścia użytkownika, a następnie używana w kontekście, który traktuje ją jako liczbę całkowitą bez znaku, bez odpowiedniej walidacji:
#include <stdio.h>
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;
}
W tym przykładzie, jeśli użytkownik wprowadzi liczbę ujemną, zostanie ona zinterpretowana jako duża liczba całkowita bez znaku z powodu sposobu interpretacji wartości binarnych, co może prowadzić do nieoczekiwanego zachowania.
Inne przykłady
-
https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html
-
Tylko 1B jest używane do przechowywania rozmiaru hasła, więc możliwe jest jego przepełnienie i sprawienie, że myśli, iż ma długość 4, podczas gdy w rzeczywistości ma 260, aby obejść ochronę sprawdzania długości
-
https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html
-
Mając kilka liczb, znajdź za pomocą z3 nową liczbę, która pomnożona przez pierwszą da drugą:
(((argv[1] * 0x1064deadbeef4601) & 0xffffffffffffffff) == 0xD1038D2E07B42569)
- https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/
- Tylko 1B jest używane do przechowywania rozmiaru hasła, więc możliwe jest jego przepełnienie i sprawienie, że myśli, iż ma długość 4, podczas gdy w rzeczywistości ma 260, aby obejść ochronę sprawdzania długości i nadpisać w stosie następną zmienną lokalną oraz obejść obie ochrony
ARM64
To nie zmienia się w ARM64, jak można zobaczyć w tym wpisie na blogu.
{{#include ../banners/hacktricks-training.md}}