C++ Reverse

Time:2019-8-13

Preface

There is no detailed analysis of C++ here, focusing on the reverse of C++ standard template library and string.
STL in C++ provides a set of templates to represent containers, iterators, function objects and algorithms. A container is a unit similar to an array and can store several values.

  • STL containers are homogeneous, i.e. the types of values stored are the same.
  • Algorithms are prescriptions for specific tasks, such as sorting arrays or finding specific values in linked lists.
  • Iterators can be used to traverse objects in containers. Similar to pointers that can traverse arrays, they are generalized pointers.
  • Function objects are function-like objects, which can be class objects or function templates.

STL makes it possible to construct various containers and perform various operations.

STL is not object-oriented programming, but a different programming mode – Generic programming.

Template class vector

Constructors and Destructor

To create a vector template object, you can use the usual < type > notation to indicate the type to be used. In addition, the vector template uses dynamic memory allocation, so initialization parameters can be used to indicate how many vectors are needed.

Vector (): Create an empty vector
Vector (int nSize): Create a vector with the number of elements nSize
Vector (int nSize, const t&t): Create a vector with the number of elements nSize and the value t
Vector (const vector &): replication constructor
Vector (begin, end): Copy the elements of another array in the [begin, end] interval into the vector

Write a demo to facilitate the next analysis using IDA

#include <iostream>
#include <vector>

using namespace std;
int main(){
    // Create an int empty vector container
    vector<int> test1();
    
    // Create a vector with 5 elements
    vector<int> test2(5);

    // Create a vector with 5 elements,且值均为3
    vector<int> test3(5,3);
    
    // Assignment constructor
    vector<int> test4(test3);

    // Replicate the element path vectors of another array in the [begin, end] interval
    vector<int> test5(test4.begin(),test4.end());

    return 0;
}

Here’s the code for the decompilation of IDA F5

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbx
  __int64 v4; // rax
  char test5; // [rsp+0h] [rbp-A0h]
  char test4; // [rsp+20h] [rbp-80h]
  char test3; // [rsp+40h] [rbp-60h]
  char test2; // [rsp+60h] [rbp-40h]
  char v10; // [rsp+86h] [rbp-1Ah]
  char v11; // [rsp+87h] [rbp-19h]
  int v12; // [rsp+88h] [rbp-18h]
  char v13; // [rsp+8Fh] [rbp-11h]

    // The empty container test1 created is not shown in IDA
    //// Create vector test2 with 5 elements
  std::allocator<int>::allocator(&v10, argv);
  Std:: vector < int, std:: allocator < int >: vector (& test2, 5LL, & v10); // / Here you can see that the size of test2 is directly carried out.
  std::allocator<int>::~allocator(&v10);

    // Create vector test3 with 5 elements and 3 values
  std::allocator<int>::allocator(&v11, 5LL);
  V12 = 3; pass values using variables
  std::vector<int,std::allocator<int>>::vector(&test3, 5LL, &v12, &v11);
  std::allocator<int>::~allocator(&v11);
    // Tes4 replicates test3, slightly different from the source code
  std::vector<int,std::allocator<int>>::vector(&test4, &test3);
    // Interval is used to transfer values.
  std::allocator<int>::allocator(&v13, &test3);
  v3 = std::vector<int,std::allocator<int>>::end(&test4);
  v4 = std::vector<int,std::allocator<int>>::begin(&test4);
  std::vector<int,std::allocator<int>>::vector<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,void>(
    &test5,
    v4,
    v3,
    &v13);
  std::allocator<int>::~allocator(&v13);

  Std:: vector < int, std:: allocator < int >: ~vector (& test5); // All containers were destructed.
  std::vector<int,std::allocator<int>>::~vector(&test4);
  std::vector<int,std::allocator<int>>::~vector(&test3);
  std::vector<int,std::allocator<int>>::~vector(&test2);
  return 0;
}

summary
Learn to simplify C++ code. You can ignore it in parentheses. You just need to see what function it is.
The parameters of different vector constructors are different. The first parameter is the container name, the second parameter is size, the third parameter is the address of the initial value, and the fourth parameter is allocator for allocating memory.

v3 = std::vector<int,std::allocator<int>>::end(&v14);
v4 = std::vector<int,std::allocator<int>>::begin(&v14);
These two sentences are usually recognized. They are begin and end of the container, which is equivalent to v14.begin (); v14.end () of C++.

Iterator

Understanding iterators is the key to understanding STL. Template makes the algorithm independent of the type of data stored, and iterator makes the algorithm independent of the type of container used.
Each container type defines its own iterator type, such as vector.

Next, use an iterator to read each element in the vector

#include <iostream>
#include <vector>

using namespace std;
int main(){
    vector<int> ivec(10,1);
    // Define an ITER variable whose data type is the iterator type defined by vector < int>.
    vector<int>::iterator iter;
    for(iter=ivec.begin();iter!=ivec.end();++iter)
        * ITER = 2; // Use * to access the elements pointed to by the iterator

    // Coust_iterator can only read elements in containers, not modify them.
    vector<int>::const_iterator citer;
    for(citer=ivec.begin();citer!=ivec.end();++citer)
        cout << *citer;
    return 0;
}

Pseudo-code

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int *v3; // rax
  __int64 j; // [rsp+0h] [rbp-60h]
  __int64 i; // [rsp+8h] [rbp-58h]
  char ivec; // [rsp+10h] [rbp-50h]
  char v8; // [rsp+2Bh] [rbp-35h]
  int v9; // [rsp+2Ch] [rbp-34h]
  __int64 v10; // [rsp+30h] [rbp-30h]
  __int64 v11; // [rsp+38h] [rbp-28h]
  __int64 v12; // [rsp+40h] [rbp-20h]
  __int64 v13; // [rsp+48h] [rbp-18h]

  std::allocator<int>::allocator(&v8, argv, envp);
  v9 = 1;
  std::vector<int,std::allocator<int>>::vector(&ivec, 10LL, &v9, &v8);
  std::allocator<int>::~allocator(&v8);
  for ( i = std::vector<int,std::allocator<int>>::begin(&ivec);
        ;
        _ gnu_cxx::_normal_iterator < int*, std:: vector < int, std:: allocator < int >:: operator++(&i)// overloaded operator +
  {
    v10 = std::vector<int,std::allocator<int>>::end(&ivec);
    if ( !(unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&i, &v10) )
      break;
    *(_DWORD *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&i) = 2;
  }

  v12 = std::vector<int,std::allocator<int>>::begin(&ivec);
  __gnu_cxx::__normal_iterator<int const*,std::vector<int,std::allocator<int>>>::__normal_iterator<int *>(&v11, &v12);
  for ( j = v11; ; __gnu_cxx::__normal_iterator<int const*,std::vector<int,std::allocator<int>>>::operator++(&j) )
  {
    v13 = std::vector<int,std::allocator<int>>::end(&ivec);
    if ( !(unsigned __int8)__gnu_cxx::operator!=<int const*,int *,std::vector<int,std::allocator<int>>>(&j, &v13) )
      break;
    v3 = (unsigned int *)__gnu_cxx::__normal_iterator<int const*,std::vector<int,std::allocator<int>>>::operator*(&j);
    std::ostream::operator<<(&std::cout, *v3);
  }
  std::vector<int,std::allocator<int>>::~vector(&ivec);
  return 0;

For loops change a lot, but it’s logical to look at them carefully.

Insert iterators front_insert and back_insert

Defined in the < iterator > header file

Back_inserter: Create an iterator using push_back
Inserter: This function accepts the second parameter, which must be an iterator pointing to a given container. The element is inserted before the element represented by the given iterator.
Front_inserter: Create an iterator that uses push_front (elements are always inserted before the first element of the container)
Because the list container type is a bidirectional linked list and supports push_front and push_back operations, the list type is selected to test the three iterators.

list<int> lst = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> lst2 ={10}, lst3={10},lst4={10};
copy(lst.cbegin(), lst.cend(), back_inserter(lst2));
// lst2 contains 10,1,2,3,4,5,6,7,8,9
copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));
// lst3 contains 1,2,3,4,5,6,7,8,9,10
copy(lst.cbegin(), lst.cend(), front_inserter(lst4));
// lst4 contains 9,8,7,6,5,4,3,2,1,10

It’s easy to see the difference between the three. Just grab the key functions. Ibid., no longer show pseudo-code.

Several Most Important Operations of Vector Objects

1. v. push_back (t) The size of the container increases by adding a data value of t at the end of the container.
   2. v. size () returns the number of data in the container, and size_type returns the value of size_type defined by the corresponding vector class.
   3. v. empty () Determine whether the vector is empty
   4. V [n] or v. at (n) returns the element with N in v, which is safer
   5. v. insert (pointer, number, content) inserts the content of number content into the position pointer points to in V.
   6. There are also v. insert (pointer, content), v. insert (pointer, a [2], a [4]) to insert a [2] to a [4] three elements.
   7. v. pop_back() deletes the last element of the container and does not return it.
   8. v. erase (pointer1, pointer2) deletes the elements between pointer1 and pointer2 (including those referred to by pointer1).
   9. After deleting an element from the vector, all the elements after this position need to move forward. Although the current iterator position does not automatically add 1, because the successive elements move forward, it is equivalent to the iterator automatically pointing to the next position.
   10.v1==v2 to determine whether V1 and V2 are equal.
   11. =,<,<=,>,>= Keep these operators in their customary meanings.
   12. vector < typeName >:: iterator P = v1. begin (); the initial value of P points to the first element of v1. * P takes the value of the pointed element.
   13. For const vector < typeName >, you can only access it with a pointer of type const_iterator.
   14.p = v1.end (); P points to the next position of the last element of v1.
   15. v. clear () deletes all elements in the container.
   16. v. resize (2v. size) or v. resize (2v. size, 99) doubles the capacity of V (and initializes the value of the new element to 99)

West Lake on Easy CPP

int __cdecl main(int argc, const char **argv, const char **envp)
{
    // Because of too much code, I deleted part of the variable definition code. 
  v15 = argv;
  v26 = __readfsqword(0x28u);
  Std:: vector < int, std:: allocator < int >: vector (_int64) & v18); // Created five vector containers
  std::vector<int,std::allocator<int>>::vector((__int64)&v19);
  std::vector<int,std::allocator<int>>::vector((__int64)&v20);
  std::vector<int,std::allocator<int>>::vector((__int64)&v21);
  std::vector<int,std::allocator<int>>::vector((__int64)&v22);
  for ( i = 0; i <= 15; ++i )
  {
    scanf("%d", &v25[4 * i], v15);
    std::vector<int,std::allocator<int>>::push_back(&v19, &v25[4 * i]);  //v19.push_back[i];
  }
  for ( j = 0; j <= 15; ++j )
  {
    LODWORD(v24) = fib(j);
    Std:: vector < int, std:: allocator < int >:: push_back (& v18, & v24); // a recursive function assigns value to container v18
  }
  std::vector<int,std::allocator<int>>::push_back(&v20, v25);  //v20.push_back(v25[0]);
  V4 = std:: back_inserter < std:: vector < int, std:: allocator < int >(& v20); // Create an iterator using push_back
  v5 = std::vector<int,std::allocator<int>>::end(&v19);  //v5 = v19.end()
  v24 = std::vector<int,std::allocator<int>>::begin(&v19);  //v24 = v19.begin()
  v6 = __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator+(&v24, 1LL);  //v6 = v24.begin() + 1
  std::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#1}>(
    V6, //v19.begin()+1 container's second element
    v5,                  //v19.end()
    V4, // Containers with only the first element input
    (_int64) v25/// Enter the value of the first element
    ) The // function is used to add the value of the first number to each number starting with the second number.  
  Std:: vector < int, std:: allocator < int >: vector (_int64) & v23); // Create a new container V23
  v7 = std::vector<int,std::allocator<int>>::end(&v20);  //v7 = v20.end()
  v8 = std::vector<int,std::allocator<int>>::begin(&v20);  //v8 = v20.begin()
  std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::vector<int,std::allocator<int>>,main::{lambda(std::vector<int,std::allocator<int>>,int)#2}>(
    (__int64)&v24,          //v19.begin()
    v8,                     //v20.begin()
    v7,                     //v20.end()
    (_int64) & v23, /// newly created container
    v9,
    v10,
    V3; // Inverted Function
  Std:: vector < int, std:: allocator < int >: operator = (& v21, & v24); // overloaded operator =, the value of container V24 is assigned to container v21
  std::vector<int,std::allocator<int>>::~vector(&v24);
  std::vector<int,std::allocator<int>>::~vector(&v23);
  If ((unsigned int8) std:: operator!= < int, std:: allocator < int > (& v21, & v18)) // v21=== v18, flag can be obtained.
  {
    puts("You failed!");
    exit(0);
  }
  std::back_inserter<std::vector<int,std::allocator<int>>>(&v22);
  v11 = std::vector<int,std::allocator<int>>::end(&v19);
  v12 = std::vector<int,std::allocator<int>>::begin(&v19);
  std::copy_if<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#3}>(v12);
  puts("You win!");
  printf("Your flag is:flag{", v11, v15);
  v23 = std::vector<int,std::allocator<int>>::begin(&v22);
  v24 = std::vector<int,std::allocator<int>>::end(&v22);
  while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v23, &v24) )
  {
    v13 = (unsigned int *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v23);
    std::ostream::operator<<(&std::cout, *v13);
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v23);
  }
  putchar(125);
  std::vector<int,std::allocator<int>>::~vector(&v22);
  std::vector<int,std::allocator<int>>::~vector(&v21);
  std::vector<int,std::allocator<int>>::~vector(&v20);
  std::vector<int,std::allocator<int>>::~vector(&v19);
  std::vector<int,std::allocator<int>>::~vector(&v18);
  return 0;

Analytical functionstd::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#1}>

__int64 __fastcall std::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#1}>(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
  int *v4; // rax
  __int64 v5; // rax
  __int64 v7; // [rsp+0h] [rbp-30h]
  __int64 v8; // [rsp+8h] [rbp-28h]
  __int64 v9; // [rsp+10h] [rbp-20h]
  __int64 v10; // [rsp+18h] [rbp-18h]
  int v11; // [rsp+24h] [rbp-Ch]
  unsigned __int64 v12; // [rsp+28h] [rbp-8h]

  v10 = a1;
  v9 = a2;
  v8 = a3;
  v7 = a4;
  v12 = __readfsqword(0x28u);
  while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v10, &v9) )
  {
    v4 = (int *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v10);
    v11 = main::{lambda(int)#1}::operator() const((_DWORD **)&v7, *v4);
    v5 = std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator*(&v8);
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator=(v5, &v11);
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v10);
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator++(&v8);
  }
  return v8;

V10 points to the second value of container v19, and overloads operator ++, which implements traversal function. v19 stores all the input values.
V7 is the first input value
Let’s take a look at main:{lambda (int)#1}:: operator () const (_DWORD)*)&v7, V4) How to achieve it

__int64 __fastcall main::{lambda(int)#1}::operator() const(_DWORD **a1, int a2)
{
  return (unsigned int)(**a1 + a2);
}

v11 = v7 + v4;

std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::vector<int,std::allocator<int>>,main::{lambda(std::vector<int,std::allocator<int>>,int)#2}>

__int64 __fastcall std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::vector<int,std::allocator<int>>,main::{lambda(std::vector<int,std::allocator<int>>,int)#2}>(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, char a7)
{
  int v7; // ebx
  __int64 v9; // [rsp+0h] [rbp-70h]
  __int64 v10; // [rsp+8h] [rbp-68h]
  __int64 v11; // [rsp+10h] [rbp-60h]
  __int64 v12; // [rsp+18h] [rbp-58h]
  char v13; // [rsp+20h] [rbp-50h]
  char v14; // [rsp+40h] [rbp-30h]
  unsigned __int64 v15; // [rsp+58h] [rbp-18h]

  V12 = a1; // v19. begin () v19 has been computed by this time
  V11 = a2; // v20. begin () V20 stores the first value
  v10 = a3;      //v20.end()
  V9 = a4; // newly created container
  v15 = __readfsqword(0x28u);
  while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v11, &v10) )
  {
    v7 = *(_DWORD *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v11);
    std::vector<int,std::allocator<int>>::vector(&v13, v9);
    main::{lambda(std::vector<int,std::allocator<int>>,int)#2}::operator() const(
      (__int64)&v14,
      (__int64)&a7,
      (__int64)&v13,
      v7);
    std::vector<int,std::allocator<int>>::operator=(v9, &v14);
    std::vector<int,std::allocator<int>>::~vector(&v14);
    std::vector<int,std::allocator<int>>::~vector(&v13);
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v11);
  }
  std::vector<int,std::allocator<int>>::vector(v12, v9);
  return v12;

You can see that each cycle container V13 and V14 is destructed, leaving only the new container V9 created in main.

__int64 __fastcall main::{lambda(std::vector<int,std::allocator<int>>,int)#2}::operator() const(__int64 a1, __int64 a2, __int64 a3, int a4)
{
  __int64 v4; // r12
  __int64 v5; // rbx
  __int64 v6; // rax
  int v8; // [rsp+4h] [rbp-3Ch]
  __int64 v9; // [rsp+8h] [rbp-38h]
  __int64 v10; // [rsp+10h] [rbp-30h]
  __int64 v11; // [rsp+18h] [rbp-28h]
  unsigned __int64 v12; // [rsp+28h] [rbp-18h]

  v11 = a1;
  v10 = a2;
  v9 = a3;
  v8 = a4;
  v12 = __readfsqword(0x28u);
  std::vector<int,std::allocator<int>>::vector(a1);
  std::vector<int,std::allocator<int>>::push_back(a1, &v8);
  V4 = std:: back_inserter < std:: vector < int, std:: allocator < int > (v11); // Defines a back_inserter insert iterator
  v5 = std::vector<int,std::allocator<int>>::end(v9);
  v6 = std::vector<int,std::allocator<int>>::begin(v9);
  std::copy<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>>(
    v6,
    v5,
    v4);
  return v11;
}

Combining the above iterator back_inserter with the knowledge of copy function
Container V9 is not destructed, inserting a value in each loop to complete the inversion of the v19 container.

summary

The general idea of encrypting programs is

  1. Add input [0] to each value starting with the second value for input []
  2. Make an inversion of the encrypted input
  3. The final input is the same as the recursive value

Recommended Today

Implementation of PHP Facades

Example <?php class RealRoute{ public function get(){ Echo’Get me’; } } class Facade{ public static $resolvedInstance; public static $app; public static function __callStatic($method,$args){ $instance = static::getFacadeRoot(); if(!$instance){ throw new RuntimeException(‘A facade root has not been set.’); } return $instance->$method(…$args); } // Get the Facade root object public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } protected […]