Flare-On 7 – Task 10

Stage 2

After we insert the first part of the flag, followed by the random string, the crackme just… freezes? Not really, but the processing of the next chunk is very slow. At first I was confused and thought that it hanged. But after a few minutes of waiting, we finally get the answer: the same as before, of course the password is invalid.

At this point we can see that the check of the next part happens directly after the memcmp passed:

Inside the function that is called next (I labeled it “decode_next_part“):

Again we can see the familiar syscall-based obfuscation. First nice is called with the index of the string that has to be decoded and retrieved. Then, this string is used in some initialization function (probably it is a key used to initialize some encryption context). Further, we have a loop of 40000 iterations. And finally a function truncate is called, triggering another syscall handler.

The “truncate” handler makes use of the decrypted data:

The output that we’ve got from the previous decoding loop, is now compared with the hardcoded buffer (at 0x81A5100). This is the buffer content:

I denoted it as a “verification buffer”:

verification buffer[32] = { 64 A0 60 02 EA 8A 87 7D 6C E9 7C E4 82 3F 2D 0C 8C B7 B5 EB CF 35 4F 42 4F AD 2B 49 20 28 7C E0 }

So, our input encrypted by our obfuscated function must be equal to this buffer… Clearly the goal of this stage is to understand what this encryption function is, and write a decryption function that will reverse the impact.

The input string is divided on 8 bytes. Each chunk of this string is passed to the processing function – along with three DWORDs from the initialized context (they will be referenced as “the triplet”).

The processing function:

This time, in addition to the obfuscation with the help of syscall interception, that we know from the previous stage, we have another layer. As we can see, there is a reference to the memory at the address 0 – that will trigger an exception.

We will see this sort of obfuscation in couple of places in this stage – sometimes with more parameters. Those exception are going to be handled by the third fork of the process – referenced as a “granchild”.

So, to understand what is really the code executed at particular places we have to refer to the “grandchild_logic”.

Understanding the grandchild

In multiple places of the child code, there are attempts to call the memory at address 0. Some examples:

This generates Segmantation fault (SIGSEGV) that is handled by the child’s “debugger” which is the grandchild. The “grandchild_logic” reads the context using PTRACE_GETREGS, and the structure user_regs_struct (defined in user.h). Then, it retrieves the parameters from the stack, and depending on them chooses the further flow of the function:

It fills the return address in the EIP and sets the registers again:

This is how the function returns to the next line after the exception has been thrown. Basically, this part works like classic nanomites.

Once we understood which handlers are being called at what point, we an reconstruct the logic that is really executed. For example, the following code:

Represents:

int __cdecl encode_chunk(_DWORD *arg_0, int arg_4)
{
    int v2 = arg_4 + arg_0[7];
    int v3 = ror(v2, arg_0[41]);
    return v3 ^ arg_0[19];
}

Because the following handlers are going to be executed:

Reconstructing the flow

At this point we could statically reconstruct the flow of this stage (commented out parts are the corresponding handlers):

https://gist.github.com/hshrzd/82db157a7c3efbf273214ba5cb278d79#file-part2_flow-cpp

The high-level overview of the encryption function can be denoted in the following way:

DWORD part1 = val[0];
DWORD part2 = val[1];
DWORD prev = 0;
for (int i = 0; i < 16; i++) {
  DWORD res = part1 ^ encode_chunk(part2, t1[i], t2[i], t3[i]);
  part1 = part2;
  part2 = res;
}
val[0] = part2;
val[1] = part1;

Before the encryption starts, there is some context initialized with the following function:

This initialization will output a buffer of values that are going to be used as previously mentioned “triplets”: constants used in the simple arithmetic operations used in the “encode_chunk” function. Before we will be able to proceed, we need to find those triplets: either generate them, or dump from the memory. I decided to dump them from the memory, as it is the less error-prone way.

Dumping the triplets

The triplets used for the arithmetic operations on the input buffer are referenced in the following function that was already mentioned:

The function making use of the triplet

It means, each time the appropriate value from the triplet is passed on the stack before the SIGSEGV is called. Those values are further retrieved inside the grandchild_logic, with the help of ptrace function:

So, in order to dump the triplets we need to just dump the parameters of the ptrace and find the relevant fragment in the dump.

As before, I tried to use LD_PRELOAD for hooking, but failed. It turns out this is because this time the function is loaded dynamically:

I was advised to just patch the original application and replace the original library libc.so.6 with my own, for example:

Then, from inside the intercepting library we can load dynamically the original ptrace from libc, so that the flow will remain unaffected. Template of the intercepting function:

long int ptrace(int request, pid_t pid, void *addr, void *data)
{
	log_before(request, pid, addr, data);
	int (*o_ptrace)(int request, pid_t pid, void *addr, void *data);

	void *handle = dlopen("libc.so.6", 1);
	o_ptrace = dlsym(handle, "ptrace");

	long int result = o_ptrace(request, pid, addr, data);

	log_after(request, result, data);
	return result;
}

The full code available here: hook.c .

Although the triplets are read only by PTRACE_PEEKDATA, I decided to dump more cases, just to see the those calls in a broader context.

Then we can use this library with the help of LD_PRELOAD with the patched binary:

LD_PRELOAD=./hook.so.6 ./break_hooked

Our hook should start printing the logs… Once we are sure that it works, we can run it again, redirecting output into a file.

LD_PRELOAD=./hook.so.6 ./break_hooked > break_log.txt

Now is time to give the input. As the first part, we have to use the password fragment:

w3lc0mE_t0_Th3_l

As the second part, we need any string that will long enough and easy to identify in the printed trace. I decided to generate a pattern with the help of metasploit framework:

$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100
 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

So, my input is:

w3lc0mE_t0_Th3_lAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

The application will run the loop 40000 times, but we should break the execution faster, otherwise the log will be really huge and inconvenient to process. The shorter version is still enough to extract triplets.

We can start by searching appropriate tags denoting operations in the log. Sample fragments given below:

   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966010 data=(nil)
   < After
       PEEKDATA: res=0x6b4e102c ,Nk <-  v2 = MEMORY[0](0x6B4E102C, arg_4, arg_0[7]);
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966014 data=(nil)
   < After
       PEEKDATA: res=0x61413161 a1Aa <- the second DWORD from the key "Aa0A a1Aa …"
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966018 data=(nil)
   < After
       PEEKDATA: res=0x4b695809    XiK <- the first chunk from the tripplet
   Before, PID=[14423] req=13
       SETREGS: addr=(nil) data=0xff965f98
   eip: 0x0804c1c9
   eax: 0xacaa896a <- the output: v2 
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966010 data=(nil)
   < After
       PEEKDATA: res=0x5816452e .EX <- v3 = MEMORY[0](0x5816452E, v2, arg_0[41]);
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966014 data=(nil)
   < After
       PEEKDATA: res=0xacaa896a j‰ª¬ <- the previous output: v2 
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966018 data=(nil)
   < After
       PEEKDATA: res=0xf    <- the second chunk from the tripplet
   Before, PID=[14423] req=13
       SETREGS: addr=(nil) data=0xff965f98
   eip: 0x0804c1ec
   eax: 0x12d55955 <- the output: v3
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966010 data=(nil)
   < After
       PEEKDATA: res=0x44de7a30 0zÞD <- return MEMORY[0](0x44DE7A30, v3, arg_0[19]);
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966014 data=(nil)
   < After
       PEEKDATA: res=0x12d55955 UYÕ <- the previous output: v3
   Before, PID=[14423] req=2
       PEEKDATA: addr=0xff966018 data=(nil)
   < After
       PEEKDATA: res=0x674a1dea êJg <- the third chunk from the tripplet
   Before, PID=[14423] req=13
       SETREGS: addr=(nil) data=0xff965f98
   eip: 0x0804c20c
   eax: 0x759f44bf <- the final output of "encode_chunk"

In order to find all the triplets, we can go through the full log and copy them till we find the point when they starts to repeat (the cycle is closed). The final list of the chunks:

DWORD t1[] = {
     0x4b695809, 0xe35b9b24, 0x71adcd92, 0x38d6e6c9, 
     0x5a844444, 0x2d422222, 0x16a11111, 0xcdbfbfa8,
     0xe6dfdfd4, 0xf36fefea, 0x79b7f7f5, 0xfa34ccda,
     0x7d1a666d, 0xf8620416, 0x7c31020b, 0x78f7b625
 };
 DWORD t2[] = {
     0xf, 0x11, 0x11, 0x11, 
     0xc, 0xc, 0xc, 0x15,
     0x15, 0x15, 0x15, 0xf,
     0xf, 0xf, 0xf, 0x12
 };
 DWORD t3[] = {
     0x674a1dea, 0xad92774c, 0x56c93ba6, 0x2b649dd3,
     0x8b853750, 0x45c29ba8, 0x22e14dd4, 0x8f47df53,
     0x47a3efa9, 0x23d1f7d4, 0x11e8fbea, 0x96c3044c,
     0x4b618226, 0xbb87b8aa, 0x5dc3dc55, 0xb0d69793
 };

Now we have all the necessary components. The key to solving this challenge is understanding that we are dealing with the Feistel cipher. This type of a cipher can be reversed following one simple principle: in both cases we need to divide the input buffer into two streams. Yet the order of processing them is reversed. The chunk encoding function remains constant in both encoding and decoding.

A diagram illustrating the Feistel cipher (source: Wikipedia)

If we apply those rules on our cipher, we get the following results:

//—–
// encode:
//
DWORD part1 = val[0];
DWORD part2 = val[1];
DWORD prev = 0;
for (int i = 0; i < 16; i++) {
DWORD res = part1 ^ encode_chunk(part2, t1[i], t2[i], t3[i]);
part1 = part2;
part2 = res;
}
val[0] = part2;
val[1] = part1;
//—–
// decode:
//
DWORD part1 = val[1];
DWORD part2 = val[0];
DWORD prev = 0;
for (int i = 15; i >= 0; i–) {
DWORD res = part2 ^ encode_chunk(part1, t1[i], t2[i], t3[i]);
part2 = part1;
part1 = res;
}
val[0] = part1;
val[1] = part2;
view raw feistel_cipher.cpp hosted with ❤ by GitHub

Now, applying the decoding buffer on the dumped verification buffer gives us the next flag part.

The complete solution is available here: solution10_p2.cpp

When we compile and run it we get the next part of the flag:

$ ./solution10_p2
4nD_0f_De4th_4nd_d3strUct1oN_4nd

If we add it to what we’ve got from the previous, it gives:

w3lc0mE_t0_Th3_l4nD_0f_De4th_4nd_d3strUct1oN_4nd

Still there is more to discover!


Stage 3

About hasherezade

Programmer and researcher, interested in InfoSec.
This entry was posted in CrackMe and tagged , . Bookmark the permalink.

1 Response to Flare-On 7 – Task 10

  1. 0xstan says:

    Great write-up !

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s