Sunday, 2 January 2022

C++ Memory Corruption (std::vector) - part 2

Summary

This is the 2nd part of the C++ memory corruption series*. In this post, we'll look at corrupting the std::vector class in Linux and see what exploitation primitives we can gain. We'll see that we can build arbitrary read/write primitives.

* https://blog.infosectcbr.com.au/2020/08/c-memory-corruption-part-1.html

 

Author: Dr Silvio Cesare

Introduction

C++ is a common language for memory corruption. However, there is much more literature on exploiting C and not C++ programs. C++ presents new classes, objects, and data structures which can all be effectively used for building exploitation primitives.  In this post, we'll look at the std::vector class and see what specific primitives we can obtain. 

Let's start by looking at /usr/include/c++/bits/stl_vector.h

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
_GLIBCXX_BEGIN_NAMESPACE_CONTAINER

  /// See bits/stl_deque.h's _Deque_base for an explanation.
  template<typename _Tp, typename _Alloc>
    struct _Vector_base
    {
      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
        rebind<_Tp>::other _Tp_alloc_type;
      typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
        pointer;

      struct _Vector_impl_data
      {
        pointer _M_start;
        pointer _M_finish;
        pointer _M_end_of_storage;

We can see there are 3 members of importance. _M_start, _M_finish, and _M_end_of_storage.  The first 2 of these members are the ones we will corrupt and are reasonable self explanatory. They point to the beginning and just past the end of the vector's contents. To see this, we'll write a simple program and debug it.


#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>

static std::vector<long> v;

int
main()
{
	v = std::vector<long>(10);
	v[0] = 10;
	v[1] = 20;
	asm("int3");
	exit(0);
}

Now let's run it inside a debugger (GDB with the GEF plugin).

     10	 {
     11	 	v = std::vector<long>(10);
     12	 	v[0] = 10;
     13	 	v[1] = 20;
     14	 	asm("int3");
    15	 	exit(0);
     16	 }
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vector", stopped 0x5555555552e2 in main (), reason: SIGTRAP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555552e2  main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef  x/gx &v
0x555555558040 <_ZL1v>:	0x000055555556aeb0
gef  
0x555555558048 <_ZL1v+8>:	0x000055555556af00
gef  
0x555555558050 <_ZL1v+16>:	0x000055555556af00
gef  
0x555555558058:	0x0000000000000000
gef  x/gx 0x000055555556aeb0
0x55555556aeb0:	0x000000000000000a
gef  
0x55555556aeb8:	0x0000000000000014
gef  

Now inside a debugger, we can see the 3 members in the vector starting at 0x55...040. We can also view the contents of the vector starting at 0x55...eb0. In hexadecimal, 10 and 20 are 0xa and 0x14 respectively.

At this point, we have enough information to test some exploitation techniques.

Technique 1

This technique is simple. We'll overwrite the vector's _M_start member with an arbitrary address. We'll then access the vector at index 0. This is an arbitrary read/write primitive!

Here's the code:

#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>

/*
 * std::vector consists of 3 pointers
 * first pointer points to the backing contents
 */

static long x;
static std::vector<long> v;

int
main()
{
	std::vector<long>::iterator it;
	v = std::vector<long>(10);
	v[0] = 10;
	v[1] = 20;
	*(long *)&v = (long)&x; // memory corruption
	v[0] = 0x41414141;
	printf("%lx\n", x);
	_exit(0);
}
 

Technique 2

This technique is similar to the first. We'll overwrite the vector's _M_start member with arbitrary address and then use an iterator to access the vector.

#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>

/*
 * std::vector consists of 3 pointers
 * first pointer points to the backing contents
 */

static long x;
static std::vector<long> v;

int
main()
{
	std::vector<long>::iterator it;
	v = std::vector<long>(10);
	v[0] = 10;
	v[1] = 20;
	*(long *)&v = (long)&x; // memory corruption
	x = 0x42424242;
	printf("%lx\n", v[0]);
	it = v.begin();
	*it = 0x41414141;
	printf("%lx\n", x);
	_exit(0);
} 

Another variation of this technique is to build an arbitrary read use the front() method.

#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>

/*
 * std::vector consists of 3 pointers
 * first pointer points to the backing contents
 */

static long x;
static std::vector<long> v;

int
main()
{
	long y;

	std::vector<long>::iterator it;
	v = std::vector<long>(10);
	v[0] = 10;
	v[1] = 20;
	x = 0x41414141;
	*(long *)((char *)&v + 0) = (long)&x; // memory corruption
	y = v.front();
	printf("%lx\n", y);
	_exit(0);
}
 

Technique 3

Can we use the back() method for an arbitrary read? Yes. But we need to corrupt the _M_finish member. We also need this pointer to point just pass the address that we use:


#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>

/*
 * std::vector consists of 3 pointers
 * first pointer points to the backing contents
 */

static long x;
static std::vector<long> v;

int
main()
{
	long y;

	std::vector<long>::iterator it;
	v = std::vector<long>(10);
	v[0] = 10;
	v[1] = 20;
	x = 0x41414141;
	*(long *)((char *)&v + 8) = (long)&x + 8; // memory corruption
	y = v.back();
	printf("%lx\n", y);
	_exit(0);
}
 

Technique 4

Can we use the push_back() method for an arbitrary write? Yes. We need to use the _M_finish member again.

 
#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>

/*
 * std::vector consists of 3 pointers
 * first pointer points to the backing contents
 */

static long x;
static std::vector<long> v;

int
main()
{
	std::vector<long>::iterator it;
	v = std::vector<long>(10);
	v[0] = 10;
	v[1] = 20;
	*(long *)((char *)&v + 8) = (long)&x; // memory corruption
	v.push_back(0x41414141);
	printf("%lx\n", x);
	_exit(0);
}

Naturally, we can use push_front for an arbitrary write by corruption _M_start.

Conclusion

This post looked at a number of techniques that we can convert a memory corruption of std::vector into useful exploitation primitives such as arbitrary read/write, arbitrary read, and arbitrary write. Keep watching the blog for more posts on C++ memory corruption.

 

Saturday, 1 January 2022

InfoSect Training Demographics for 2021

InfoSect is an Australian based training computer security company that started in 2016, offering professional training from the end of 2018. 

In 2021 we had the following courses on offer:

  • Reverse Engineering
  • Code Review
  • Linux Heap Exploitation
  • Browser (JS Engine) Exploitation
  • IoT Security
InfoSect teaches both in-person (COVID-19 permitting) and live, interactive online training options and keeps low class sizes.  

The following are the demographics of our course offerings for 2021 taken from course booking details and post-course surveys.

Geography

InfoSect was traditionally a local training company, but opened itself up to International training in 2020 when we began offering live, online training delivery. In 2021 InfoSect's overseas' students made up just under 20% of its trainings. 

The below graph shows a breakdown of the continent the students were located. It is apparent that we are still primarily an Australian training company.

Delivery Format

In 2020 InfoSect began offering online training due to COVID-19 lockdown restrictions. This made up almost the entire offering for 2020. In 2021 we've seen a slow return to face-to-face training. Still over 75% of our trainings ofr 2021 were delivered online.

Job Title*

After every course students were given an optional survey to fill in, with an optional, open-ended question on Job Title. Below are indicative of the types of people that came and attended InfoSect training in 2021.

Qualifications

We were interested in prior qualifications of students before attending InfoSect training. Below are the answers given on our survey form.

3 Year Training Growth

The following graph shows the number of students we have trained for the past 3 years as a percentage of total.

This growth has been organic without any budget allocated to sales or marketing for InfoSect. At the end of 2021 we have 7 staff working at InfoSect, all technical practioners.

Thank you to our customers and colleagues for a wonderful year of training and development in the infosec community. We hope to continue delivering the same quality of training based on real, modern research into the future.

*Only 35% of students filled in details of their Job Title and Qualifications on the post-course surveys

Exploiting the Lorex 2K Indoor Wifi at Pwn2Own Ireland

Introduction In October InfoSect participated in Pwn2Own Ireland 2024 and successfully exploited the Sonos Era 300 smart speaker and Lor...