sar's blog


Home

Prisoners_dilemma crackme

This is a solution to the 4aca7f6c's crackme prisoners_dilemma.

The crackme is available here

It took me almost 4 days to solve prisonner_dilemma. 4aca7f6c set its difficulty to 3, but he was very modest. I hesitate to set it to 4 or 5 when I managed to solve it. In order to see the correct message printed on your screen, you need to solve 3 little maths game. The very interesting part of this crackme is that theses little games are really easy to understand, but pretty difficult to solve within the constraints of the crackme. I'll decompose this crackme in 6 steps:

First things first, let's start with the encryption.

Encryption and mixup

The program isn't obfuscated at all, and we can clearly see the control flow. It starts by printing the ASCII-ART, ask for the input, and read 0x100 bytes on the standard input. The program then checks if the index of the first "\n" is 42, and exit if not. If the first occurence of "\n" is at index 42, the program map 0x400 bytes with the permissions RWX. Then the program calls the function "encrypt_string", with our input as second argument.

The algorithm is easy to understand, here's how it works:

I did not try to understand how this algorithm was working. I just noticed a small but important thing: If we consider the input as two distincts parts (B1 and B2), the modification of a byte at the index i (for B1 or B2), will not modify the previous bytes at the index <= i (for B1 and B2). It'll allow us to bruteforce the solution at the end.

To be a bit more precise, let's imagine this hexstring and its encrypted version:

index       0 1 2 3 4 5 6
input       41424344454647
encrypted   fadbe34522254e

Then if we change the 5th byte of the input, only the 5th and the 6th bytes will be modified in the encrypted output.

Now that it's clear, let's keep moving. Our input is now encrypted, the program enter the function game_1. In the three differents games, the first function called is the one I called "Check_and_mixup".

This function (which is at 0x400b2d) accept two arguments:

It perform a check on a part of the encrypted input, then mixup and array regarding the encrypted part. Again, here's how it works.

The check is very simple:

for i in range(len(part)):
    if part[i] > i:
        return false
return true

This means that the bytes at position 0, 0xd, and 0x1b are 0. The bytes at position 1, 0xe and 0x1c are 0 or 1. The bytes at position 2, 0xf and 0x1d are 0, 1 or 2. Etc.

The mixup is pretty simple too. Once the check is passed, the program loop on each bytes of the encrypted part starting at the last ones, and swap the byte at the index i with the byte at the index encrypted_part[i].

Here's the algorithm:

i = len(encrypted_part) - 1
while i >= 0:
    swap(array[i], array[encrypted_part[i]])
    i -= 1

This function give us an unique permutation of the original array. Let's keep moving to the next part.

Game's hidden codes

Now that our input has generated an unique permutation of the original array, this array will be used to modify the control flow of the program.

In each game, a function is called just after the Check_and_mixup. I named it Modify_hidden_code.

Let's talk about the games' hidden codes. In the .rodata section, there are 3 arrays of byte 0x4017c0 -> game_3, 0x4019a0 -> game_2, 0x401ba0 -> game_1.

these arrays are executable code. Each array is composed by:

- An initializer
- A dispatcher
- Some blocks of code which I call "meta-instructions"

these meta-instructions all look the same in the original on-disk file. We can enumerate then. Let's say that for a array, the block 0 will be the one at the lowest address, followed by the block 1, etc.

- 1 or 2 operation on registers
- mov r13, 0
- jump dispatcher

The dispatcher jump to the next meta-instruction, regarding the value in r13. Obviously, at runtime, the 0 of the mov r13, 0 instructions are modified, regarding the unique permutation generated by the encrypted_input !

If the array generated by the encrypted input is [0xb, 0x1, 0x4, 0x3, 0x6, 0x00, 0x2, 0x7, 0x8, 0x9, 0xc, 0xa, 0x5]

Then the control flow will be:

We now have all elements to start to look at the games.

GAME 1

Let's start with game_1 ! Once the Check_and_mixup and Modify_hidden_code are executed, the program initialize two arrays of 7 32-bit integer. - The first array is populated with random values % 7. - The second one is populated with -1 in the 7 32-bits values.

Then the program call the hidden_code_game_1, which is below. When the hidden code returns, it checks for each 7 32-bits values of both arrays if array[i] == array2[i]. If this assertion is false for all the 7 values, the input is wrong. In order to really validate that our permutation (= controlflow) is the good one, the program does this 50 times.

Let's have a look at the hidden code:

  401ba0:   41 50                   push   r8
  401ba2:   49 89 d0                mov    r8,rdx
  401ba5:   41 51                   push   r9
  401ba7:   49 89 d1                mov    r9,rdx
  401baa:   41 54                   push   r12
  401bac:   4d 31 e4                xor    r12,r12
  401baf:   41 55                   push   r13
  401bb1:   4c 63 e9                movsxd r13,ecx
  401bb4:   41 56                   push   r14
  401bb6:   4c 8d 35 40 01 00 00    lea    r14,[rip+0x140]
  401bbd:   49 81 fc 00 04 00 00    cmp    r12,0x400
  401bc4:   0f 8d 1c 00 00 00       jge    401be6 
  401bca:   4f 63 2c ae             movsxd r13,DWORD PTR [r14+r13*4]
  401bce:   4d 01 f5                add    r13,r14
  401bd1:   41 ff e5                jmp    r13
  401bd4:   49 ff c4                inc    r12
  401bd7:   41 83 fd 0d             cmp    r13d,0xd
  401bdb:   0f 8d 05 00 00 00       jge    401be6 
  401be1:   e9 d7 ff ff ff          jmp    401bbd
  401be6:   83 c8 ff                or     eax,0xffffffff
  401be9:   41 5e                   pop    r14
  401beb:   41 5d                   pop    r13
  401bed:   41 5c                   pop    r12
  401bef:   41 59                   pop    r9
  401bf1:   41 58                   pop    r8
  401bf3:   c3                      ret    
  401bf4:   42 8d 04 01             lea    eax,[rcx+r8*1]   ; block_0
  401bf8:   41 bd 00 00 00 00       mov    r13d,0x0
  401bfe:   e9 d1 ff ff ff          jmp    401bd4 
  401c03:   01 d0                   add    eax,edx      ; block_1
  401c05:   99                      cdq    
  401c06:   85 c9                   test   ecx,ecx
  401c08:   0f 84 d8 ff ff ff       je     401be6 
  401c0e:   f7 f9                   idiv   ecx
  401c10:   41 bd 00 00 00 00       mov    r13d,0x0
  401c16:   e9 b9 ff ff ff          jmp    401bd4 
  401c1b:   41 39 c8                cmp    r8d,ecx      ; block_2
  401c1e:   0f 8d 0f 00 00 00       jge    401c33 
  401c24:   42 89 14 86             mov    DWORD PTR [rsi+r8*4],edx
  401c28:   41 bd 00 00 00 00       mov    r13d,0x0
  401c2e:   e9 a1 ff ff ff          jmp    401bd4 
  401c33:   31 c0                   xor    eax,eax      ; block_3
  401c35:   41 5e                   pop    r14
  401c37:   41 5d                   pop    r13
  401c39:   41 5c                   pop    r12
  401c3b:   41 59                   pop    r9
  401c3d:   41 58                   pop    r8
  401c3f:   c3                      ret    
  401c40:   41 bd 00 00 00 00       mov    r13d,0x0     
  401c46:   e9 89 ff ff ff          jmp    401bd4 
  401c4b:   49 ff c1                inc    r9       ; block_4
  401c4e:   e9 42 00 00 00          jmp    401c95 
  401c53:   41 bd 00 00 00 00       mov    r13d,0x0
  401c59:   e9 76 ff ff ff          jmp    401bd4 
  401c5e:   45 31 c9                xor    r9d,r9d      ; block_5
  401c61:   31 d2                   xor    edx,edx
  401c63:   41 bd 00 00 00 00       mov    r13d,0x0
  401c69:   e9 66 ff ff ff          jmp    401bd4 
  401c6e:   45 39 c8                cmp    r8d,r9d      ; block_6
  401c71:   0f 84 d4 ff ff ff       je     401c4b 
  401c77:   41 bd 00 00 00 00       mov    r13d,0x0
  401c7d:   e9 52 ff ff ff          jmp    401bd4 
  401c82:   49 ff c0                inc    r8       ; block_7
  401c85:   e9 47 00 00 00          jmp    401cd1 
  401c8a:   41 bd 00 00 00 00       mov    r13d,0x0
  401c90:   e9 3f ff ff ff          jmp    401bd4 
  401c95:   44 39 c9                cmp    ecx,r9d      ; block_8
  401c98:   0f 8e 56 ff ff ff       jle    401bf4 
  401c9e:   41 bd 00 00 00 00       mov    r13d,0x0
  401ca4:   e9 2b ff ff ff          jmp    401bd4 
  401ca9:   89 d1                   mov    ecx,edx      ; block_9
  401cab:   45 31 c0                xor    r8d,r8d
  401cae:   41 bd 00 00 00 00       mov    r13d,0x0
  401cb4:   e9 1b ff ff ff          jmp    401bd4 
  401cb9:   29 d0                   sub    eax,edx      ; block_A
  401cbb:   99                      cdq    
  401cbc:   85 c9                   test   ecx,ecx
  401cbe:   0f 84 22 ff ff ff       je     401be6 
  401cc4:   f7 f9                   idiv   ecx
  401cc6:   41 bd 00 00 00 00       mov    r13d,0x0
  401ccc:   e9 03 ff ff ff          jmp    401bd4 
  401cd1:   41 39 c8                cmp    r8d,ecx      ; block_B
  401cd4:   0f 8d 59 ff ff ff       jge    401c33 
  401cda:   41 bd 00 00 00 00       mov    r13d,0x0
  401ce0:   e9 ef fe ff ff          jmp    401bd4 
  401ce5:   44 39 c9                cmp    ecx,r9d      ; block_C
  401ce8:   0f 8e 45 ff ff ff       jle    401c33 
  401cee:   42 8b 04 8f             mov    eax,DWORD PTR [rdi+r9*4]
  401cf2:   41 bd 00 00 00 00       mov    r13d,0x0

What I did is pretty simple: moving the value in array[0] to array2[0]. This is possible with the following sequence of blocks:

0x09,0x05,0x0c,0x0a,0x02,0x03

We only used 6 of the 13 given blocks, so we can just append the others blocks.

We now have our first permutation array ! We need to find the input, that will produces the permutation:

[0x09,0x05,0x0c,0x0a,0x02,0x03,0x00,0x06,0x01,0x08,0x04,0x07,0x0b]

NOTE: After some discussions with 4aca7f6c, this solution wasn't intended, and the original problem was refering to the rainbow_hats_puzzle, which is well explained here.

Anyway, that was a good and pretty easy start, and the second game will be WAY much harder.

GAME 2

In my opinion, game_2 was the hardest. This time, the program initialize an array of 50 elements. It starts by setting array[i] = i, then it mix-up the array using the Fisher-Yates algorithm. It is well explained everywhere on internet. I will not describe it because the important thing is that the program initialize an array of 50 elements, containing the number from 0 to 49, randomly shuffled.

Then, the program call the hidden code with a pointer on this array as an input.

When the hidden code stop, the program does the following:

r = rand() % 50
c = 25
i = r
while c >= 0:
    if array[i] == r:
        return good
    i = array[i]
return wrong

The code is terribly simple. What it does is taking a random integer r between 0 and 49, and then check in the array if the element at index r is r. If not the new index to look at is the element that was compared to r. If it has to do this less than 25 times to find the value r, you're good. If not, you lose. Here's a scheme to fully understand how it works.


            +------>24 +--------> 37 +----------12+
            |                                     |
            +                                     |
+--------> 17                                     |
            ^                                     v
            |                                     19
            |                                     +
            |                                     |
            |                                     |
            +                                     |
            7<-------------+48<--------+3<--------+

In this exemple, the random number picked by the program is 17. At the index 17, there's 24. At the index 24, there's 37, etc. It takes the program 8 step to find the random value 17.

In this exemple, it works. But most of the time, the Fisher-Yates algorithm will shuffle the array in a way that there will be a linked-list containing more than 25 elements. To be precise, the algorithm will create N linked lists, where 1 <= N <= 50. (50 means that the list is not shuffled and each element is equal to its index). Obviously, the sum lengths of the N linked list is 50.

So what do we have to do ? As in game_1, we have to find an algorithm that take an array as input, and shuffle the array in a way that all looped linked-list with length > 25 will be removed. The number hasn't been randomly chose. It's the exact half of the array's length. It means that if there's a looped linked list containing more than 25 elements, it's the only one in the array. It's obvious, the sum of all lengths is exactly equal to 50, so if a list contains at least 26 elements, the others cannot contain more than 24.

Let's look at the hidden code:

  4019a0:   41 50                   push   r8
  4019a2:   4d 31 c0                xor    r8,r8
  4019a5:   41 51                   push   r9
  4019a7:   4d 31 c9                xor    r9,r9
  4019aa:   41 52                   push   r10
  4019ac:   4d 31 d2                xor    r10,r10
  4019af:   41 54                   push   r12
  4019b1:   4d 31 e4                xor    r12,r12
  4019b4:   41 55                   push   r13
  4019b6:   4c 63 ea                movsxd r13,edx
  4019b9:   41 56                   push   r14
  4019bb:   4c 8d 35 4c 01 00 00    lea    r14,[rip+0x14c] 
  4019c2:   89 f2                   mov    edx,esi
  4019c4:   31 c9                   xor    ecx,ecx
  4019c6:   49 81 fc 00 20 00 00    cmp    r12,0x2000
  4019cd:   0f 8d 1c 00 00 00       jge    4019ef 
  4019d3:   4f 63 2c ae             movsxd r13,DWORD PTR [r14+r13*4]
  4019d7:   4d 01 f5                add    r13,r14
  4019da:   41 ff e5                jmp    r13
  4019dd:   49 ff c4                inc    r12
  4019e0:   41 83 fd 0e             cmp    r13d,0xe
  4019e4:   0f 8d 05 00 00 00       jge    4019ef 
  4019ea:   e9 d7 ff ff ff          jmp    4019c6 
  4019ef:   83 c8 ff                or     eax,0xffffffff
  4019f2:   41 5e                   pop    r14
  4019f4:   41 5d                   pop    r13
  4019f6:   41 5c                   pop    r12
  4019f8:   41 5a                   pop    r10
  4019fa:   41 59                   pop    r9
  4019fc:   41 58                   pop    r8
  4019fe:   c3                      ret    
  4019ff:   48 63 0c 8f             movsxd rcx,DWORD PTR [rdi+rcx*4] ; b_0
  401a03:   41 bd 00 00 00 00       mov    r13d,0x0
  401a09:   e9 cf ff ff ff          jmp    4019dd 
  401a0e:   8b 14 8f                mov    edx,DWORD PTR [rdi+rcx*4] ; b_1
  401a11:   42 31 14 8f             xor    DWORD PTR [rdi+r9*4],edx
  401a15:   42 8b 14 8f             mov    edx,DWORD PTR [rdi+r9*4]
  401a19:   31 14 8f                xor    DWORD PTR [rdi+rcx*4],edx
  401a1c:   8b 14 8f                mov    edx,DWORD PTR [rdi+rcx*4]
  401a1f:   42 31 14 8f             xor    DWORD PTR [rdi+r9*4],edx
  401a23:   e9 65 00 00 00          jmp    401a8d 
  401a28:   41 bd 00 00 00 00       mov    r13d,0x0
  401a2e:   e9 aa ff ff ff          jmp    4019dd 
  401a33:   41 ba 01 00 00 00       mov    r10d,0x1          ; b_2
  401a39:   41 bd 00 00 00 00       mov    r13d,0x0
  401a3f:   e9 99 ff ff ff          jmp    4019dd 
  401a44:   4d 63 c8                movsxd r9,r8d            ; b_3
  401a47:   41 bd 00 00 00 00       mov    r13d,0x0
  401a4d:   e9 8b ff ff ff          jmp    4019dd 
  401a52:   41 f6 c2 01             test   r10b,0x1          ; b_4
  401a56:   0f 84 e8 ff ff ff       je     401a44 
  401a5c:   41 bd 00 00 00 00       mov    r13d,0x0
  401a62:   e9 76 ff ff ff          jmp    4019dd 
  401a67:   ff c2                   inc    edx           ; b_5
  401a69:   e9 87 00 00 00          jmp    401af5 
  401a6e:   41 bd 00 00 00 00       mov    r13d,0x0
  401a74:   e9 64 ff ff ff          jmp    4019dd 
  401a79:   41 39 c2                cmp    r10d,eax          ; b_6
  401a7c:   0f 8c e5 ff ff ff       jl     401a67 
  401a82:   41 bd 00 00 00 00       mov    r13d,0x0
  401a88:   e9 50 ff ff ff          jmp    4019dd 
  401a8d:   31 c0                   xor    eax,eax           ; b_7
  401a8f:   41 5e                   pop    r14
  401a91:   41 5d                   pop    r13
  401a93:   41 5c                   pop    r12
  401a95:   41 5a                   pop    r10
  401a97:   41 59                   pop    r9
  401a99:   41 58                   pop    r8
  401a9b:   c3                      ret    
  401a9c:   41 bd 00 00 00 00       mov    r13d,0x0
  401aa2:   e9 36 ff ff ff          jmp    4019dd 
  401aa7:   41 ff c2                inc    r10d          ; b_8
  401aaa:   41 bd 00 00 00 00       mov    r13d,0x0
  401ab0:   e9 28 ff ff ff          jmp    4019dd 
  401ab5:   4e 63 04 87             movsxd r8,DWORD PTR [rdi+r8*4]   ; b_9
  401ab9:   41 bd 00 00 00 00       mov    r13d,0x0
  401abf:   e9 19 ff ff ff          jmp    4019dd 
  401ac4:   42 3b 14 8f             cmp    edx,DWORD PTR [rdi+r9*4]  ; b_A
  401ac8:   0f 85 d9 ff ff ff       jne    401aa7 
  401ace:   41 bd 00 00 00 00       mov    r13d,0x0
  401ad4:   e9 04 ff ff ff          jmp    4019dd 
  401ad9:   89 f0                   mov    eax,esi           ; b_B
  401adb:   41 bd 00 00 00 00       mov    r13d,0x0
  401ae1:   e9 f7 fe ff ff          jmp    4019dd 
  401ae6:   d1 e8                   shr    eax,1             ; b_C
  401ae8:   31 d2                   xor    edx,edx
  401aea:   41 bd 00 00 00 00       mov    r13d,0x0
  401af0:   e9 e8 fe ff ff          jmp    4019dd 
  401af5:   39 f2                   cmp    edx,esi           ; b_D
  401af7:   0f 8d 90 ff ff ff       jge    401a8d 
  401afd:   48 63 ca                movsxd rcx,edx
  401b00:   4c 63 c2                movsxd r8,edx
  401b03:   41 bd 00 00 00 00       mov    r13d,0x0
  401b09:   e9 cf fe ff ff          jmp    4019dd 

I commented the blocks numbers, as in game_1's hidden code. Let's have a look at the blocks. The block_1 is important, it's the one that allow us to swap the element at index rcx and r9. But then after the execution of this block, the program jumps to block_7 which exit. It means that we need to satisfy the conditions by swapping two elements just one time.

Finding the algorithm was really hard for me and took me almost 2 days. I was missing a small thing.

Here's the first idea I had in python:

for i, c in enumerate(array):
    len, ele25 = count_len_list_and_get_25th_element(array[i])
    if len > 25:
        swap(array, i, ele25)
        break

The algorithm is really simple, and it totally works in python ! With just one swap, the length of the longest linked-list in the array is <= 25!

The problem is that it's IMPOSSIBLE to implement this with the blocks we have in the hidden code. You cannot get the 25th element of the list.

The solution is not far from this algo, but is way more subtle. When you're searching for the r value in the linked list, just increment a second pointer when two elements are find. So you'll need two pointers, the one which count the elements, and the one which is incremented when the counter count 2!

Another graph to explain:

When you start:

counter_ptr      +------>24 +--------> 37 +--------> 12+
semi_ptr         |                                     |
                 +                                     |
     +--------> 17                                     |
                 ^                                     v
                 |                                     19
                 |                                     +
                 |                                     |
                 |                                     |
                 +                                     |
                 7<-------------+48<----------+3<------+

Then

                      counter_ptr

                       +
                       |
                       |
                       v
semi_ptr      +------>24 +--------> 37 +--------> 12+
              |                                     |
              +                                     |
  +--------> 17                                     |
              ^                                     v
              |                                     19
              |                                     +
              |                                     |
              |                                     |
              +                                     |
              7<--------------+48<--------+3<-------+

Then

                 semi_ptr       counter_ptr

                    +             +
                    |             |
                    v             v
            +------>24 +--------> 37 +--------> 12+
            |                                     |
            +                                     |
+--------> 17                                     |
            ^                                     v
            |                                     19
            |                                     +
            |                                     |
            |                                     |
            +                                     |
            7<--------------+48<--------+3<-------+

Then at the end:


                 +------>24 +--------> 37 +--------> 12+
counter_ptr      |                                     |
                 +                                     |
     +--------> 17                                     |
                 ^                                     v           semi_ptr
                 |                                     19 <--------+
                 |                                     +
                 |                                     |
                 |                                     |
                 +                                     |
                 7<--------------+48<--------+3<-------+

And then you just have to swap the elements in semi_ptr and counter_ptr.

After this, the graph now looks like:

 +--->24+----->37+----->12
 |                       +
 |                       |
 +                       |
 19<---------------------+

+------------------------>17
|                          +
|                          |
|                          |
+                          |
7<--------+48<------+3<----+

We just divided the looped linked list of length 8 in 2 looped linked-lists of length 4 each.

Once you find the algorithm, finding the good blocks is pretty easy.

Here's what we need:

[0x0b,0x0c,0x0d,0x02,0x08,0x09,0x04,0x00,0x03,0x0a,0x06,0x01,0x05,0x07]

GAME 3

The last game is pretty cool too, and is pretty similar to the second one in its construction. The program starts by generating a random value r between 2500 and 5000. It then malloc an array of r 32 bits integers. It then initialize this array with the values (i + 1) % r at the index i.

The array looks like this

[1, 2, 3, 4, ..., r - 1, 0]

The hidden_code of game_3 is called, the programs checks if its return value is < r. Then call the check function, which accept the array and the return value of the hidden code as input.

Here's what the check function does:

def check(array, return_value_hidden):
    cpt = 0
    cur = 0
    while True:
        v3 = array[cur]
        if cur == v3:
            break
        v4 = array[v3]
        array[cur] = v4
        cur = v4
    if cur == return_value_hidden:
        return good
    return false

I did not try to understand this mixing algorithm, I just looked at the values of cur at the end of the function for 2 <= i <= 2500, and looked for a pattern.

Here's the result:

cur = 0 i: 2
cur = 2 i: 3
cur = 0 i: 4
cur = 2 i: 5
cur = 4 i: 6
cur = 6 i: 7
cur = 0 i: 8
cur = 2 i: 9
cur = 4 i: 10
cur = 6 i: 11
cur = 8 i: 12
cur = 10 i: 13
cur = 12 i: 14
cur = 14 i: 15
cur = 0 i: 16
cur = 2 i: 17
cur = 4 i: 18
cur = 6 i: 19
cur = 8 i: 20
cur = 10 i: 21
cur = 12 i: 22
cur = 14 i: 23
cur = 16 i: 24
cur = 18 i: 25
cur = 20 i: 26
cur = 22 i: 27
cur = 24 i: 28
cur = 26 i: 29
cur = 28 i: 30
cur = 30 i: 31
cur = 0 i: 32
cur = 2 i: 33
cur = 4 i: 34
cur = 6 i: 35
cur = 8 i: 36
cur = 10 i: 37
cur = 12 i: 38
cur = 14 i: 39
cur = 16 i: 40
cur = 18 i: 41
cur = 20 i: 42
cur = 22 i: 43
cur = 24 i: 44
cur = 26 i: 45
cur = 28 i: 46
cur = 30 i: 47
cur = 32 i: 48
cur = 34 i: 49
cur = 36 i: 50
cur = 38 i: 51
cur = 40 i: 52
cur = 42 i: 53
cur = 44 i: 54
cur = 46 i: 55
cur = 48 i: 56
cur = 50 i: 57
cur = 52 i: 58
cur = 54 i: 59
cur = 56 i: 60
cur = 58 i: 61
cur = 60 i: 62
cur = 62 i: 63
cur = 0 i: 64
cur = 2 i: 65
cur = 4 i: 66
cur = 6 i: 67
cur = 8 i: 68
cur = 10 i: 69
cur = 12 i: 70
cur = 14 i: 71
cur = 16 i: 72
cur = 18 i: 73
cur = 20 i: 74
cur = 22 i: 75
cur = 24 i: 76
cur = 26 i: 77
cur = 28 i: 78
cur = 30 i: 79
cur = 32 i: 80
cur = 34 i: 81
cur = 36 i: 82
cur = 38 i: 83
cur = 40 i: 84
cur = 42 i: 85
cur = 44 i: 86
cur = 46 i: 87
cur = 48 i: 88
cur = 50 i: 89
cur = 52 i: 90
cur = 54 i: 91
cur = 56 i: 92
cur = 58 i: 93
cur = 60 i: 94
cur = 62 i: 95
cur = 64 i: 96
cur = 66 i: 97
cur = 68 i: 98
cur = 70 i: 99
cur = 72 i: 100
cur = 74 i: 101
cur = 76 i: 102
cur = 78 i: 103
cur = 80 i: 104
cur = 82 i: 105
cur = 84 i: 106
cur = 86 i: 107
cur = 88 i: 108
cur = 90 i: 109
cur = 92 i: 110
cur = 94 i: 111
cur = 96 i: 112
cur = 98 i: 113
cur = 100 i: 114
cur = 102 i: 115
cur = 104 i: 116
cur = 106 i: 117
cur = 108 i: 118
cur = 110 i: 119
cur = 112 i: 120
cur = 114 i: 121
cur = 116 i: 122
cur = 118 i: 123
cur = 120 i: 124
cur = 122 i: 125
cur = 124 i: 126
cur = 126 i: 127
cur = 0 i: 128
cur = 2 i: 129
cur = 4 i: 130
cur = 6 i: 131
cur = 8 i: 132
cur = 10 i: 133
cur = 12 i: 134
cur = 14 i: 135
cur = 16 i: 136
cur = 18 i: 137
cur = 20 i: 138
cur = 22 i: 139
cur = 24 i: 140
cur = 26 i: 141
cur = 28 i: 142
cur = 30 i: 143
cur = 32 i: 144
cur = 34 i: 145
cur = 36 i: 146
cur = 38 i: 147
cur = 40 i: 148
cur = 42 i: 149
cur = 44 i: 150
cur = 46 i: 151

It's pretty easy to recognize the pattern. For a value i, we find the biggest power of two below i, let's call it p, then cur = 2*(i - p).

Now, let's look at the hidden code, and build this algorithm.

  4017c0:   41 50                   push   r8
  4017c2:   4d 31 c0                xor    r8,r8
  4017c5:   41 54                   push   r12
  4017c7:   4d 31 e4                xor    r12,r12
  4017ca:   41 55                   push   r13
  4017cc:   4c 63 ee                movsxd r13,esi
  4017cf:   41 56                   push   r14
  4017d1:   4c 8d 35 43 01 00 00    lea    r14,[rip+0x143]
  4017d8:   b8 01 00 00 00          mov    eax,0x1
  4017dd:   49 81 fc 00 04 00 00    cmp    r12,0x400
  4017e4:   0f 8d 1c 00 00 00       jge    401806 
  4017ea:   4f 63 2c ae             movsxd r13,DWORD PTR [r14+r13*4]
  4017ee:   4d 01 f5                add    r13,r14
  4017f1:   41 ff e5                jmp    r13
  4017f4:   49 ff c4                inc    r12
  4017f7:   41 83 fd 0f             cmp    r13d,0xf
  4017fb:   0f 8d 05 00 00 00       jge    401806 
  401801:   e9 d7 ff ff ff          jmp    4017dd 
  401806:   83 c8 ff                or     eax,0xffffffff
  401809:   41 5e                   pop    r14
  40180b:   41 5d                   pop    r13
  40180d:   41 5c                   pop    r12
  40180f:   41 58                   pop    r8
  401811:   c3                      ret    
  401812:   89 fa                   mov    edx,edi      ; block_0
  401814:   d1 ea                   shr    edx,1
  401816:   41 bd 00 00 00 00       mov    r13d,0x0
  40181c:   e9 d3 ff ff ff          jmp    4017f4 
  401821:   41 f7 d0                not    r8d      ; block_1
  401824:   41 bd 00 00 00 00       mov    r13d,0x0
  40182a:   e9 c5 ff ff ff          jmp    4017f4 
  40182f:   83 f8 01                cmp    eax,0x1      ; block_2
  401832:   89 cf                   mov    edi,ecx
  401834:   0f 84 e7 ff ff ff       je     401821 
  40183a:   41 bd 00 00 00 00       mov    r13d,0x0
  401840:   e9 af ff ff ff          jmp    4017f4 
  401845:   44 21 c7                and    edi,r8d      ; block_3
  401848:   41 bd 00 00 00 00       mov    r13d,0x0
  40184e:   e9 a1 ff ff ff          jmp    4017f4 
  401853:   81 e2 55 55 55 55       and    edx,0x55555555   ; block_4
  401859:   29 d7                   sub    edi,edx
  40185b:   41 bd 00 00 00 00       mov    r13d,0x0
  401861:   e9 8e ff ff ff          jmp    4017f4 
  401866:   89 f8                   mov    eax,edi      ; block_5
  401868:   c1 e8 04                shr    eax,0x4
  40186b:   41 bd 00 00 00 00       mov    r13d,0x0
  401871:   e9 7e ff ff ff          jmp    4017f4 
  401876:   89 f8                   mov    eax,edi      ; block_6
  401878:   c1 ef 02                shr    edi,0x2
  40187b:   41 bd 00 00 00 00       mov    r13d,0x0
  401881:   e9 6e ff ff ff          jmp    4017f4
  401886:   89 f9                   mov    ecx,edi      ; block_7
  401888:   41 b8 ff ff ff ff       mov    r8d,0xffffffff
  40188e:   41 bd 00 00 00 00       mov    r13d,0x0
  401894:   e9 5b ff ff ff          jmp    4017f4 
  401899:   01 f8                   add    eax,edi      ; block_8
  40189b:   25 0f 0f 0f 0f          and    eax,0xf0f0f0f
  4018a0:   41 bd 00 00 00 00       mov    r13d,0x0
  4018a6:   e9 49 ff ff ff          jmp    4017f4 
  4018ab:   41 5e                   pop    r14      ; block_9
  4018ad:   41 5d                   pop    r13
  4018af:   41 5c                   pop    r12
  4018b1:   41 58                   pop    r8
  4018b3:   c3                      ret    
  4018b4:   41 bd 00 00 00 00       mov    r13d,0x0
  4018ba:   e9 35 ff ff ff          jmp    4017f4 
  4018bf:   44 21 c7                and    edi,r8d      ; block_A
  4018c2:   48 8d 04 3f             lea    rax,[rdi+rdi*1]
  4018c6:   41 bd 00 00 00 00       mov    r13d,0x0
  4018cc:   e9 23 ff ff ff          jmp    4017f4 
  4018d1:   69 c0 01 01 01 01       imul   eax,eax,0x1010101; block_B
  4018d7:   c1 e8 18                shr    eax,0x18
  4018da:   41 bd 00 00 00 00       mov    r13d,0x0
  4018e0:   e9 0f ff ff ff          jmp    4017f4 
  4018e5:   41 d1 e0                shl    r8d,1        ; block_C
  4018e8:   e9 58 ff ff ff          jmp    401845 
  4018ed:   41 bd 00 00 00 00       mov    r13d,0x0
  4018f3:   e9 fc fe ff ff          jmp    4017f4 
  4018f8:   81 e7 33 33 33 33       and    edi,0x33333333   ; block_D
  4018fe:   01 c7                   add    edi,eax
  401900:   41 bd 00 00 00 00       mov    r13d,0x0
  401906:   e9 e9 fe ff ff          jmp    4017f4 
  40190b:   25 33 33 33 33          and    eax,0x33333333   ; block_E
  401910:   41 bd 00 00 00 00       mov    r13d,0x0
  401916:   e9 d9 fe ff ff          jmp    4017f4 

The first things we see are the weird constants in the code. Googling them quickly gives some informations about a counting bit algorithm which is described here

The algorithm is short:

v = v - ((v >> 1) & 0x55555555);                  
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);    
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;

and what it does is simple: counting the number of bit set in an 32-bit integer.

This gaves us a big part of the needed permutation ! It's easy to understand that we will count the number of bits set in a number, and if it's equal to 1, then this number is a power of 2.

Let's build the rest of the algo. But first, let's have a look at the binary representation of r and cur for a given r, let's say 2804.

If r = 2804, i = 2*(2804 - 2048) = 1512
bin(2804) = 0b101011110100
bin(1512) = 0b10111101000

these two numbers are almost similar in base 2! In fact, we just have to flip the highest bit of 2804, then shift left the number just one time (multiply by 2 ), and we have cur.

Suppose that r is the length of our array. First, we initialize an integer mask = 0xffffffff (all bit 1 set) (block_7). Then we count the number of bits set of (mask & r) (block_3 and the counting bit algorithm). If it's greater than 1 (block_2), we shift left mask by 1 (block_C), and do it again. If it's one, we flip all the bits of mask (block_1).

For r = 2804, number_of_bit_set(r & 0b11111111111111111111110000000000) = 1. So we flip all bits of mask (which is a "not"). That gives us: mask = 0b00000000000000000000001111111111

Then we compute (r & mask) (block_A), and shift left the result (block_9). The value is now equal to cur !

Let's chain the blocks:

[0x07,0x03,0x00,0x04,0x06,0x0e,0x0d,0x05,0x08,0x0b,0x02,0x0c,0x01,0x0a,0x09].

GENERATE THE INPUT

We now have everything to solve this crackme. Let's quickly remember how our input is processed. First, our input is encrypted. After the encryption, the encrypted input is cutted in 3 parts. In each part, the value of byte at index i must be below or equal i (array[i] <= i). these 3 parts of encrypted input are used to mixup 3 arrays. The uniques permutations generated are the ones which build the control flow graph of the 3 hiddens codes.

What do we do now ? We know how works the algorithms, and we what we need for the permutations:

game_1: [0x09,0x05,0x0c,0x0a,0x02,0x03,0x00,0x06,0x01,0x08,0x04,0x07,0x0b],
game_2: [0x0b,0x0c,0x0d,0x02,0x08,0x09,0x04,0x00,0x03,0x0a,0x06,0x01,0x05,0x07]
game_3: [0x07,0x03,0x00,0x04,0x06,0x0e,0x0d,0x05,0x08,0x0b,0x02,0x0c,0x01,0x0a,0x09]

I've attached a quick script that will gives us the encrypted input we need to generate these permutations. The file is find_input.py [1].

Let's run it:

io :: crackmes.one/prisoners_delimma » python3 find_input.py 
[0, 0, 0, 2, 2, 3, 0, 6, 1, 8, 4, 7, 11]
[0, 0, 0, 2, 3, 4, 4, 0, 3, 6, 6, 1, 5, 7]
[0, 0, 0, 2, 2, 1, 2, 5, 8, 1, 2, 1, 1, 10, 9]

If we concat these three arrays, we have what what we need for the encrypted input. We can find the input that generate the encrypted input with the script prisoners.py [2].

io :: crackmes.one/prisoners_delimma » python3 prisonner.py
Found 2b and 5f for 0 and 21
Found b0 and 2f for 1 and 22
Found 1e and 80 for 2 and 23
Found b6 and 3e for 3 and 24
Found b5 and f3 for 4 and 25
Found 9c and f3 for 5 and 26
Found 42 and 12 for 6 and 27
Found fe and a1 for 7 and 28
Found 93 and 2f for 8 and 29
Found 27 and 91 for 9 and 30
Found 14 and e6 for 10 and 31
Found f2 and 45 for 11 and 32
Found 02 and 6b for 12 and 33
Found 79 and f4 for 13 and 34
Found f7 and 58 for 14 and 35
Found 58 and b9 for 15 and 36
Found b4 and 51 for 16 and 37
Found dd and 57 for 17 and 38
Found fb and 86 for 18 and 39
Found 6a and 0c for 19 and 40
Found be and 99 for 20 and 41

input: 2bb01eb6b59c42fe932714f20279f758b4ddfb6abe5f2f803ef3f312a12f91e6456bf458b95157860c99
io :: crackmes.one/prisoners_delimma » echo "2bb01eb6b59c42fe932714f20279f758b4ddfb6abe5f2f803ef3f312a12f91e6456bf458b95157860c99" | xxd -r -p | ./prisoners_dilemma



______     _                           _            ______
| ___ \   (_)                         ( )          / / / /
| |_/ / __ _ ___  ___  _ __   ___ _ __|/ ___      / / / / 
|  __/ '__| / __|/ _ \| '_ \ / _ \ '__| / __|    < < < <  
| |  | |  | \__ \ (_) | | | |  __/ |    \__ \     \ \ \ \ 
\_|  |_|  |_|___/\___/|_| |_|\___|_|    |___/      \_\_\_\
______          ______ _ _                                
\ \ \ \         |  _  (_) |                               
 \ \ \ \        | | | |_| | ___ _ __ ___  _ __ ___   __ _ 
  > > > >       | | | | | |/ _ \ '_ ` _ \| '_ ` _ \ / _` |
 / / / /        | |/ /| | |  __/ | | | | | | | | | | (_| |
/_/_/_/         |___/ |_|_|\___|_| |_| |_|_| |_| |_|\__,_|



The warden is feeling generous today, and wants to play a 
game. If you win, you may leave with your freedom, but if 
you lose....                                              

Think long and hard about your strategy, because you only 
get one chance.                                           

Your move: 
You won! In utter disbelief, the warden is forced to let
you go....

Finally, it works ! 

"What we do in life echoes in eternity" -- i https://patchfriday.com/22/

[1] Source code of find_input.py

def swap(a, i, j):
    a[i], a[j] = a[j], a[i]
    return a

base = [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e]
bases = [base[:-2], base[:-1], base[:]]
needed_arrays =    [[0x09,0x05,0x0c,0x0a,0x02,0x03,0x00,0x06,0x01,0x08,0x04,0x07,0x0b],
            [0x0b,0x0c,0x0d,0x02,0x08,0x09,0x04,0x00,0x03,0x0a,0x06,0x01,0x05,0x07],
            [0x07,0x03,0x00,0x04,0x06,0x0e,0x0d,0x05,0x08,0x0b,0x02,0x0c,0x01,0x0a,0x09]]

for i, array in enumerate(needed_arrays):
    length = len(array) - 1
    base = bases[i]
    res = [0xff for i in range(length + 1)]
    while length >= 0:
        for j in range(length + 1):
            if base[j] == array[length]:
                base = swap(base, length, j)
                res[length] = j
                break
        length -= 1
    print(res)

[2] Source code of prisoners.py

#[0, 0, 0, 2, 2, 3, 0, 6, 1, 8, 4, 7, 11]
#[0, 0, 0, 2, 3, 4, 4, 0, 3, 6, 6, 1, 5, 7]
#[0, 0, 0, 2, 2, 1, 2, 5, 8, 1, 2, 1, 1, 10, 9]
needed =    [[0, 0, 0, 2, 2, 3, 0, 6, 1, 8, 4, 7, 11, 0, 0, 0, 2, 3, 4, 4, 0],
            [3, 6, 6, 1, 5, 7, 0, 0, 0, 2, 2, 1, 2, 5, 8, 1, 2, 1, 1, 10, 9]]
LENGTH_INPUT = 42
key1 = bytearray.fromhex('27749f2cbfede7496059f991b2e1bb16bedc5ea068807bc7e4b95a0cf20e34b72595d13c2cc961e55447')
key2 = bytearray.fromhex('d387a354528520d5057a40c7962a12bba8400a24480fd80488380b6b8b5761a3e747e797902b7cccfd78')
current = 0
res = [0xff for i in range(LENGTH_INPUT)]
while current < (LENGTH_INPUT / 2):
    for i in range(256 * 256):
        j = int(i / 256)
        k = i & 0xff

        b1 = res[:int(LENGTH_INPUT / 2)]
        b2 = res[int(LENGTH_INPUT / 2):]

        b1[current] = j
        b2[current] = k

        for i, c in enumerate(b1):
           b1[i] ^= key1[i]

        for i, c in enumerate(b2):
           b2[i] ^= key1[i + int(LENGTH_INPUT / 2)]

        dec = 0x32
        summ = 0
        cnt = 0
        while dec:
            while cnt != 0x15:
                summ += b2[cnt]
                summ &= 0xff
                b1[cnt] ^= summ
                cnt += 1
            cnt = 0
            summ = 0
            b1, b2 = b2, b1
            dec -= 1

        temp_res = b1 + b2

        for i, c in enumerate(temp_res):
           temp_res[i] ^= key2[i]

        if temp_res[current] == needed[0][current] and temp_res[current + int(LENGTH_INPUT / 2)] == needed[1][current]:
            res[current] = j
            res[current + int(LENGTH_INPUT / 2)] = k
            print("Found {:02x} and {:02x} for {} and {}".format(j, k, current, current + int(LENGTH_INPUT / 2)))
            #print("Res is {}".format(bytes(res).hex()))
            break
    current += 1
print("\ninput: {}".format(bytes(res).hex()))

Twitter - Github - Discord: sar#5430 - Visit Crackmes.one!