JISCTF 2020 Qualifications Writeup

この大会は2020/11/20 2:00(JST)~2020/11/22 4:00(JST)に開催されました。
今回もチームで参戦。結果は3750点で546チーム中14位でした。
自分で解けた問題をWriteupとして書いておきます。

Common (Misc 150)

違いがある文字のfile3.txt側の文字を拾っていくと、フラグの逆順になるので、逆にしてフラグにする。

with open('file2.txt', 'rb') as f:
    data1 = f.read()

with open('file3.txt', 'rb') as f:
    data2 = f.read()

flag = ''
for i in range(len(data1)):
    if data1[i] != data2[i]:
        flag += data2[i]

flag = flag[::-1]
print flag
JISCTF{s0m3_c0mm0n_dt4_b3t33n_tw0_f1l3s_jisctf_2019}

Oh my rain!!! (Misc 150)

パスワード付きzipになっているので、クラックする。

$ zip2john OH-MY-R41N.zip > hash.txt
ver 81.9 OH-MY-R41N.zip/data.txt is not encrypted, or stored with non-handled compression type
$ john --wordlist=dict/rockyou.txt hash.txt
Warning: detected hash type "ZIP", but the string is also recognized as "ZIP-opencl"
Use the "--format=ZIP-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 128/128 AVX 4x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
management       (OH-MY-R41N.zip/data.txt)
1g 0:00:00:01 DONE (2020-11-21 09:57) 0.5235g/s 9650p/s 9650c/s 9650C/s chatty..sweetgurl
Use the "--show" option to display all of the cracked passwords reliably
Session completed

パスワード management で解凍すると、data.txtが展開される。その内容にはアルファベット大文字と数字、最後に=のパディングがあることからbase32と推測する。

import base64

with open('data.txt', 'r') as f:
    data = f.read().replace('\n', '')

while True:
    try:
        data = base64.b32decode(data)
        data = data.replace('\n', '')
    except:
        break

print data

何回もbase32デコードすると、brainf*ck言語になる。

-[------->+<]>+.-.++++++++++.--[->++++<]>-.>-[--->+<]>-.[----->+<]>++.>--[-->+++++<]>.>-[--->+<]>-.------------.[--->++<]>+.>-[--->+<]>--.+[-->+<]>+++.++++.>-[--->+<]>--.+[-->+<]>+++.---[->++<]>-.>-[----->+<]>--.+[-->+++<]>++.+++.----.-[--->++<]>+.------.-[-->+++<]>+.+++++.[++>-----<]>.[-->+++<]>--..-[--->++<]>+.+[-->+++<]>.-------.>-[----->+<]>.>--[-->+++<]>.

https://sange.fi/esoteric/brainfuck/impl/interp/i.htmlで実行すると、フラグが表示された。

JISCTF{TH1S-1S-S1MPL3-CH4LL3NG3}

Baby Reverse (Reversing 100)

Ghidraでデコンパイルする。

undefined8 main(int param_1,long param_2)

{
  size_t sVar1;
  long in_FS_OFFSET;
  int local_134;
  int local_130;
  int local_12c;
  char *local_118 [4];
  char *local_f8;
  char *local_f0;
  char *local_e8;
  char *local_e0;
  char *local_d8;
  char *local_d0;
  char *local_c8;
  char *local_c0;
  char *local_b8;
  char *local_b0;
  char *local_a8;
  char *local_a0;
  char *local_98;
  char *local_90;
  char *local_88;
  char *local_80;
  char *local_78;
  char *local_70;
  char *local_68;
  char *local_60;
  char *local_58;
  char local_48 [4];
  undefined local_44;
  undefined local_43;
  undefined local_42;
  undefined local_28;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  if (param_1 == 1) {
    puts("Try harder!!");
  }
  else {
    if (param_1 == 2) {
      local_130 = 0;
      local_134 = 0;
      while( true ) {
        sVar1 = strlen(*(char **)(param_2 + 8));
        if (sVar1 <= (ulong)(long)local_134) break;
        if (*(char *)((long)local_134 + *(long *)(param_2 + 8)) == '-') {
          local_130 = local_130 + 1;
        }
        local_134 = local_134 + 1;
      }
      if (local_130 == 3) {
        puts("drink water!!");
      }
      else {
        puts("First argument must be in the format number-number-number-number");
      }
    }
    else {
      if (2 < param_1) {
        puts("Think out of the BOX!");
      }
    }
  }
  local_118[0] = "AFLdfgArTkdwelkfkmkasFFEWSWasCFsfsdfewRRWDSCFAWDHg";
  local_118[1] = "sddgrlksjpothjafkgpOEWFKQW0EOG9AWRI90VA8RUGWE9R8BIhDJFOIBSDF";
  local_118[2] = "SDSL;FKOKEIER0T9W85690843GJDKVJASFQLIGUQWO;ECLDskvjar;lgksjg1dflb";
  local_118[3] = "kjhKKHJLKlkjhlksriufer9erudskjhLKHJW3EROIWUEOIUkjkhsdllkejgrgpdffSljk";
  local_f8 = "sdrdgkj4598fgjkksdffvggvljkt66yhpooijjKJWEROJISDOVIHEERGNoqiqwjedqwkd_jaasfefn";
  local_f0 = "wewro3k34j4r39vjwewlkrfjwefoiasjALDLADSLFKWJEFWk23eqwqfweoryirut0h9dfbjlkwelkw1kj3e3f"
  ;
  local_e8 = 
  "xdlkjefwi9eru8239r2efjkldsvjlwk4g439berflklkjwff1982eiuqukKJSDFKJWEEF0OSDVJLKSKSLKJEFSJW0";
  local_e0 = 
  "098765432QWERTYUIOPASDFGHJKLZXCVBNM,W453948573498EWIFUERRGKHJFGBKDJGNBDDGksljgerogijdflkb_ckdfbj"
  ;
  local_d8 = 
  "dflfkgdjflgkjdflgsktjhworepgqajrlvLKLSADDKGJWREOGSJIFDLBKXDFGJBLKEKRTHHJOPSFGBJLSKFDGBJSLsdlfkdf4ldkfgj"
  ;
  local_d0 = 
  "DSLDFKGJWREGPOIWERJBSDFLKJBDFGLBKJklsdjff3948tueg98idduvvoierrguj0eer9gjgodfbbojiereoigsdfkgjdfdfdfdfdfNdb"
  ;
  local_c8 = 
  "dglkjerge9r0gwe9fwfoiuiSKDFJFWER9GDEORIJGDFOIBJRTOIBJDFOBIKJER90GGU3U3EGF09USEDGOISDFJGDOetewrtwertwfwFIJB_DF"
  ;
  local_c0 = 
  "XFLLKVJERGPERG9W8UER0G9SPUDFBOIIJDFOIIBSRT9BURERFBOIDFDBKSJDFBSDFOIBUJSFOIBJDDFLKLKBJSRT09HWRBBOJIDFJOIdfdfsdeffsdWOI"
  ;
  local_b8 = 
  "DFDVLKJWWEFF983R7239812IUEASDFKJSDKJSFrgsdfgsdfgsdfgsddRTOISIJFBDFKGJBDFOGOLNIJDdherethdrgbrffbPWRGQ98ETW98EFSIUDdfsd4fsdfVH"
  ;
  local_b0 = 
  "XDKXDJVER9847T398289341092E09EFUFDVJW5Rdfgdfg947T98EREGIUUDFVHJEE9R88TUEED99FVIUHJW9E8FFUQW039DID0CVIDKDFV0E9RUVE09UUJdsfsdfSsdfs"
  ;
  local_a8 = 
  "REZEVkxLSldXRUZGOTgzUjcyMzk4MTJJVUVBU0RT0lTSUpGQkRGS0dKQkRGT0dPTE5JSkRkaGVyZXRoZHJnYnJmZmJQV1JHUTk4RVRXOThFRlNJVURWSAsdfsdfsdfsdfysd=="
  ;
  local_a0 = 
  "3158117bae155dab40b42244b9554d0d92ad05e8f11d5fe22d3334b84fd3aa5b0289431069ae8757448ae6dfd61c8edasfsdfsdfsdefweoifusiodfjsdfdfsdfsdfsdf_sdfk"
  ;
  local_98 = 
  "sdldfkerjguwoierfhjKJLJSDFHROIGEHVSDOIHkjsdhgeorighsdslvkndfljkbfjgbldhkfjghdflgkjsdfpogewrigwweroiojdflkvjdfklgdfjgdlfkgjdfgdlfSDFDRGERGDFRGEkj21"
  ;
  local_90 = 
  "xlxdkfjddrgloeiut9348ut398gf3jur9gfeirjklefejlkKLSDSJGEORIGEJRGOIEJRGOEIRGJDLFKGDRGEIOWUERWEIORUWEksehj9f38ru239r2e9jf3948fj34oiferjgDRGSDFGDSFGSD3FGSDFGSDeoirjg"
  ;
  local_88 = 
  "dfgldkjgw958yt3u4ro09eijusdikjhOSIDHFWW9E8FWOJEFSWJFDLKFDJskdgher49g383egijoigjerlgkejrglgeworigjwregvdfmvdfoigerugeoirjgrolgDFGDSFGSDFGSDFGSDFGSDFGWERTIWjidfdlkvvgdjfge0rg"
  ;
  local_80 = 
  "dlkfjdflfkdfjfgjoi4353KSDJDHFW938RR23IFHWksdjdhgweoituweoigjeglksdgjdslkgjweoijdskvgjdfsklbjhdfgwoeireruqwopqiuyiuIUQWY293R23OIF23R9O32R2378R23IUR2H3sfsdfsdfsdfsdfsfsdfsdfs3dfsR"
  ;
  local_78 = 
  "SDSFLKSDFLRKGJkjsdhfw933r823urowieruwoeitueoriteuergoidfjgdlfkgjeeroigerjgoijgvdlllllllllllllllllllkoerieurtoieruteroituDFSDFSDFSDFSDFSDFSDFSERTWEReeewereeeeeeorituwpoqwiposfkjsRld"
  ;
  local_70 = 
  "fdghjkytrewqdfgbh9u8toiu4jrwefud98uvgijerkwefiudsvosoifjwrek3efoiusdvffijkewioiduvjekwiosdouvijsdfkelodsviudjsfqeudvs9oijfkiodvdknfdhewuwksam,m,.fgm,glkflkflkvoicxjdksedhsfvoijnfsdskehdvio"
  ;
  local_68 = 
  "KSJDFHSIDFWEUIshf3w894efiowejknefioewuwghjkfkkdfkjvdfkdvbfiourifiuruifodrjifjoonvjfkiivojiodjdoivvjdfovdifjdvooooooooooooooooooooooooooooooooffijvdlfkvdjfl2329382@@@@@##$#$#$$%vdfkjvdlfkvj1df"
  ;
  local_60 = 
  "lxkcvjdfolvekjvkldkfvjdlllllllllllllllkeowfiefwjeofwoejwfeeeeeeeeeeeeeeeeeeeeeeeeidjsdlvskdfjsdlkfjweoiweifjovdjivoeijrveorijverlkjelrbkkbjerlkejervl%$$%TRGEFGFHFGHFGHJFGHkvjeerlgjerovjervoerNijve"
  ;
  local_58 = 
  "LKSDJFWKERJ329FWJEFLSSKDFJSDLKsksejfeh4foeiwrjverlg4jrtlgelkjSLKDFJGBRTORJTBLKDFJBE9RTG3U4GFWOIJjveroibjrthhjtie0orithrjtbrhoithjrotiwjer0oihgeirtDFGDFGDDFGWERGRETHRTYJR^TYUJTYJT%%oidjfgblektrjbelgrgkbjdfnglkjdflg"
  ;
  local_12c = 0;
  bzero(local_48,0x23);
  local_48[0] = 'J';
  local_48[1] = 0x49;
  local_48[2] = 0x53;
  local_48[3] = 0x43;
  local_44 = 0x54;
  local_43 = 0x46;
  local_42 = 0x7b;
  local_134 = 7;
  while (local_134 < 0x20) {
    if (local_12c == 0) {
      local_48[local_134] = local_118[0][8];
    }
    else {
      sVar1 = strlen(local_118[local_12c + -1]);
      local_48[local_134] = local_118[local_12c][sVar1];
    }
    local_134 = local_134 + 1;
    local_12c = local_12c + 1;
  }
  local_28 = 0x7d;
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

このコードからlocal_48のデータを算出する。

data = [
    'AFLdfgArTkdwelkfkmkasFFEWSWasCFsfsdfewRRWDSCFAWDHg',
    'sddgrlksjpothjafkgpOEWFKQW0EOG9AWRI90VA8RUGWE9R8BIhDJFOIBSDF',
    'SDSL;FKOKEIER0T9W85690843GJDKVJASFQLIGUQWO;ECLDskvjar;lgksjg1dflb',
    'kjhKKHJLKlkjhlksriufer9erudskjhLKHJW3EROIWUEOIUkjkhsdllkejgrgpdffSljk',
    'sdrdgkj4598fgjkksdffvggvljkt66yhpooijjKJWEROJISDOVIHEERGNoqiqwjedqwkd_jaasfefn',
    'wewro3k34j4r39vjwewlkrfjwefoiasjALDLADSLFKWJEFWk23eqwqfweoryirut0h9dfbjlkwelkw1kj3e3f',
    'xdlkjefwi9eru8239r2efjkldsvjlwk4g439berflklkjwff1982eiuqukKJSDFKJWEEF0OSDVJLKSKSLKJEFSJW0',
    '098765432QWERTYUIOPASDFGHJKLZXCVBNM,W453948573498EWIFUERRGKHJFGBKDJGNBDDGksljgerogijdflkb_ckdfbj',
    'dflfkgdjflgkjdflgsktjhworepgqajrlvLKLSADDKGJWREOGSJIFDLBKXDFGJBLKEKRTHHJOPSFGBJLSKFDGBJSLsdlfkdf4ldkfgj',
    'DSLDFKGJWREGPOIWERJBSDFLKJBDFGLBKJklsdjff3948tueg98idduvvoierrguj0eer9gjgodfbbojiereoigsdfkgjdfdfdfdfdfNdb',
    'dglkjerge9r0gwe9fwfoiuiSKDFJFWER9GDEORIJGDFOIBJRTOIBJDFOBIKJER90GGU3U3EGF09USEDGOISDFJGDOetewrtwertwfwFIJB_DF',
    'XFLLKVJERGPERG9W8UER0G9SPUDFBOIIJDFOIIBSRT9BURERFBOIDFDBKSJDFBSDFOIBUJSFOIBJDDFLKLKBJSRT09HWRBBOJIDFJOIdfdfsdeffsdWOI',
    'DFDVLKJWWEFF983R7239812IUEASDFKJSDKJSFrgsdfgsdfgsdfgsddRTOISIJFBDFKGJBDFOGOLNIJDdherethdrgbrffbPWRGQ98ETW98EFSIUDdfsd4fsdfVH',
    'XDKXDJVER9847T398289341092E09EFUFDVJW5Rdfgdfg947T98EREGIUUDFVHJEE9R88TUEED99FVIUHJW9E8FFUQW039DID0CVIDKDFV0E9RUVE09UUJdsfsdfSsdfs',
    'REZEVkxLSldXRUZGOTgzUjcyMzk4MTJJVUVBU0RT0lTSUpGQkRGS0dKQkRGT0dPTE5JSkRkaGVyZXRoZHJnYnJmZmJQV1JHUTk4RVRXOThFRlNJVURWSAsdfsdfsdfsdfysd==',
    '3158117bae155dab40b42244b9554d0d92ad05e8f11d5fe22d3334b84fd3aa5b0289431069ae8757448ae6dfd61c8edasfsdfsdfsdefweoifusiodfjsdfdfsdfsdfsdf_sdfk',
    'sdldfkerjguwoierfhjKJLJSDFHROIGEHVSDOIHkjsdhgeorighsdslvkndfljkbfjgbldhkfjghdflgkjsdfpogewrigwweroiojdflkvjdfklgdfjgdlfkgjdfgdlfSDFDRGERGDFRGEkj21',
    'xlxdkfjddrgloeiut9348ut398gf3jur9gfeirjklefejlkKLSDSJGEORIGEJRGOIEJRGOEIRGJDLFKGDRGEIOWUERWEIORUWEksehj9f38ru239r2e9jf3948fj34oiferjgDRGSDFGDSFGSD3FGSDFGSDeoirjg',
    'dfgldkjgw958yt3u4ro09eijusdikjhOSIDHFWW9E8FWOJEFSWJFDLKFDJskdgher49g383egijoigjerlgkejrglgeworigjwregvdfmvdfoigerugeoirjgrolgDFGDSFGSDFGSDFGSDFGSDFGWERTIWjidfdlkvvgdjfge0rg',
    'dlkfjdflfkdfjfgjoi4353KSDJDHFW938RR23IFHWksdjdhgweoituweoigjeglksdgjdslkgjweoijdskvgjdfsklbjhdfgwoeireruqwopqiuyiuIUQWY293R23OIF23R9O32R2378R23IUR2H3sfsdfsdfsdfsdfsfsdfsdfs3dfsR',
    'SDSFLKSDFLRKGJkjsdhfw933r823urowieruwoeitueoriteuergoidfjgdlfkgjeeroigerjgoijgvdlllllllllllllllllllkoerieurtoieruteroituDFSDFSDFSDFSDFSDFSDFSERTWEReeewereeeeeeorituwpoqwiposfkjsRld',
    'fdghjkytrewqdfgbh9u8toiu4jrwefud98uvgijerkwefiudsvosoifjwrek3efoiusdvffijkewioiduvjekwiosdouvijsdfkelodsviudjsfqeudvs9oijfkiodvdknfdhewuwksam,m,.fgm,glkflkflkvoicxjdksedhsfvoijnfsdskehdvio',
    'KSJDFHSIDFWEUIshf3w894efiowejknefioewuwghjkfkkdfkjvdfkdvbfiourifiuruifodrjifjoonvjfkiivojiodjdoivvjdfovdifjdvooooooooooooooooooooooooooooooooffijvdlfkvdjfl2329382@@@@@##$#$#$$%vdfkjvdlfkvj1df',
    'lxkcvjdfolvekjvkldkfvjdlllllllllllllllkeowfiefwjeofwoejwfeeeeeeeeeeeeeeeeeeeeeeeeidjsdlvskdfjsdlkfjweoiweifjovdjivoeijrveorijverlkjelrbkkbjerlkejervl%$$%TRGEFGFHFGHFGHJFGHkvjeerlgjerovjervoerNijve',
    'LKSDJFWKERJ329FWJEFLSSKDFJSDLKsksejfeh4foeiwrjverlg4jrtlgelkjSLKDFJGBRTORJTBLKDFJBE9RTG3U4GFWOIJjveroibjrthhjtie0orithrjtbrhoithjrotiwjer0oihgeirtDFGDFGDDFGWERGRETHRTYJR^TYUJTYJT%%oidjfgblektrjbelgrgkbjdfnglkjdflg'
]

flag = ''
flag += 'J'
flag += chr(0x49)
flag += chr(0x53)
flag += chr(0x43)
flag += chr(0x54)
flag += chr(0x46)
flag += chr(0x7b)
for i in range(7, 0x20):
    if i == 7:
        flag += data[0][8]
    else:
        l = len(data[i-8])
        flag += data[i-7][l]
flag += chr(0x7d)
print flag
JISCTF{Th1S_1S_4N_e4Sy_R3v3Rs1Ng}

Rev 102 (Reversing 150)

$ file be_true 
be_true: python 2.7 byte-compiled
$ mv be_true be_true.pyc
$ uncompyle6 be_true.pyc
# uncompyle6 version 3.7.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.17 (default, Sep 30 2020, 13:38:04) 
# [GCC 7.5.0]
# Embedded file name: be_true.py
# Compiled at: 2019-12-06 22:36:32
import operator
flag = 0
power = (12 * flag + 44) / 4 - 1234 / 617 * flag - sum([1, 4, 7])
flag *= power
ppc = filter(lambda cc22: not any(cc22 % uu22 == 0 for uu22 in range(2, cc22)), range(2, 10000))
dat = reduce(operator.mul, (ppc[i] ** int(str(flag)[i]) for i in range(len(str(flag)))))
print dat == 3560267726635400465627540581996487760054035685444946475657505105403063442856963534755133504166293811169018648637812735995934052617540980495952470560844800443626277335201401028364068797946796579956457852457279957300619892623751175557650873016730889125068146398488627192356718363004881175012104986391177956396290571688585499385019181192105543180552708742672275440530116680223358366613050183523086165910780504269235376773473500L
# okay decompiling be_true.pyc

逆算していけばよい。

ppcは2-10000の素数の配列。
flagは数値。

dat = 2**(flagの1桁目) * 3**(flagの2桁目) * 5**(flagの3桁目) * ...

期待値を素因数分解すればpowerを掛けた後のflagの値がわかる。

flag = 27390185364980124152929536025508671562111273743374147056

この値をCとする。

power = (12 * flag + 44) / 4 - 1234 / 617 * flag - sum([1, 4, 7])
      = (3 * flag + 11) - 2 * flag - 12
      = flag - 1

C = flag * (flag - 1)

2次方程式を解けばフラグがわかる。

JISCTF{5233563352533350594343304433}

このままだとフラグが通らない。この数値を2桁ずつ16進数としてデコードする。

from sympy import *

ppc = filter(lambda cc22: not any(cc22 % uu22 == 0 for uu22 in range(2, cc22)), range(2, 10000))
target = 3560267726635400465627540581996487760054035685444946475657505105403063442856963534755133504166293811169018648637812735995934052617540980495952470560844800443626277335201401028364068797946796579956457852457279957300619892623751175557650873016730889125068146398488627192356718363004881175012104986391177956396290571688585499385019181192105543180552708742672275440530116680223358366613050183523086165910780504269235376773473500

fac = factorint(target)
C = ''
mal = 1
for i in range(len(ppc)):
    if ppc[i] in fac:
        power = fac[ppc[i]]
        C += str(power)
        mal *= pow(ppc[i], power)
    else:
        C += '0'
    if mal == target:
        break

C = int(C)

x = symbols('x')
ans = solve(x**2 - x - C, x)

for i in range(len(ans)):
    if ans[i] > 0:
        flag = ans[i]
        break

print '[+] flag =', flag

flag = str(flag)
msg = ''
for i in range(0, len(flag), 2):
    msg += chr(int(flag[i:i+2], 16))

flag = 'JISCTF{%s}' % msg
print '[*] flag =', flag

実行結果は以下の通り。

[+] flag = 5233563352533350594343304433
[*] flag = JISCTF{R3V3RS3PYCC0D3}
JISCTF{R3V3RS3PYCC0D3}

Malicious (Forensics 100)

docxが添付されている。ZIP解凍したら、バーコードの画像が入っているので、読み取る。
f:id:satou-y:20201201221758p:plain

JISCTF{B4RC0D3_1M4G3_2019}

So Easy (Forensics 100)

No.182のパケットで認証情報をPOSTしていて、usernameにフラグが設定されていた。

Form item: "username" = "JISCTF{V3RY_34SY_PC4P_F1L3}"
JISCTF{V3RY_34SY_PC4P_F1L3}

Malicious 2 (Forensics 100)

docxが添付されている。

$ file mycv.docx 
mycv.docx: CDFV2 Encrypted

パスワードがかかっているので、johnでクラックする。

$ office2john.py mycv.docx > hash.txt
$ john --wordlist=dict/rockyou.txt hash.txt
Warning: detected hash type "Office", but the string is also recognized as "office-opencl"
Use the "--format=office-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (Office, 2007/2010/2013 [SHA1 128/128 AVX 4x / SHA512 128/128 AVX 2x AES])
Cost 1 (MS Office version) is 2007 for all loaded hashes
Cost 2 (iteration count) is 50000 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
princess101      (mycv.docx)
1g 0:00:00:22 DONE (2020-11-21 07:16) 0.04349g/s 434.6p/s 434.6c/s 434.6C/s sammy2..nopassword
Use the "--show" option to display all of the cracked passwords reliably
Session completed

パスワード princess101 で開くと、フラグが書いてあった。

JISCTF{H4PPY_HUNT1NG}

Unknow Ransomware (Forensics 150)

13回base64デコードすると、PNGファイルの逆順の内容になっているので、元に戻す。

with open('flag.enc', 'rb') as f:
    data = f.read()

for _ in range(13):
    data = data.decode('base64')

with open('flag.png', 'wb') as f:
    f.write(data[::-1])

生成された画像に逆順でフラグが書いてあった。
f:id:satou-y:20201201222253p:plain

JISCTF{R3V3RS3_1M4G3_C0NT3NTS}

Colorfull (Forensics 300)

No.524パケットからFTPで送受信しているfiles.zipを保存する。パスワード付きZIPになっているが、パスワードはパケットに見当たらないので、クラックしてみる。

$ zip2john files.zip > hash.txt
ver 81.9 files.zip/secret_data.txt is not encrypted, or stored with non-handled compression type
$ john --wordlist=dict/rockyou.txt hash.txt
Warning: detected hash type "ZIP", but the string is also recognized as "ZIP-opencl"
Use the "--format=ZIP-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 128/128 AVX 4x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
labeba           (files.zip/secret_data.txt)
1g 0:00:00:01 DONE (2020-11-21 10:29) 0.9900g/s 10138p/s 10138c/s 10138C/s toodles..11221122
Use the "--show" option to display all of the cracked passwords reliably
Session completed

パスワード labeba で解凍すると、secret_data.txtが展開された。その内容は数字が3個、"-"区切りで10693行並んでいる。

34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
34-177-76
  :
34-177-76
29-100-17
0-0-0
8-100-66
34-177-76
29-100-17
0-0-31
24-177-66
20-40-0
0-0-17
20-152-76
  :

RGBを表していると推測する。画像にするためには、縦、横を知る必要があるので、行数を素因数分解してみる。

10693 = 17 * 17 * 37

横:17 * 17、縦:37で画像にしてみる。

from PIL import Image

WIDTH = 17 * 17
HEIGHT = 37

with open('secret_data.txt', 'r') as f:
    lines = f.readlines()

img = Image.new('RGB', (WIDTH, HEIGHT), (255, 255, 255))

i = 0
for y in range(HEIGHT):
    for x in range(WIDTH):
        r = int(lines[i].rstrip().split('-')[0])
        g = int(lines[i].rstrip().split('-')[1])
        b = int(lines[i].rstrip().split('-')[2])
        img.putpixel((x, y), (r, g, b))
        i += 1

img.save('flag.png')

生成した画像にフラグが書いてあった。
f:id:satou-y:20201201222459p:plain

JISCTF{EXF1LT3R4T3D_D4T4_1N_1M4G3_F1L3}

Hidden (Crypto-Stego 100)

StegSolveで開き、[Analyse]-[Data Extract]で、RGB、それぞれのLSBのみチェックを入れると、フラグが現れる。
f:id:satou-y:20201201222611p:plain

JISCTF{G00D_J0B_Y0U_EXTR4KT_M3!!!}

Upside Down!! (Crypto-Stego 150)

{}の中がAtbash暗号で暗号化されている。https://www.dcode.fr/atbash-cipherで復号する。

JISCTF{upside_down_english_characters_cryptography}

Baby Crypto (Crypto-Stego 200)

暗号化処理は以下の通り。

ct: 現在時刻(小数第2位まで)
ctをシードにランダム値をとる。
k1: flagの長さだけ256までのランダム値の配列
ciphertext: flag+c1とk1+[0x99]*len(ct)でXOR

末尾18桁を0x99とXORすれば時刻がわかり、ランダム値もわかるので、復号できる。

#!/usr/bin/python3
import random

with open('flag.enc', 'rb') as f:
    enc = f.read()

ct = ''
for i in range(18):
    ct += chr(enc[-18+i] ^ 0x99)
print('[+] ct =', ct)

random.seed(ct)
k1 = [random.randrange(256) for _ in range(len(enc) - 18)]

flag = ''
for i in range(len(k1)):
    flag += chr(enc[i] ^ k1[i])

print(flag)

実行結果は以下の通り。

[+] ct = 1605733600.6308804
JISCTF{B4BY_ENCRYPT10N_JISCTF2020_QUALIFICATION_RND_101}
JISCTF{B4BY_ENCRYPT10N_JISCTF2020_QUALIFICATION_RND_101}

Logic (Crypto-Stego 200)

1バイトの鍵のXOR暗号と推測し、復号する。

with open('ciphertext.txt', 'rb') as f:
    enc = f.read()

key = ord(enc[0]) ^ ord('J')
flag = ''
for i in range(len(enc)):
    flag += chr(ord(enc[i]) ^ key)

print flag
JISCTF{W34K_X0R_3NKRYPT10N_M4N_$!}

Not baby crypto (Crypto-Stego 300)

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

■generate_key()
・ran: ランダム32バイト文字列の16進数文字列
・key_list: ranを2バイトごとにした配列
・key_listの各要素を10で割った余りを結合して返す。

■encrypt(key)
・file: 'plainData'ディレクトリのファイル
・enc_file: fileの拡張子をencにしたもの
・平文の各文字と鍵のXORをとる。(A)
・(A)の各要素と鍵の2倍のXORをとる。

各文字のASCIIコードは末端6bitしか影響しないので、それより前の情報から1ビットずつ鍵を導く。その後に末端6ビットを条件を満たすようにして鍵を求める。鍵が分かればXORで復号できる。

with open('flag.enc', 'r') as f:
    enc = f.read()

enc_l = [int(enc[i:i+32]) for i in range(0, len(enc), 32)]

b_enc_base = bin(enc_l[0] - (enc_l[0] & 0b1111111))[2:]

k1 = '0'
k2 = '1'
for i in range(len(b_enc_base)-7):
    c = int(b_enc_base[i+1])
    k1 += k2[-1]
    k2 += str(c ^ int(k1[-1]))

k1 += '0' * 6
k1_base = int(k1, 2)

for i in range(64):
    key = k1_base + i
    cb0 = ord('J') ^ key
    ct0 = cb0 ^ (key * 2)
    if ct0 == enc_l[0]:
        break

flag = ''
for c in enc_l:
    code = c ^ key ^ (key * 2)
    flag += chr(code)
print flag
JISCTF{R4ND0M_NUMB3RS_4S_K3Y_!!!}

Not that easy!! (Crypto-Stego 400)

Fを0、Tを1として逆順にすると、2進数としてデコードできそう。このあと、また0, 1の文字列になる。いろいろ試した結果、ベーコニアン暗号と推測できるので、対応付けて復号する。

bacon = {'00000': 'A', '00001': 'B', '00010': 'C', '00011': 'D', '00100': 'E',
    '00101': 'F', '00110': 'G', '00111': 'H', '01000': 'I', '01001': 'K',
    '01010': 'L', '01010': 'M', '01100': 'N', '01101': 'O', '01110': 'P',
    '01111': 'Q', '10000': 'R', '10001': 'S', '10010': 'T', '10011': 'U',
    '10100': 'W', '10101': 'X', '10110': 'Y', '10111': 'Z'}

with open('my_data.dat', 'r') as f:
    data = f.read().rstrip()

data = data.replace('F', '0')
data = data.replace('T', '1')
data = data[::-1]

b64 = ''
for i in range(0, len(data), 8):
    b64 += chr(int(data[i:i+8], 2))

ct = b64.decode('base64').rstrip()
flag = ''
for i in range(0, len(ct), 5):
    flag += bacon[ct[i:i+5]]

flag = 'JISCTF{%s}' % flag
print flag
JISCTF{BACONCIPHERISNOTGOODTOENCRYPTDATA}