ふるつき

v(*'='*)v かに

Facebook CTF writeup

f:id:Furutsuki:20190603164636p:plain

I played Facebook CTF as a member of team zer0pts. We were in 18th place and got 9372 points. There were a lot of well-crafted challenges. I enjoyed all the challenges I tried even though I couldn't solve some of them.

[Crypto 453points (79 solves)]keybaseish

We built a secure messaging platform based on twitter identities, verified with our proprietary signature protocol! Right now our founder is the only one using it. Would you like to join?

Note: You do not need a twitter account to solve this challenge

http://challenges.fbctf.com:8080

The site had a login page, a register page, and a account recovery page. The administrator's twitter account was also provided(the account page has already deleted). What we had to do is (to) recover account as an administrator and login to the account page.

This site used a unique way to recover the account. The user tweets the RSA encrypted PIN value (pow(pin, e, n) in his own twitter account and upload the encryption key (e and n). If the tweeted ciphervalue is match to the server-side encryption the account will be recovered.

The administrator has already tweeted a ciphervalue in past as shown below.

43522081190908620239526125376626925272670879862906206214798620592212761409287968319160030205818706732092664958217053982767385296720310547463903001181881966554081621263332073144333148831108871059921677679366681345909190184917461295644569942753755984548017839561073991169528773602380297241266112083733072690367

We just did a search for the appropriate e and n of the given PIN value. I used the following script.

from Crypto.PublicKey import RSA
from Crypto.Util.number import getPrime
from Crypto import Random

RSAkey = RSA.generate(1024)
key_params = RSAkey.__getstate__()
sign = 43522081190908620239526125376626925272670879862906206214798620592212761409287968319160030205818706732092664958217053982767385296720310547463903001181881966554081621263332073144333148831108871059921677679366681345909190184917461295644569942753755984548017839561073991169528773602380297241266112083733072690367

pin = int(input().strip())
n = getPrime(pin.bit_length() + 1)
for i in range(2, n):
    x = pow(sign, i, n)
    if x == pin:
        print(x)
        print("{}:{}".format(i, n))
        exit()

If we choose a prime as modulo, it forms a cyclic group*1. Thus, it will be a correct e in [1, n).

[Reversing 453points (79 solves)] go_get_the_flag

Go get the flag!

We were given an ELF file which is written in go. Just open it in radare2, and see checkpassword function. We could see the specious string in it: s0_M4NY_Func710n2!`.

f:id:Furutsuki:20190603171057p:plain

I ran ./ggtf s0_M4NY_Func710n2! then got the flag: fb{.60pcln74b_15_4w350m3}.

[Reversing 738points (55 solves)]matryoshka

There was a downloader found on a Mac desktop. It's your job to have layers of fun getting the flag.

Written by malwareunicorn

I solved first three stages. My teammate @ptr-yudai solved the last stage and got the flag.

We were given an executable which runs on the macOS. It was multi staged crackme challenge, but downloaded a corrupted PNG image before running as a crackme. The downloaded image includes some machine codes. The binary ran them.

On the first stage, the machine code is located at 393216 in the image. I used cutter to reversing it. Then I found that the code did rot13 to the input and compared it to 4ZberYriryf2TbXrrcTbvat. Then the first value is 4MoreLevels2GoKeepGoing.

The 2nd, 3rd and 4th stages were countinuoused. The machine code is located at 424056. It used an input to decrypt next stage's machine code.

The second stage required 4 bytes as input, and subtracted it from encrypted machine code. There was a check if the decrypted machine code starts with 0x55488945 (push rbp; mov rbp rsp;). Thus, to get the keyword, we can compare encrypted machine code and 0x55488945. The following script told me that the key value is r00t.

r13 = [0x59, 0xb9, 0x78, 0xc7]
correct = [0xe5, 0x89, 0x48, 0x55]
ans = [[] for _ in range(4)]

for p in range(4):
    for c in range(256):
        if (r13[p] - c + 256) % 256 == correct[p]:
            ans[p].append(chr(c))
print(ans)

Now we can decrypt the machine code of the third stage.

offset = 0x137
size = 0x678

data = open("code2", "rb").read()[offset : offset + size]
key = [ord(c) for c in "r00t"]

new_opecodes = []

for i in range(0, size):
    new_opecodes.append((data[i] - key[i % 4] + 256) % 256)
open("stage3_code", "wb").write(bytearray(new_opecodes))

The stage3 was simple xor. Since it also checks the function header, we can get the 4 bytes of key value easily. However, in this stage, it required 8 bytes key. Luckily, latter bytes was determined to be uOQJ because there was a raw-compare in the code.

r15 = [0x19, 0x02, 0xea, 0x87][::-1]
correct = [0xe5, 0x89, 0x48, 0x55]
ans = []

for i in range(4):
    ans.append(chr(r15[i] ^ correct[i]))

ans = "".join(ans)[::-1] + "uOQJ"
print(ans)

The third keyword was LJcbuOQJ. We can extract 4th stage's machine code.

offset = 0x12c
size = 0x44a

data = open("stage3_code", "rb").read()[offset : offset + size]
key = [ord(c) for c in "LJcbuOQJ"]

new_opecodes = []

for i in range(0, size):
    new_opecodes.append(data[i] ^ key[i % 8])
open("stage4_code", "wb").write(bytearray(new_opecodes))

The last part was pretty hard. It also required 8bytes value and the last 4 bytes was wT96, but first 4 bytes can be any letters. As the input value is used for some calculation to get the return value and a string, this process may help us determine the first bytes.

However, I completely stucked here. Then my teammate @ptr-yudai wrote a bruteforce script and got the correct input: YrQmwT96.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <byteswap.h>

unsigned char memory[0x100];
unsigned char original[0x30] =
  "\xf6\x2c\x72\x1a\x03\x99\x0e\x78\xbd\x90\xe9\x68\xd0\x69\x37\x29"
  "\xf8\x12\xf4\xe5\xd0\xfb\xf3\x7e\x72\x61\x79\x19\xed\x44\x12\x52"
  "\xf5\xf9\xaa\x14\x36\x0d\x1f\xb2\x52\x6b\xf2\x6a\xda\x9d\xec\x3c";
unsigned char string[0x30];

void scramble_memory(char *password)
{
  unsigned long delta = 0x0808080808080808;
  unsigned long seed = 0x0706050403020100;
  unsigned long *ptr = (unsigned long*)&memory;
  int i;
  for(i = 0; i < 0x20; i++) {
    *ptr = seed;
    seed += delta;
    ptr++;
  }
  unsigned char r9 = 0, r10 = 0;
  for(i = 0; i < 0x100; i++) {
    unsigned char xmm0 = memory[i];
    r9 += xmm0 + password[r10];
    memory[i] = memory[r9];
    memory[r9] = xmm0;
    r10 = (r10 + 1) % 8;
  }
}

void create_string()
{
  int i;
  unsigned char pos0 = 0, pos1 = 0;
  for(i = 0; i < 0x30; i++) {
    pos0 += 1;
    unsigned char xmm2 = memory[pos0];
    pos1 += xmm2;
    unsigned char xmm3 = memory[pos1];
    memory[pos0] = xmm3;
    memory[pos1] = xmm2;
    string[i] ^= memory[(xmm2 + xmm3) & 0xff];
  }
}

int main()
{
  char password[] = "????wT96";
  char c1, c2, c3, c4;
  for(c1 = 0x20; c1 < 0x7f; c1++) {
    printf("c1 = %d\n", c1);
    for(c2 = 0x20; c2 < 0x7f; c2++) {
      for(c3 = 0x20; c3 < 0x7f; c3++) {
    for(c4 = 0x20; c4 < 0x7f; c4++) {
      password[0] = c1;
      password[1] = c2;
      password[2] = c3;
      password[3] = c4;
      memcpy(string, original, 0x30);
      
      scramble_memory(password);
      create_string();

      unsigned long ret = *(unsigned long*)&(string);
      unsigned char *text = (unsigned char*)((unsigned long)string + 8);

      int i;
      for(i = 0; i < 0x28; i++) {
        if (!(0x20 <= text[i] && text[i] < 0x7f)) {
          break;
        }
      }
      if (i > 0x20) {
        printf("password: %s\n", password);
        printf("ret: %016lx\n", ret);
        printf("text: %s\n", text);
          
        unsigned long d, c, b, a;
        unsigned long x = __bswap_64(ret);
        d = 0x115c28da834feffd ^ x;
        c = 0x665f336b1a566b19 ^ d;
        b = 0x393b415f5a590044 ^ c;
        a = 0x3255557376f68 ^ b;
        printf("%016lx %016lx %016lx %016lx\n", a, b, c, d);
      }
    }
      }
    }
  }
  return 0;
}