1 of 13

What is a std::string?

aturt13

2 of 13

Some obligatory memes…

3 of 13

Some obligatory memes…

4 of 13

Huh? std::string?

  • Super safe data structure from the STL (Standard Template Library)
  • Can be read into directly using std::getline which is like gets, but…
  • No more buffer overflows!
  • No more memory corruption!

std::string str;

std::getline(std::cin, str); // Read until a newline character is encountered

5 of 13

Nah?

  • Super safe data structure from the STL
  • Can be read into directly using std::getline which is like gets, but…
  • No more buffer overflows!
  • No more memory corruption!
  • Almost…

std::string str;

std::getline(std::cin, str); // Read until a newline character is encountered

6 of 13

But what is it, really?

  • SSO (small string optimization): strings shorter than 0x10 bytes are stored within the std::string object and on the stack, unless dynamically allocated
  • As a bonus, mere declaration initializes an empty string (no undefined behavior!)
  • Below is an example std::string object with the text “test”:

0xc0: 0x0000000000000000 0x0000000000000031 <- chunk size or flags

0xd0: 0x000055555556c6e0 0x0000000000000004 <- string pointer | length

0xe0: 0x0000000074736574 0x0000000000000000 <- the actual short string

7 of 13

Longer strings?

  • Longer strings are stored on the heap
  • The pointer just points outside of the chunk:

0xc0: 0x0000000000000000 0x0000000000000031 <- chunk size or flags

0xd0: 0x000055555556c700 0x000000000000001a <- string pointer | length

0xe0: 0x000000000000001e 0x0065676e6f6c6d6f <- old string content

0xf0: 0x0000000000000000 0x0000000000000031 <- another chunk metadata

0x00: 0x646e6172656d6f73 0x7265676e6f6c6d6f <- new string content

0x10: 0x6568676e69727473 0x0000000000006568

8 of 13

Longer strings?

  • Longer strings are stored on the heap
  • The pointer just points outside of the chunk
  • If a larger chunk gets allocated to store the string, its size is written to the std::string object:

0xc0: 0x0000000000000000 0x0000000000000031 <- chunk size or flags

0xd0: 0x000055555556ced0 0x0000000000000400 <- string pointer | size

0xe0: 0x0000000000000780 0x0042424242424242 <- buffer size | old string

9 of 13

How do we read to strings?

  • As mentioned previously, we use std::getline
  • If the string from stdin is short, std::getline writes directly to the std::string object
  • Otherwise, all hell breaks loose!
  • Incrementally larger buffers get allocated on the heap — 0x1f, 0x3d, 0x79, 0xf1, 0x1e1, 0x3c1, 0x781 — till we get a large enough buffer to store the whole string
  • The std::string then changes its buffer pointer to point to that chunk and changes the stored buffer size
  • The unused buffers get freed

10 of 13

But what can we do with strings?

  • A string contains a pointer and a length
  • Controlling either can give us arbitrary write or read
    • The std::string pointer is NOT protected by safelinking!
  • Controlling the buffer size gives us out of bounds write
  • Allocating a string allows us to perform both malloc and free (to some extent)
    • Can be leveraged for heap feng shui if used carefully

11 of 13

What if the string pointer gets overwritten after free?

  • Imagine the following scenario:
  • You have UAF, but the std::string gets printed before you can write to the buffer (or you have no leaks)
  • What now?
  • Is there a way to get a valid pointer at the correct location?
  • This is left for the hacker to discover.

12 of 13

How do we get heap vulnerabilities in C++, though?

  • We’ve seen that having control over std::string allows us to quickly gain fairly strong primitives
  • But C++ is supposed to be safer than C
  • It is very difficult to get a buffer overflow in modern C++
  • It is not that difficult to implement heap vulnerabilities if managing memory manually
  • Thus, C++ STL gives us so called “smart” pointers
  • If used incorrectly, they can cause memory corruption as well

13 of 13

A short note on std::string_view

  • std::string_view is commonly used instead of const std::string& in modern C++
  • It is faster, cheaper, better…
  • But it does not take ownership of the string
  • It can happen that the string gets freed, but the std::string_view attached to it remains
  • This can leak some pointerzz!