HackPack CTF 2023 Writeup

この大会は2023/4/15 0:00(JST)~2023/4/16 0:00(JST)に開催されました。
今回もチームで参戦。結果は1022点で381チーム中29位でした。
自分で解けた問題をWriteupとして書いておきます。

Speed-Rev: Humans (rev)

$ nc cha.hackpack.club 41709
Welcome to the speedrun challenge! You have 30 minutes to solve 5 levels!
Level 1, here is the binary!
f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAcBA...
What is the flag?

base64デコードすると、バイナリになる。何回か試し、Ghidraでデコンパイルしたところ、以下のようになり、毎回異なる文字列だが、validate関数で文字列の比較を行っている。

bool validate(char *param_1)

{
  int iVar1;
  
  iVar1 = strncmp(param_1,"jKuhSWXRGzt5D8bK",0x10);
  return iVar1 != 0;
}

バイナリのオフセット0x2004-0x2013の16バイトがフラグになる。

Level 2のバイナリでは、デコンパイル結果は以下のようになる。

undefined8 validate(char *param_1)

{
  undefined8 uVar1;
  
  if (*param_1 == 'R') {
    if (param_1[1] == 'S') {
      if (param_1[2] == 'A') {
        if (param_1[3] == 'o') {
          if (param_1[4] == 'i') {
            if (param_1[5] == 'p') {
              if (param_1[6] == 'z') {
                if (param_1[7] == 'o') {
                  if (param_1[8] == '8') {
                    if (param_1[9] == 'Q') {
                      if (param_1[10] == 'o') {
                        if (param_1[0xb] == 'g') {
                          if (param_1[0xc] == '6') {
                            if (param_1[0xd] == 'p') {
                              if (param_1[0xe] == 'a') {
                                if (param_1[0xf] == 'X') {
                                  uVar1 = 0;
                                }
                                else {
                                  uVar1 = 1;
                                }
                              }
                              else {
                                uVar1 = 1;
                              }
                            }
                            else {
                              uVar1 = 1;
                            }
                          }
                          else {
                            uVar1 = 1;
                          }
                        }
                        else {
                          uVar1 = 1;
                        }
                      }
                      else {
                        uVar1 = 1;
                      }
                    }
                    else {
                      uVar1 = 1;
                    }
                  }
                  else {
                    uVar1 = 1;
                  }
                }
                else {
                  uVar1 = 1;
                }
              }
              else {
                uVar1 = 1;
              }
            }
            else {
              uVar1 = 1;
            }
          }
          else {
            uVar1 = 1;
          }
        }
        else {
          uVar1 = 1;
        }
      }
      else {
        uVar1 = 1;
      }
    }
    else {
      uVar1 = 1;
    }
  }
  else {
    uVar1 = 1;
  }
  return uVar1;
}

バイナリのオフセットが0x1155から25バイトごとに11個取り出し、さらに22バイトごとに5個取り出したものがフラグになる。

Level 3のバイナリでは、デコンパイル結果はLevel 2と同様で、アセンブリの構造も同じなので、フラグの取り出し方は同様にできる。

Level 4のバイナリでは、デコンパイル結果は以下のようになる。

undefined8 validate(char *param_1)

{
  undefined8 uVar1;
  
  if ((int)param_1[1] + (int)*param_1 == 0xaf) {
    if ((int)param_1[2] + (int)param_1[1] == 0xa4) {
      if ((int)param_1[3] + (int)param_1[2] == 0x96) {
        if ((int)param_1[4] + (int)param_1[3] == 0xac) {
          if ((int)param_1[5] + (int)param_1[4] == 0x9b) {
            if ((int)param_1[6] + (int)param_1[5] == 0x80) {
              if ((int)param_1[7] + (int)param_1[6] == 0x8d) {
                if ((int)param_1[8] + (int)param_1[7] == 0xaa) {
                  if ((int)param_1[9] + (int)param_1[8] == 0xa7) {
                    if ((int)param_1[10] + (int)param_1[9] == 0x8a) {
                      if ((int)param_1[0xb] + (int)param_1[10] == 0x8a) {
                        if ((int)param_1[0xc] + (int)param_1[0xb] == 0x8b) {
                          if ((int)param_1[0xd] + (int)param_1[0xc] == 0x9f) {
                            if ((int)param_1[0xe] + (int)param_1[0xd] == 0xa7) {
                              if ((int)param_1[0xf] + (int)param_1[0xe] == 0xa8) {
                                uVar1 = 0;
                              }
                              else {
                                uVar1 = 1;
                              }
                            }
                            else {
                              uVar1 = 1;
                            }
                          }
                          else {
                            uVar1 = 1;
                          }
                        }
                        else {
                          uVar1 = 1;
                        }
                      }
                      else {
                        uVar1 = 1;
                      }
                    }
                    else {
                      uVar1 = 1;
                    }
                  }
                  else {
                    uVar1 = 1;
                  }
                }
                else {
                  uVar1 = 1;
                }
              }
              else {
                uVar1 = 1;
              }
            }
            else {
              uVar1 = 1;
            }
          }
          else {
            uVar1 = 1;
          }
        }
        else {
          uVar1 = 1;
        }
      }
      else {
        uVar1 = 1;
      }
    }
    else {
      uVar1 = 1;
    }
  }
  else {
    uVar1 = 1;
  }
  return uVar1;
}

バイナリの "\x48\x8b\x45\xf8\x0f\xb6\x00" の後から "\x3d"または "\x83\xf8" の次の値を15個取り出したものがそれぞれフラグの各文字の隣通しの和になる。あとは英数字でこの条件を満たすよう1文字目をブルートフォースし、フラグを求める。

Level 5のバイナリでは、デコンパイル結果は以下のようになる。

undefined8 validate(char *param_1)

{
  undefined8 uVar1;
  
  if (*param_1 == 'X') {
    if ((int)param_1[2] + (int)param_1[1] == 0xa6) {
      if ((int)param_1[3] + (int)param_1[2] == 0x8c) {
        if ((int)param_1[4] + (int)param_1[3] == 0x99) {
          if ((int)param_1[5] + (int)param_1[4] == 0xa5) {
            if (param_1[5] == 'a') {
              if (param_1[6] == 'B') {
                if (param_1[7] == 'A') {
                  if (param_1[8] == 'S') {
                    if (param_1[9] == 'C') {
                      if (param_1[10] == '8') {
                        if ((int)param_1[0xc] + (int)param_1[0xb] == 0xac) {
                          if ((int)param_1[0xd] + (int)param_1[0xc] == 0x91) {
                            if ((int)param_1[0xe] + (int)param_1[0xd] == 0x8a) {
                              if ((int)param_1[0xf] + (int)param_1[0xe] == 0xbc) {
                                uVar1 = 0;
                              }
                              else {
                                uVar1 = 1;
                              }
                            }
                            else {
                              uVar1 = 1;
                            }
                          }
                          else {
                            uVar1 = 1;
                          }
                        }
                        else {
                          uVar1 = 1;
                        }
                      }
                      else {
                        uVar1 = 1;
                      }
                    }
                    else {
                      uVar1 = 1;
                    }
                  }
                  else {
                    uVar1 = 1;
                  }
                }
                else {
                  uVar1 = 1;
                }
              }
              else {
                uVar1 = 1;
              }
            }
            else {
              uVar1 = 1;
            }
          }
          else {
            uVar1 = 1;
          }
        }
        else {
          uVar1 = 1;
        }
      }
      else {
        uVar1 = 1;
      }
    }
    else {
      uVar1 = 1;
    }
  }
  else {
    uVar1 = 1;
  }
  return uVar1;
}

バイナリの "\x48\x8b\x45\xf8\x0f\xb6\x00" の後から次のように条件を取り出す。

・"\x3c"の次の値がフラグ文字
・"\x3d"の次の値または\x83\xf8"の次の値は和の値

z3でフラグを割り出す。

Level 6のバイナリでは、デコンパイル結果はLevel 5と同様で、アセンブリの構造も同じなので、フラグの取り出し方は同様にできる。

#!/usr/bin/env python3
import socket
from base64 import *
from z3 import *

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('cha.hackpack.club', 41709))

data = recvuntil(s, b'\n').rstrip()
print(data)

#### Level 1 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level1 = b64decode(eval(data))
flag = level1[0x2004:0x2014].decode()

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 2 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level2 = b64decode(eval(data))

flag = ''
offset = 0x1155
for i in range(16):
    flag += chr(level2[offset])
    if i < 10:
        offset += 25
    else:
        offset += 22

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 3 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level3 = b64decode(eval(data))

flag = ''
offset = 0x1155
for i in range(16):
    flag += chr(level3[offset])
    if i < 10:
        offset += 25
    else:
        offset += 22

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 4 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level4 = b64decode(eval(data))

adds = []
offset = level4.index(b'\x48\x8b\x45\xf8\x0f\xb6\x00')
for i in range(15):
    offset1 = level4.find(b'\x3d', offset) + 1
    offset2 = level4.find(b'\x83\xf8', offset) + 2
    if min(offset1, offset2) > offset:
        offset = min(offset1, offset2)
    else:
        offset = max(offset1, offset2)
    adds.append(level4[offset])

for f0 in range(48, 123):
    flag = chr(f0)
    error = False
    for i in range(15):
        code = adds[i] - ord(flag[-1])
        if code < 0:
            error = True
            break
        flag += chr(code)
    if error:
        continue
    if flag.isalnum():
        break

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 5 ####
for _ in range(7):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level5 = b64decode(eval(data))

x = [BitVec('x%d' % i, 8) for i in range(16)]
slv = Solver()
for i in range(16):
    slv.add(Or(And(x[i] > 47, x[i] < 58), And(x[i] > 64, x[i] < 91),
        And(x[i] > 96, x[i] < 123)))

offset = level5.index(b'\x48\x8b\x45\xf8\x0f\xb6\x00')
for i in range(15):
    offset1 = level5.find(b'\x3c', offset) + 1
    offset2 = level5.find(b'\x3d', offset) + 1
    offset3 = level5.find(b'\x83\xf8', offset) + 2
    if offset1 < offset:
        offset1 = 99999
    if offset2 < offset:
        offset2 = 99999
    if offset3 < offset:
        offset3 = 99999
    offsets = [offset1, offset2, offset3]
    flg = -1
    offset = 99999
    for j in range(3):
        if offsets[j] < offset:
            flg = j
            offset = offsets[j]
    if flg == 2:
        flg = 1

    if flg == 0:
        slv.add(x[i] == level5[offset])
    else:
        slv.add(x[i] + x[i + 1] == level5[offset])

r = slv.check()
assert r == sat
m = slv.model()
flag = ''
for i in range(16):
    flag += chr(m[x[i]].as_long())
data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 6 ####
for _ in range(6):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level6 = b64decode(eval(data))

x = [BitVec('x%d' % i, 8) for i in range(16)]
slv = Solver()
for i in range(16):
    slv.add(Or(And(x[i] > 47, x[i] < 58), And(x[i] > 64, x[i] < 91),
        And(x[i] > 96, x[i] < 123)))

offset = level6.index(b'\x48\x8b\x45\xf8\x0f\xb6\x00')
for i in range(15):
    offset1 = level6.find(b'\x3c', offset) + 1
    offset2 = level6.find(b'\x3d', offset) + 1
    offset3 = level6.find(b'\x83\xf8', offset) + 2
    if offset1 < offset:
        offset1 = 99999
    if offset2 < offset:
        offset2 = 99999
    if offset3 < offset:
        offset3 = 99999
    offsets = [offset1, offset2, offset3]
    flg = -1
    offset = 99999
    for j in range(3):
        if offsets[j] < offset:
            flg = j
            offset = offsets[j]
    if flg == 2:
        flg = 1

    if flg == 0:
        slv.add(x[i] == level6[offset])
    else:
        slv.add(x[i] + x[i + 1] == level6[offset])

r = slv.check()
assert r == sat
m = slv.model()
flag = ''
for i in range(16):
    flag += chr(m[x[i]].as_long())
data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### get flag ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

実行結果は以下の通り。

Welcome to the speedrun challenge! You have 30 minutes to solve 5 levels!
Level 1, here is the binary!
b'f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAcBAAAAAAAABAAAAAAAAAACA...
                :
                :
Level 6, here is the binary!
4
6
3
2
b'f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAYBAAAAAAAABAAAAAAAAAAOg...
What is the flag?
k8aQtg3FZkTy6e0q
Congrats! Here is your flag!
flag{Human_or_r0b0t_1dk}
flag{Human_or_r0b0t_1dk}

Speed-Rev: Bots (rev)

Speed-Rev: Humans と同じスクリプトで接続先だけ変更する。

#!/usr/bin/env python3
import socket
from base64 import *
from z3 import *

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('cha.hackpack.club', 41702))

data = recvuntil(s, b'\n').rstrip()
print(data)

#### Level 1 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level1 = b64decode(eval(data))
flag = level1[0x2004:0x2014].decode()

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 2 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level2 = b64decode(eval(data))

flag = ''
offset = 0x1155
for i in range(16):
    flag += chr(level2[offset])
    if i < 10:
        offset += 25
    else:
        offset += 22

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 3 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level3 = b64decode(eval(data))

flag = ''
offset = 0x1155
for i in range(16):
    flag += chr(level3[offset])
    if i < 10:
        offset += 25
    else:
        offset += 22

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 4 ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level4 = b64decode(eval(data))

adds = []
offset = level4.index(b'\x48\x8b\x45\xf8\x0f\xb6\x00')
for i in range(15):
    offset1 = level4.find(b'\x3d', offset) + 1
    offset2 = level4.find(b'\x83\xf8', offset) + 2
    if min(offset1, offset2) > offset:
        offset = min(offset1, offset2)
    else:
        offset = max(offset1, offset2)
    adds.append(level4[offset])

for f0 in range(48, 123):
    flag = chr(f0)
    error = False
    for i in range(15):
        code = adds[i] - ord(flag[-1])
        if code < 0:
            error = True
            break
        flag += chr(code)
    if error:
        continue
    if flag.isalnum():
        break

data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 5 ####
for _ in range(7):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level5 = b64decode(eval(data))

x = [BitVec('x%d' % i, 8) for i in range(16)]
slv = Solver()
for i in range(16):
    slv.add(Or(And(x[i] > 47, x[i] < 58), And(x[i] > 64, x[i] < 91),
        And(x[i] > 96, x[i] < 123)))

offset = level5.index(b'\x48\x8b\x45\xf8\x0f\xb6\x00')
for i in range(15):
    offset1 = level5.find(b'\x3c', offset) + 1
    offset2 = level5.find(b'\x3d', offset) + 1
    offset3 = level5.find(b'\x83\xf8', offset) + 2
    if offset1 < offset:
        offset1 = 99999
    if offset2 < offset:
        offset2 = 99999
    if offset3 < offset:
        offset3 = 99999
    offsets = [offset1, offset2, offset3]
    flg = -1
    offset = 99999
    for j in range(3):
        if offsets[j] < offset:
            flg = j
            offset = offsets[j]
    if flg == 2:
        flg = 1

    if flg == 0:
        slv.add(x[i] == level5[offset])
    else:
        slv.add(x[i] + x[i + 1] == level5[offset])

r = slv.check()
assert r == sat
m = slv.model()
flag = ''
for i in range(16):
    flag += chr(m[x[i]].as_long())
data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### Level 6 ####
for _ in range(6):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

level6 = b64decode(eval(data))

x = [BitVec('x%d' % i, 8) for i in range(16)]
slv = Solver()
for i in range(16):
    slv.add(Or(And(x[i] > 47, x[i] < 58), And(x[i] > 64, x[i] < 91),
        And(x[i] > 96, x[i] < 123)))

offset = level6.index(b'\x48\x8b\x45\xf8\x0f\xb6\x00')
for i in range(15):
    offset1 = level6.find(b'\x3c', offset) + 1
    offset2 = level6.find(b'\x3d', offset) + 1
    offset3 = level6.find(b'\x83\xf8', offset) + 2
    if offset1 < offset:
        offset1 = 99999
    if offset2 < offset:
        offset2 = 99999
    if offset3 < offset:
        offset3 = 99999
    offsets = [offset1, offset2, offset3]
    flg = -1
    offset = 99999
    for j in range(3):
        if offsets[j] < offset:
            flg = j
            offset = offsets[j]
    if flg == 2:
        flg = 1

    if flg == 0:
        slv.add(x[i] == level6[offset])
    else:
        slv.add(x[i] + x[i + 1] == level6[offset])

r = slv.check()
assert r == sat
m = slv.model()
flag = ''
for i in range(16):
    flag += chr(m[x[i]].as_long())
data = recvuntil(s, b'\n').rstrip()
print(data)
print(flag)
s.sendall(flag.encode() + b'\n')

#### get flag ####
for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

実行結果は以下の通り。

Welcome to the speedrun challenge! You have 3 minutes to solve 6 levels!
Level 1, here is the binary!
b'f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAcBAAAAAAA...
                :
                :
Level 6, here is the binary!
2
3
4
6
b'f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAYBAAAAAAA...
What is the flag?
qb9IfqStzAvyHlGg
Congrats! Here is your flag!
flag{speedruns_are_aw3s0m3_4nd_4ll}
flag{speedruns_are_aw3s0m3_4nd_4ll}