Memory Layout
Unions
malloc
, calloc
, new
static
variables, string constantschar big_array[1L<<24]; // 16 MB
char huge_array[1L<<31]; // 2 GB
int global = 0;
int useless() { return 0; }
int main() {
void *phuge1, *psmall2, *phuge3, *psmall4;
int local = 0;
phuge1 = malloc(1L << 28); // 256 MB
psmall2 = malloc(1L << 8); // 256 B
phuge3 = malloc(1L << 32); // 4 GB
psmall4 = malloc(1L << 8); // 256 B
// some print statements
}
Symbol | Example Address | Location |
---|---|---|
local |
0x00007ffe4d3be87c |
stack |
phuge1 |
0x00007f7262a1e010 |
heap |
phuge3 |
0x00007f7162a1d010 |
heap |
psmall4 |
0x000000008359d120 |
heap |
psmall2 |
0x000000008359d010 |
heap |
big_array |
0x0000000080601060 |
data |
huge_array |
0x0000000000601060 |
data |
main() |
0x000000000040060c |
text |
useless() |
0x0000000000400590 |
text |
int recurse(int x) {
int a[1<<15]; // 128 KiB
printf("x = %d. a at %p\n", x, a);
a[0] = (1<<14)-1;
a[a[0]] = x-1;
if (a[a[0]] == 0) {
return -1;
}
return recurse(a[a[0]]) - 1;
}
typedef struct {
int a[2];
double d;
} struct_t;
double fun(int i) {
volatile struct_t s;
s.d = 3.14;
s.a[i] = 1073741824; // possibly out of bounds
return s.d;
}
Example inputs/outputs
fun(0) -> 3.1400000000
fun(1) -> 3.1400000000
fun(2) -> 3.1399998665
fun(3) -> 2.0000006104
fun(6) -> Stack smashing detected
fun(8) -> Segmentation fault
Results are system specific
Implementation of Linux function gets()
char *gets(char *dest) {
int c = getchar();
char *p = dest;
while (c != EOF && c != '\n') {
*p++ = c;
c = getchar();
}
*p = '\0';
return dest;
}
strcpy
, strcat
: copy strings of arbitrary lengthscanf
, fscanf
, sscanf
: when given %s
conversion specifier/* Echo Line */
void echo() {
char buf[8]; // way too small
gets(buf);
puts(buf);
}
void call_echo() {
echo();
}
echo:
subq $24, %rsp # allocate 24 bytes on stack
movq %rsp, %rdi # compute buf as %rsp
call gets # call gets
movq %rsp, %rdi # compute buf as %rsp
call puts # call puts
addq $24, %rsp # deallocate stack space
ret
Characters typed | Additional Corrupted State |
---|---|
0 - 7 | None |
9 - 23 | Unused stack space |
24 - 31 | Return address |
32+ | Saved state in caller |
A
with address of some other code S
When Q
executes ret
, will jump to other code.
void P() {
Q();
... // <- return address A
}
void Q() {
char buf[64];
gets(buf);
...
return ...;
}
void S() {
// something unexpected
}
Target C code
void smash() {
printf("I've been smashed!\n");
exit(0);
}
Target Disassembly
00000000004006c8 <smash>:
4006c8: 48 83 ec 08
Goal: craft a string that overwrites the return address with the address of the target function.
Put hex string in file smash-hex.txt
Provide as input to a vulnerable program.
A
with address of buffer B
When Q
executes ret
, will jump to exploit code
void P() {
Q();
... // <- return address A
}
int Q() {
char buf[64];
gets(buf);
...
return ...;
}
void echo() {
char buf[4];
fgets(buf, 4, stdin);
puts(buf);
}
fgets
instead of gets
strncpy
instead of strcpy
scanf
with %s
conversion specifier
fgets
to read the string%ns
where n
is a suitable integer-fstack-protector
ret
0xc3
C example code
long ab_plus_c(long a, long b, long c) {
return a*b + c;
}
Disassembly
0000000000000000 <ab_plus_c>:
0: f3 0f 1e fa endbr64
4: 48 0f af fe imul %rsi,%rdi
8: 48 8d 04 17 lea (%rdi,%rdx,1),%rax
c: c3 retq
Use tail end of existing functions
C example code
void setval(unsigned *p) {
*p = 3347663060u;
}
Disassembly
000000000000000d <setval>:
d: f3 0f 1e fa endbr64
11: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
17: c3 retq
48 89 c7
encodes movq %rax, %rdi%
Repurpose byte codes
ret
instruction
ret
in each gadget will start next one
ret
: pop address from stack and jump to that addressCan only use one field at a time
Union example: 8 bytes total storage
union U1 {
char c; // 1 byte
int i[2]; // 8 bytes
double v; // 8 bytes
} *up;
struct S1 {
char c; // 1 byte + 3 bytes padding
int i[2]; // 8 bytes + 4 bytes padding
double v; // 8 bytes
} *up;
typedef union {
float f;
unsigned u;
} bit_float_t;
float bit2float(unsigned u) {
bit_float_t arg;
arg.u = u;
return arg.f;
}
unsigned float2bit(float f) {
bit_float_t arg;
arg.f = f;
return arg.u;
}