Sunday, 27 March 2022

C++ Memory Corruption (std::string) - part 4

 

Summary

This is the next part of the C++ memory corruption series*. In this post, we'll look at corrupting the std:string object in Linux and see what exploitation primitives we can gain.

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

* https://blog.infosectcbr.com.au/2022/01/c-memory-corruption-stdvector-part-2.html 

https://blog.infosectcbr.com.au/2022/03/c-memory-corruption-stdlist-part-3.html

Author: Dr Silvio Cesare

Introduction

C++ is a common language for memory corruption. However, there is much more literature on exploiting C programs and little on 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 corrupting the std::string class and see what specific primitives we can obtain.

std::string

We note that the object stored in memory for a basic string consists firstly of the backing pointer to the string contents. Secondly, the next member is the size of the string content.

These 2 members are very useful to target in a memory corruption. If we can modify the backing pointer, we might be able to construct an arbitrary read/write primitive such that accessing the string contents points to our modified backing pointer.

If we can modify the size member, then we might be able to access the relative memory from the backing pointer base, effectively giving us a relative read/write primitive.

Corrupting the Backing Pointer

In this attack, we'll corrupt the backing pointer of the string. The backing pointer is the first member of the object. We'll make it point to an arbitrary address by corrupting p[0] and then leak the contents of that new memory address. We effectively build an arbitrary read primitive.

#include <cstdio>
#include <cstdlib>
#include <string>

static char victim[] = "secret";

int
main()
{
	std::string str;
	unsigned long *p = (unsigned long *)&str;
	unsigned long x;

	str = "12345678";
	p[0] = (unsigned long)&victim;
	printf("%s\n", str.c_str());
		
	exit(1);
}

Corrupting the String Size

The string content's size member is the 2nd member of the object. We can make this size larger than it should be, and this allows us to access out of bounds beyond the original string contents.

In the following code, p[1] is the memory corruption of the size member. The subsequent access to the string through s.str[i] goes out of bound but this is allowed since it's within the new corrupted size bounds. We effectively build a relative read primitive.

#include <cstdio>
#include <cstdlib>
#include <string>


#define N (0x40)

struct struct_s {
	std::string str;
	unsigned long victim;
};

static struct struct_s s;

int
main()
{
	unsigned long *p = (unsigned long *)&s.str;
	unsigned long x;

	s.str = std::string("12345678");
	s.victim = 0x1122334455667788;

	p[1] = N;
	for (int i = 16; i < (16 + 8); i++) {
		x <<= 8;
		x |= (unsigned char)s.str[i];
	}
	printf("%lx\n", x);
	exit(1);
}

Conclusion

This short blog post presents another technique of corrupting a C++ object and gaining a useful primitive to an attacker. In this edition, we looked at corrupting std::string objects and built arbitrary and relative read primitives.

Monday, 14 March 2022

C++ Memory Corruption (std::list) - part 3

 

Summary

This is the 3rd part of the C++ memory corruption series*. In this post, we'll look at corrupting the std::list 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

* https://blog.infosectcbr.com.au/2022/01/c-memory-corruption-stdvector-part-2.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::list class and see what specific primitives we can obtain.

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

    /// Common part of a node in the %list.
    struct _List_node_base
    {
      _List_node_base* _M_next;
      _List_node_base* _M_prev;

 ...

      struct _List_impl
      : public _Node_alloc_type
      {
        __detail::_List_node_header _M_node;

 

The 2 members _M_next and _prev begin the std::list class as an object in memory since _List_node_header derives from _List_node_base. List headers don't represent the actual data nodes of the list. However, every list has a header node.

Technique 1

For this technique, an attacker has the ability to corrupt the list object and then accesses the first node in the list through an iterator. We can make this iterator access point to an arbitrary location in memory.

The first data node of a list is implemented as header.next. We'll corrupt this next node.

#include <cstdio>
#include <cstdlib>
#include <list>

static long victim;

int
main()
{
	std::list<long> mylist;
	unsigned long *p = (unsigned long *)&mylist;
	std::list<long>::iterator iter;

	p[0] = (unsigned long)&victim - 16;

	// iter = header.next
	iter = mylist.begin();
	if (iter != mylist.end()) {
		*iter = 0x4142434445464748;
	}
	printf("%lx\n", victim);
	exit(0);
}

We can see that we overwrite p[0] which corrupts the header.next pointer. We make it point to a fake data node such that the victim data overlaps the list node's data. In the end, *iter = 0x4142434445464748 overwrites the victim.

 

Technique 2

 

The second technique I'll show is corrupting the last data node of the list. Again, we'll corrupt the std::list object in memory, then make an iterator point to the last node. We'll write with this iterator and end up writing to an arbitrary address of our choosing - in this case, the victim variable.

 

A little additional information on this technique is needed in how lists are organised. The last data node of a list is given as header.prev. In fact, the header is a stopper node in a circular linked list. This design of a linked list makes some linked list operations simpler without the need for conditional checks against NULL. When the list is empty, the next and prev pointers point back to the header/stopper.


Here is technique 2.

 

#include <cstdio>
#include <cstdlib>
#include <list>

static long victim;

int
main()
{
	std::list<long>::iterator iter;
	std::list<long> mylist;
	unsigned long *p = (unsigned long *)&mylist;

	mylist.push_back(0x11);

	p[1] = (unsigned long)&victim - 16;

	// iter = header.prev
	iter = mylist.end();
	if (iter != mylist.begin()) {
		--iter;
		*iter = 0x4142434445464748;
	}

	printf("%lx\n", victim);
	exit(0);
}

 

Conclusion

 

In this blog post, I presented 2 techniques to take a std::list object corruption and create an arbitrary r/w primitive through iterator access. This can be the basis for writing a complete exploit.




Wednesday, 2 March 2022

InfoSect announces HackerChix edition - training opportunities for women


InfoSect has long been a supporter of increasing the number of women in the Cyber Security industry, particularly in the technical streams. HackerChix was established by the InfoSect founders in 2017 to provide a community of women to support and encourage one another. It has been a regular staple of BSides Canberra every year and has resumed monthly meetings on the 2nd Monday of every month.

We wanted to do more, so InfoSect has partnered with the Australian Signals Directorate (ASD) to offer a suite of its courses in 2022. The courses will be heavily subsidised for those that identify as a woman to participate in.

Three of our most popular courses will be taught by women, for women.

  1. Reverse Engineering
  2. Code Review
  3. Network Security
The courses will be facilitated by Kylie McDevitt. Kylie has worked in technology for 22 years, the last 13 years have been in cyber security research and development. She has taught cyber security courses at UNSW Canberra and at 0xCC for the past 5 years.

We invite anyone identifying as a woman, located within Australia and with an interest or passion to go deeper into cyber security technical topics to register for the training. 

If funding is an issue, please reach out to us. In fact, please reach out with any questions to courses@infosectcbr.com.au or join us on the HackerChix channel on the InfoSect discord or BSides Canberra slack.

"The varied and in depth content gives you a rich overview, and many paths to deepen your understanding. A good amount of exercises help apply the learning, and by the last day, the open ended activities gave me a chance to see what knowledge stuck, and where I need more learning."  
- Reverse Engineering feedback, 2021 IWD subsidised attendee

 

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...