Codeforces Round #176 (Div. 1) D. Tourists

JOIerお家芸感。なんかこれはJOIerは結構見た瞬間簡単だなあと思う気がする。おそらくDではない。

 

原点にある点がn個あり、i個目の点は時刻q[i]に原点を出発し、x軸の正方向に沿って速さ1で進む。また、それとは別にm個の線分が存在して、i番目の線分は時刻t[i]に区間[l[i], r[i]]を覆うように一瞬の内に出現する。各点について、点が線分に覆われながら移動している時間の長さを求めよ。

 

なんか本当に簡単で、div1Dにしては簡単だと思うんだけど、1つの線分(l, r, t)に注目した時、q[i] >= t-lであるような点iはその線分によって、r-l秒覆われていることになる。そして、これはどう見てもqに関して単調性がある、すなわち、q[j] >= q[i] >= t-lであれば、点jもr-l秒覆われることになるから、点をq[i]で昇順にsortしておけば、二分探索により、r-l秒覆われる点が全て分かる上、imos法を用いれば、全ての線分について考えたときの具体的な秒数の合計も一瞬で分かる。ここで少し怖くなるのは、t-r+1 <= q[i] < t-lであるような点iについては、点が移動している途中で線分が出現するので、具体的には、q[i]+r-t秒だけ微妙に覆われることになる。しかしこれも同様に二分探索とimos法によって対応可能で、どうするかというと、二分探索でt-r+1 <= q[i] < t-lを満たすiの範囲を求めた上で、定数r-tを普通のimos法で計算(A[i]にi番目の点のこの定数に関する累積和が入るとする)、定数1をimos法で計算する(P[i]にこの累積和が入るとする)と、微妙に覆われた時間の累計はP[i]*q[i] + A[i]で求まる。1次関数の累積を求めているイメージ。基本的にはこれだけなんだけど、後は線分に重複があるところだけ解決しないといけない。これは、各座標において、その座標を覆う線分の内、tが最小であるものを求めればよい。これは結構明らかだと思う(tが最小であるような線分以外を無視すれば重複は解決され、かつ最も覆われる点が多い)。実際に各座標についてやると当然計算量がやばいが、これは簡単なイベント走査で解決できる。線分(l, r, t)についてイベント(l, t)とイベント(r, t)を作成し、イベントの集合を1つ目の要素に関してソート。multisetを1つ使って(l, t)が来たらtをinsert、(r, t)が来たらtをeraseしていけば、連続する2つのイベント(x1, t1), (x2, t2)について、区間[x1, x2]におけるtの最小値は、イベント(x1, t1)を処理し終わった後のmultisetの最小値となる。これも多分典型。なので各イベントの処理後に新しい線分の集合に、それらの値を追加してやれば、重複を取り除いた線分が全て求められて、解ける。このやり方ではどうやっても、重複を取り除いた線分は流石に自明にO(m)個しか存在しないので、かなり適当に追加しても問題ない。

 

ちなみに初め誤読して、「点は線分に覆われなくなったらその場で止まる。」という条件があると思っていたんだけど、それでも解けるので考えてみてください。

Codeforces Round #176 (Div. 1)

codeforces.com

はいサイアクの1完。

Bが分からない時は本当に辛いンゴねえ

 

A. nが与えられる。1~nの順列P[1]~P[n]が「∀i(1 <= i <= n) P[P[i]] = n+1-i」を満たす。このようなPを1つ出力せよ。そのようなPがなければ-1と出力すること。

順列なのでグラフではdirected "cycle"(cyclicではない) graphの集合と対応する。グラフにおいては、上の条件は、頂点iから有向辺を2本辿った先の頂点が常にn+1-iであることを表している。頂点n+1-iから2本辿った先が頂点iにならないといけないから、そのような条件を満たす順列は、大きさ1または4のdirected cycle graphのみで構成されている(ただしnが奇数でかつ頂点(n+1)/2についてのみ大きさ1は許される)。

よって与えられたnをまず4で割って、剰余が2, 3であればそのようなPはない。

後は適当にやればよくて、nが奇数であれば、ans[(n+1)/2] = (n+1)/2にだけ気をつけて、(k)→(k+1)→(n+1-k)→(n-k)→(k)みたいな閉路を考えて答えを構成してやればよい。

なんかめっちゃバグった。アホ。

 

B. 1~nの順列P[1]..P[n]と自然数kをとって、P[2]P[3]...P[k]P[1], P[k+2]P[K+3]...P[2k]P[k+1], ...を新しい順列として返す写像f(P, k)を定義する(つまり順列をk個ごとで区切って、区切った中で先頭の数を最後に移動させる)。L[1] = [1, 2, 3, ..., n]として、L[k] = f(L[k-1], k)と置く。L[n]を求めよ。

まず最後に移動させる操作の回数は、Σ[i=2..N]ceil(N/i) = O(NlogN)であるから、L[1][1] ~ L[n][n]のn^2個の数の内、いい感じに最後に移動させる数だけを処理することが出来れば、つまり1つの区間内のシフトをO(1)で出来ればO(NlogN)が達成できることはわかる。

ここからが問題で、僕はL[n]におけるi番目の数がL[1]においてどこにあったかというのを独立に考えようとしたのでドツボにハマった。つまり、L[1] ~ L[n]を正方形に並んだn^2個の数だというイメージで見た時、僕は縦に区切って処理しようとしてしまったんだけど、横に区切らないといけなかった。横に区切る、つまりL[k]からL[k+1]を一度に求めようという気持ちでいくとちゃんと出来て、これは、最後に移動させられる数を無視してみると、左に1個ずつズレていくだけだから、このズレを実際にはズラさずに固定してやると良い。どういうことかというと、配列L[k]から配列L[k+1]を作る時、本来ならL[k+1][i-1] = L[k][i]とやるわけだけど、定数1分ずつ全てがズレるのだから、i' = i-1と置いてやれば、L[k+1][i'] = L[k][i]と出来て、L[k+1]を新しく生成するのではなく、L[k]をinplaceに置き換えることにすると(dpのメモリ削減みたいなもん)、1ズレる数に関しては値を参照したりコピーする必要がなくなる。後は、最後に移動させる必要のある数だけ、L[k+1][i+k-1] = L[k][i]みたいな感じにする必要があるから、i'とinplaceを考えると、後ろからL[i'+k] = L[i]で更新していくだけでよく、上の評価式からこの更新の回数はO(NlogN)であるから解ける。

 

ちなみにL[n][i]がL[1]においてどこにあるかという考え方だと、O(NlogN(logN+loglogN))は達成できるんだけど、N=10^6でしかも旧版の問題に対する処置としてTLEが1/2の1秒だから普通に間に合わない。

 

自分用メモ: 流石にどっちで対処するかぐらいちょっと考えましょう、というか結構dpの変数置き換えはよくやるし好きなんだからこれぐらい思いつくべき

Codeforces Round #127 (Div. 1)

codeforces.com

f:id:potetisensei:20161024174651p:plain

 

tourist回嫌い(直球)

色々やってしまい1完でした。

 

A. 以下の様な条件を満たす行列を考える。

  • n*nの正方行列である
  • 全ての要素が0または1
  • 行列は上下左右対称すなわちAi, j = An - i + 1, j and Ai, j = Ai, n - j + 1が成り立つ
  • 上下左右に隣り合う要素同士が同時に1となることはない

そのような行列の内、1をx個要素としてもつものを構成することの出来る最小のnを求めよ。

なんか本当にヤバそうだなあと思いながら適当にやったら6WAぐらいして諦めた。

計算量は問題にならないので、n=1から順に試していけば良い。

そして基本的にまずnが偶数か奇数かで場合分けしないといけない。

nが偶数の時は結構簡単で、最後の条件と対称性から、中央の2行2列は0で埋まる。すると小さい正方形4つに分かれるイメージになって、それらの正方形には最後の条件以外を特に考えないで埋める事ができるから、xが4の倍数であってかつx/4 <= MAX_PLACE( (n-2)/2)であればよい。ここでMAX_PLACE(t)はt*tの正方形に最後の条件を満たすように入れることの出来る1の最大数。

nが奇数の時が辛い。nが奇数の時、nが偶数の時と同様に中央の1行1列以外の場所に1が存在する時は、勝手に対称性から4つ発生するので、4の倍数ずつ増える。

中央の1行1列でかつ、完全な中央でない場所に1が存在する時は、対称性から2つ発生するので2の倍数ずつ増える。

完全に中央に1が存在する時は、対称性を考えなくていいので1個だけ増やせる。

その上で、中央が1かそうでないかによって、置くことの出来る数が変わる。

まず、中央が1である時、すなわちxが奇数の時、他の場所に、1は、0 <= a <= MAX_PLACE( (n-1)/2) ∧ 0 <= b <= ( (n-1)/4) * 2を満たす様な(a, b)で表せる4 * a + 2 * b個であれば置くことが出来る。

中央が1でない時、すなわちxが偶数の時、(n-1)/2が奇数の時、中央に1がないことによって、2 * bのbが2増えるため、これを考慮しないと落ちる。分かってたけど実装しなかったのはカス。

もうちょっと良い方法ないのかなあ。

 

B. n, m, Cが与えられるので、Σ[i=1..n]Σ[j=1..m] C[i][j] * D{(4X, 4Y), (4j-2, 4i-2)}^2 を最小化するようなX, Yと最小化された値を求めよ。ただしDはユークリッド距離

なんかユークリッド距離とマンハッタン距離を頭の中で取り違えるやらかしが発生したせいでめちゃくちゃ悩んだ。距離に2乗がついてるから次元が分解出来ないンゴねえって感じ。最終的に、定点に対するマンハッタン距離の関数D(X, Y)は2次元で離散な凸関数になっていて、2乗しても凸のままで、凸関数の和は凸関数だから、結局全体として2次元の凸関数だから3分探索すればエエやんけと思って、O(NM * log3/2(N) * log3/2(M))を書いたらTLEして落ちた(概算が10^9なので当たり前、概算しましょう)。でTLEしてオイオイオイとなった時にユークリッド距離であることに気づく。ユークリッド距離の2乗はX, Yの項が分離するから次元が分解できるンゴねえ....

よって適当に上の目的関数をXとYの項に分解して同じように3分探索すると、logが1つ消えて通った。

 

C. n個の頂点がn-1本の辺によって接続され連結になっていて、このグラフは道である。各辺には人が通れる回数の上限が決まっている。好きな頂点から初めて好きに辺を通って良い時、最大で何回移動することが出来るか。

n-1本の辺の通ることの出来る上限回数を左からE1, E2, ..., En-1とする。

ちょっと考察すると、全体で移動出来る回数は、1 <= a <= b <= c <= d <= n-1を用いて、Σ[i=a..d-1] Ei - |{Ei | Eiが奇数 i=a..b-1)}| - |{Ei | Eiが偶数 i=b..c-1)}| - |{Ei | Eiが奇数 i=c..d)}|と表すことが出来、これの最大を求めれば良いことが分かる。これはどうしてかというと、何度も同じ辺を行き来することが出来るから、とりあえず最大化するためにはi番目の辺を通る時はEi-2回往復してしまうと、残り上限2回をどう通るかによって、行って引き返してきたり、行ったまま帰ってこなかったり、そもそも通らなかったりする(当たり前)。これは何を意味しているかというと、全体の経路はかならず、(1度通って引き返してくる辺たち) (1度通ったきり引き返して来ない辺たち) (1度通って引き返してくる辺たち)という並びになってないとおかしい。要は偶数回通る辺、奇数回通る辺、偶数回通る辺というふうに連続して存在している(もしくはこれらの内存在しない辺の集合があってもいい)。図にするとこんな感じ。まあ自分で書いたら分かると思う。

 

f:id:potetisensei:20161024171543p:plain

後は計算量と、初めから上限回数が1の辺の処理が問題になる。

とりあえず後者は一旦無視して考えると、流石にさっきの上の式の4変数を総当りするとO(n^4)になって間に合わないので、自明な変数を削る所から始める。

まずa, dはなくてもいい。これはa, dは左端と右端に設定した方が絶対に通れる回数が大きくなって得だから。

bは流石にないと困るので、これは総当りすることにする(bの総当りでO(n))。

これによって、固定したbに対して、最大の移動回数を与えるようなcを効率良く求めたいと考えられる。

これは結構簡単で、要は「bit列が与えられる。bit列をある場所で分割して、それより左側にあるbitを全て反転させる。この時、立っているbitの数を最小化するにはどの位置で分割すれば良いか。」という問題と同じ。

これは、左から、

diff[idx] := 位置idxまでに出現した1の数と位置idxまでに出現した0の数の差

を累積和的に計算して、segment treeなどに突っ込んでおけば、Range Max Queryによって、maxidx = argmax(diff[idx])が分かって、位置maxidxで反転させると、立っているbitは最小化されて|{Ei | Eiが奇数 i=b..n-1)}| - (diff[maxidx] - diff[b-1])になることが分かる。ちなみに実際には反転させないケースもあるので、|{Ei | Eiが奇数 i=b..n-1)}| - max(diff[maxidx] - diff[b-1], 0)。

さて問題は上限回数が初めから1回であるような辺が含まれている時で、これがかなり罠。なんか上までの考察はすぐ終わったのに本当に嘘&バグらせで辛い思いをした。

さて、上限回数が1回である辺は、当然2回通ることが出来ないから、aからb-1やcからdに含まれては行けない。よって、最も右に存在する1の場所pつまりEp=1となる最大のpを記憶して(1がなければp = 1)、RMQのクエリの区間の左端をpに設定する必要がある(つまり[p, n-1]にクエリを投げる)。これだけだと、RMQを[0, p-2]に飛ばしていないわけだからpより右側の辺を一切通らないケースを考慮していなくて嘘っぽいけど、ある辺iについてそこを通る回数はEi - {0, 1}でこれは常に0以上であるから、さっきも言った通り全ての道を通ったケースが、全ての道を通らないケースに比べて劣ることは絶対にない。

累積和を取ったりindexをいじったりする系の奴、考察が終わっていてもバグりまくるの辛いしどうにかしてくれ。

 

自分向けメモ: コーナーケースとかヤバそうなケース思いついたらとりあえずメモっとけ。考察もう少し丁寧に。嘘が入らない程度に考察は少しずつ積み重ねる。飛躍出来るだけしない。

 

Codeforces Round #177 (Div. 1)

codeforces.com

 

受験終わって初回virtual。3完。辛い。

 

A. k種類の小文字アルファベットから成るちょうど長さnの文字列であって、隣り合う文字が全て異なるものの内、辞書順最小のものは何か。

流石にaから始まるのが辞書順最小で、次は隣り合う文字は異なってないといけないからbで、次はaでと考えると、ababab...となる。後は末尾にk-2種類の文字、それも辞書順最小なので当然'c' ~ 'c'+k-3を登場させると良いけど、ちょっと実装がだるい。k=1とかに気をつける

 

B. σはS={1,..., n}のS→Sな写像であって、「∀t(1 <= t <= k)について∃x(x∈N)が存在して、σ^x(t)=1」∧「∀t(k < t <= n)についてσ^x(t)=1を満たすxが存在しない」を満たす。σは何通りあるか。

こういった写像はdirected pseudoforestと対応するので、自明に1個のdirected pseudotreeと、{k+1, .. , n}→{k+1, .., n]の任意の写像1つの数え上げ。後者は自明に(n-k)^(n-k)。いちおうdirected pseudotreeと書いたけど、実はこの場合は有向辺と無向辺が一対一に対応して数え上げられるので、pseudotreeでいいはず。なんかpseudotreeといえば、閉路の各頂点に、0個以上の木がくっついてる形をしているわけだけれども、道しかくっついていないという嘘想定を何故かしてしまって死んだ。この良く分からん勘違い、春合宿でもやってしまったけどマジで意味が分からん。

結局やることとしては、σ^x(1)=1となるxが存在するわけだから、pseudotreeの閉路上に頂点1は存在する。後は、どうにでも出来ると思うけど、例えば閉路がt個の頂点から構成されるとすると、閉路上にある頂点は残りt-1個に関しては自由なので(n-1)C(t-1)通り。そしてt-1個の並びは自由なので(t-1)!通り。

後は木をこの閉路につけるわけだけど、ここで、一般に、無向グラフにおいて、十分に大きいnについて、n個の互いに非連結な成分C1 ~ Cnをn-1本の辺で連結にする時、異なる連結のさせ方は(π[i=1..n]Ci) × {(Σ[1..n] |Ci|)^(n-2)}通りある。これは∀iで|Ci|=1のとき、cayleyの公式と一致する。

以上から答えは、(n-k)^(n-k) × [Σ[t=1..k-1]{(t-1)! × (k-1)C(t-1) × t × k^(t-k-1)} + (k-1)!] となる(t == kのとき、t-k-1 = -1なことに注意する)。

 

C. 0~nの順列P0~Pnについて美しさを Σ[i=0..n] (i⊕Pi)で定義する(⊕はXOR)。可能な美しさの最大値と最大値を与える順列1つを出力せよ。

等式 A⊕B = A+B - ((A&B) << 1)を用いると(ここで&はAND)、Σ[i=0..n] (i⊕Pi) ≦ Σ(i+Pi) = n(n+1)であって、上界がn(n+1)であることが分かる。

貪欲に順列を構成すると、上界を達成することが出来ることを示す。

まず

  • 2進数でnがk桁で表されるとすると、k桁で表される全ての整数tについて、t⊕x = 2^k-1となるようなxがただ1つ存在する。これはt&x = 0となるようなxがk-1桁以下で表される整数にただ1つ存在することによる。
  • t1⊕x1 = 2^k-1, t2⊕x2 = 2^k-1の時、t1≠t2 ⇒ x1≠x2である
  • 上記の条件でt⊕x = 2^k-1となる時、A&B = 0 ⇔ A+B = A⊕Bを考えるとt+x = t⊕x = 2^k-1である。

ここで、n以下でかつk桁で表される全ての整数の集合、すなわち、”離散な"閉区間(面倒くさいのでこう呼ぶ)S = [2^(k-1), n]に対して、T = {x | t∈S, t⊕x = 2^k-1}とすれば、t+x = t⊕xより、Tは離散な閉区間[2^k-1-n, 2^(k-1)-1]であり、S∩T=∅かつS∪T = [2^k-n-1,  n]であるから、これらを全て消費すると、離散な閉区間[0, 2^k-n-2]が残るので、残った離散な閉区間に対して、同じ問題を再帰的に解いていくと良い。

こうして構成された順列は、全ての項について、i⊕Pi = i+Piが成り立っているので、上の考察からこれは上界を与えている。

なんか実装力が衰えすぎていたのと、この考察中々にややこしいのでめちゃくちゃなバグが発生しまくった。反省。

 

自分向けメモ: 貪欲の下界上界を先に意識するパターン忘れない。一回やったら流石にcombinatoricsは解けて...

DEFCON CTF 本戦

shinhさんがこんな記事を書いてたので、それに習って僕も書いてみる。

shinh.hatenablog.com

ルールとかが変わったみたいな話は流石にもう有名だと思うので、詳しく解説はしないけど、まあ雰囲気とかは練習にCGC形式の問題解いた動画あるので、それ見るといいかもしれない。

www.youtube.com

slackのログとかを元に出来るだけ時系列で書きつつ、僕は何をしていたかということを書いていきたい

準備

6/26

legitbsのブログで2016年のDEFCON本戦はCGCの形式を用いることが発表される。「パッチ適用してバイナリ提出したら次のラウンドでそのバイナリ他のチームに完全公開されるのやばない?」「難読化しないと差分ですぐpatchバレそうだね」「バックドアが必要かもしれない」という話をする(伏線)。後スコアリング意味不明すぎるなという感想。legitbsとDARPAが用意してるドキュメントが💩すぎるのでこれは情報まとめてまだドキュメント読んでないメンバーも把握できるようにすべきだと思ったので、docsを用意した

~7/21

7月半ばまでslackに動きなし。bonoさんやcharoさんやbackerさんなど、メンバーの内何人かが進んで数百個の質問回答が羅列されている💩なFAQなどのドキュメントを読み進めてくれていたので、例のdocsには結構情報が書き出されていた。スコアリングとかルールについてチーム内で議論しながら、どんなmitigationが考えられるかなあみたいな話をする。おそらくここら辺でshinhさんがmitigationツールを書き始めてくれる。

7/22

現状何も決まってなくてやばない?wみたいな雰囲気が漂っていたからか、bataさんが「現状決まってないが早急に解決すべき議題」のリストを投下してくれる。具体的には、

  • 役割分担内役

    本番の競技時間中に誰がパッチを当てるか、PoVを書くか、インフラ管理、運営との折衝をするかなど。どれに何人振り分けるかとかも大事。

  • パッチの方針

    脆弱性を見つけた場合、どういう形式で他のメンバーに共有し、どういう形式でパッチを書き、管理するか。最速でミスなくパッチを適用するということはこれまでのDEFCONにおいては至上命題だったので、やはり重要だと思った。

  • PoVの方針

    脆弱性を見つけ、メンバー間で共有できた場合、xml、Cのどちらの形式でPoVを書き、どの形式でメンバーに共有し、どのような形式でマージ、提出するのか。点数を得るためにはPoVは書かざるを得ない上、1つの問題に複数の脆弱性があるA&Dならではの、複数のPoVのマージの方法などの問題がある。

これに関してまた色々議論があって、僕は「バックドア欲しい」という発言をしていたり、それに対して「でも正直うちみたいな弱小チームのバイナリどこもパクらない気がするし、用意する必要なくね?w」という一理ある意見などがあった(この日はこれ以上この話は進まず)。

自チームのPoVが他チームにどれぐらい有効かみたいなのは当然戦略を立てる上で最重要な情報の1つなわけだけど、そこら辺を見るのにダッシュボードが欲しいという話が出る(伏線)。

shinhさんがボイスチャットでミーティングした方がいいのではという提案をする。確かにした方が良いねという話なので24日の夜ぐらいにしましょうと決定。

デコンパイルサーバー欲しくねという話になり、悪ノリでmageさん担当になる。

これは結局有償版IDA持ってる人間は自分のソフトで出来るので、僕は誰か使っていたのかは把握してないんだけど、有償版IDA持ってない人にとっては、curlワンライナーを叩くだけで、シグネチャとかから関数名推測してくれた状態のデコンパイルした結果が取得できて結構便利だったと思う。

この日を境に進捗が結構ある。

7/23

shinhさんのmitigationツールが具体的な形になる。この時点でも既に結構強くなってて、type1 PoVはかなり防げていた。

bataさんがバイナリ内のlibc関数のシグネチャを作ろうとし始める。CGCのlibcはlibcというほどlibcではなくて、問題ごとにフルスクラッチで書かれてるので、効果がどれぐらいあるのかはあの時点では予想できなかった。

docsに書かれている議題の内いくつかに対して独断で暫定案を書いた。

各種ツール使いづらすぎるという話になり、前日のダッシュボードと共にwebuiを作るべきという話になる。tyageがやることになる(デスマの始まり)。

akiymさんが運営から提供されるチーム間のパケットから入力データを抜き出し、バイナリに対してリプレイすることで正常入力系を生成したり脆弱性を探すツールを作り始める。

本番ではこれがすごい大活躍する。

7/24

bataさんがCGCとAFLとかのfuzzerを連携させようとし始める。

Google Hangoutでミーティングする。

確かインフラ構成とかと、議題に関して色々話した。

後PoVをCで書かないといけない可能性が高いので、exploitation用の便利関数のライブラリを作りたいねという話になりbackerさんが作り始める。

すごい便利だった。

7/25

ここで初めてCGC環境立てた。それまで一切実際に触れてなかった。

インフラ構成の図が欲しいという話をするんだけど皆反応が薄かったので自分でやり始める。

運営から情報があり、今まで謎だったパケットの取得方法が判明。なんか決められた運営サーバのポートからUDPで流されるらしい。受け取れなかったらどうしてくれるんだ。

bataさんがバックドア実装してくれる。これが結構すごい。「弱小チームのバイナリをパクってくれるのか?」という疑問があったが、競技終了後に他のチームと話してたら、「君らのチームバックドアあっただろ?最悪だわ」みたいなことを言ってるチームがあったし、まあ効果あったんじゃないかなあ。実際うちのmitigation当てたバイナリが結構脆弱性防いでいたからか、Shellphishが丸パクリしているのは目撃した。

shinhさんがCGCのコマンドのラッパを書いてくれる。上のリンクから見れる動画みたら分かるけどもコマンドが本当に💩なので結構助かる。

~7/27

各自色々作業する。僕は比較的仕事がないニートだったので、shinhさんのmitigation bypass出来ないかなーと思って色々試したら結構すぐ出来てしまった。他のチームも似たようなことしてきたらこれ使おうと思った。

7/28

インフラ構成の図をtyageと作る。

ネットワーク構成の図もあったほうが良くね?という話になり、多分ucqさんあたりが作った。

多分これぐらいは公開しても怒られないかなーと思うので貼るけどこんな感じ

f:id:potetisensei:20160821161359p:plain

~7/30

各々作業。悲しいことに僕はすごい暇だったんで、ROP支援ツールでも書くかという感じになって書き始める。凝り性なので本番まで全然完成しないんだけど、本番で実際使う機会あったしすごい便利だった。どういうものかというとこんな感じ。

poteti@ubuntu:~/CGC$ ./rop-investigator/investigate LEGIT_00001
// pop gadget for esp
// gadset 0xa0fcdb0: ----
// 80485dd:
// 80485dd: pop    esp;
// 80485de: and    al, 0x08;
// 80485e0: int    0x80;
// 80485e2: pop    ebx;
// 80485e3: ret    ;
//

// pop gadget for eax, ebx, ecx, edx:
// gadset 0xa3b1860: ----
// 8048600:
// 8048600: pop    edx;
// 8048601: pop    ecx;
// 8048602: pop    ebx;
// 8048603: ret    ;
//
// 80486fd:
// 80486fd: pop    eax;
// 80486fe: ret    ;
//

// pop gadget for eax, ebx, ecx, edx, esi:
// gadset 0xa560db0: ----
// 80485ff:
// 80485ff: pop    esi;
// 8048600: pop    edx;
// 8048601: pop    ecx;
// 8048602: pop    ebx;
// 8048603: ret    ;
//
// 80486fd:
// 80486fd: pop    eax;
// 80486fe: ret    ;
//

// syscall gadget:
// gadset 0xa6dacb0: ----
// 80485e0:
// 80485e0: int    0x80;
// 80485e2: pop    ebx;
// 80485e3: ret    ;
//


// ROP start
buf_t *buf = buf_new();

// transmit(1, flag_addr, read_size, NULL)
buf_p32(buf, 0x80485ff); // pop esi edx ecx ebx
buf_p32(buf, NULL);
buf_p32(buf, read_size);
buf_p32(buf, flag_addr);
buf_p32(buf, 1);
buf_p32(buf, 0x80486fd); // pop eax
buf_p32(buf, 2);
buf_p32(buf, 0x80485e0); // int 0x80;

// exit(0)
buf_p32(buf, 0x8048600); // pop edx ecx ebx
buf_p32(buf, NULL);
buf_p32(buf, NULL);
buf_p32(buf, 0);
buf_p32(buf, 0x80486fd); // pop eax
buf_p32(buf, 1);
buf_p32(buf, 0x80485e0); // int 0x80;

// Extra Stager for bypassing IDS
// If you wanna use this function, please XOR buf->buf by 0xff
buf_t *buf2 = buf_new();

// receive(0, dummy_stack, 0x100, NULL)
buf_p32(buf2, 0x80485ff); // pop esi edx ecx ebx
buf_p32(buf2, NULL);
buf_p32(buf2, 0x100);
buf_p32(buf2, dummy_stack);
buf_p32(buf2, 0);
buf_p32(buf2, 0x80486fd); // pop eax
buf_p32(buf2, 3);
buf_p32(buf2, 0x80485e0); // int 0x80;

// xor(dummy_stack, dummy_stack+0x100, 0xff)
buf_p32(buf2, 0x80486ff);
buf_p32(buf2, pop3_gadget);
buf_p32(buf2, dummy_stack);
buf_p32(buf2, dummy_stack+0x100);
buf_p32(buf2, 0xff);

// stack pivot -> dummy_stack
buf_p32(buf2, 0x80485dd); // pop esp ebx
buf_p32(buf2, dummy_stack);

この時点でもrp++は超えてるし、shinhさんのmitigationに対しても一定の効果はあった。ちなみに今はもっと賢い。後公開する気はさらさらない。

7/31

mageさんがサラッとslack botを作ってくれる。これによって、脆弱性を発見した時、slackへの通知とdocsの準備が一度に出来るようになった。

8/1

2度目のボイスチャットでのミーティング。結構順調そうに見えるかもしれないけど、この時点でダッシュボードは完成してないし、実はPoVとかバイナリの管理に関してはノータッチでマジでやばかった。というか本番もここら辺はなあなあになった。そこら辺について話す。

~8/3

各自作業する。僕は飛行機に乗る。2度目のズートピアを見た。爆睡した。

到着する。皆だんだんと合流し始める。

運営がホテルの部屋を取ってくれてるのは8/4からなんだけど、完全に知らなくてホテルの部屋がなかったので、ucqさんの部屋の床に靴掛け用のタオルを敷いて寝る。

8/4

起きる。皆合流する。Splatoonをした。ROP支援ツール実装やってたけど実装したいところまで実装できなかった。Splatoonのせい。皆27時ぐらいまで起きていて良くなかった。

 

初日

11時準備開始のはずなんだけども、なんかホテルの廊下にDEFCON参加者のクソバカ行列が出来てて人間渋滞みたいなのが発生したせいで遅刻する。当初の予定では11:30から本番開始なんだけど、実際始まったの15時7分とかだった気がする。

それまでは何してるかというと、練習問題みたいなのが1問だけ公開されてて、api叩けるかとかの確認が出来る。

ちなみにその練習問題フェーズの間、legitbsは💩なので、事前に公開されていた仕様書には「Digest認証で」と書いてあった所がBasic認証になっていたりして、ダッシュボード書きなおす必要があったり、その認証のid/passが配布されてないやんけと運営に文句言いに言ったら「idはbinja(space)(space)(space)だ。自チームの名前の後に空白文字3つ入れて」と言われて入れてみても認証が通らず、再度言いにいったら別の運営の人が「no space!」って言ってて本当になんなんだという気持ちになった(それで通った)。

本番が始まる。1問目が公開される。なんかtrumpを揶揄する内容だった気がする。

始まって8分ぐらいで自明なbof脆弱性見つけた。20分ぐらいしてtype1 PoV書く。流石にこれは時間かかりすぎ。反省すべき。

それと大体同じタイミングで2問目が出る。なんかWiiで使われてるファイルシステムのパーサー兼データ操作系のプログラムだったかな。全体的に整数値オーバーフローがあるんだけど明らかにExploit書くのは辛くて、放置した。

その後3時間ぐらいは1問目にまだ脆弱性があるという説が浮上して確認したり、2問目解析したり、クラッシュしたパケットログを元に解析とかてんやわんやしてて進捗がなかった。

3問目が公開される。3問目は結構ノータッチだった気がする。

パッチを当てた後も、1問目に対して結構クラッシュするパケットが見つかるのでそれをずっと見てたんだけど、これshinhさんのパッチが不完全じゃねという感じになって調べてたらやっぱりそうだったらしい。でもexploitableには見えないのでまあ良いかと思ったけど、結構時間ロスしたなあ。ちゃんとpatchどんな感じか確認しておくべきだった。

1日目終了。僕個人で言うとこの日はかなりパフォーマンス悪かったけど、チームとしてはリプレイとかを上手く使ってまあまあの立ち回りをしていた気がする。順位は12位とかで最悪な感じの出だしになった。

 

2日目

10時開始。

初めの1時間ぐらいは進捗なかった。

11時ぐらいに4問目が公開される。30分ぐらいして人間fuzzerのmageが4問目で結構criticalっぽいクラッシュを発見していたので、それの解析に入る。30分ぐらいで解析してみて、exploitableという結論は得たんだけど、めちゃくちゃ難しい。2時間ぐらいしてPoV書ける。焦っていたのでtype2 PoVで投げようか迷ったが、type1 PoVで投げる。10チーム/14チームに通る。30分ぐらい掛けてtype2 PoVに書き直す。今思うと意外なことに他のチームはパケットリプレイを上手く使えてなかった様に思えるので、必要なかったかもしれない。4問目、DragonSectorがうちのバイナリをパクっていることに気づく。shinhさんのmitigationは確かに強力だけれども、中身を知ってる僕からするとbypassするのは容易い。30分ぐらいで"DragonSector用"(実はbinjaにも通ってしまう)PoVを書いた。これで11チームに通る。

14時の段階で5問目が追加される。17時ぐらいに前述のPoVに関しては暇になったため、5問目にもfuzzer mageが見つけた脆弱性があったので、解析に入る。30分ぐらい解析して、これきつくね?という感じがしてくる。heap上でbuffer overflowが起きるんだけど、overflowに使える文字種がalnum + アンダーバーで、bofした部分はアドレスとしてfreeの引数として渡される。こんなん無理やんけと思ったものの、おそらくどのチームもunexploitableと判断して修正して来ないだろうから、PoV書けたら結構価値はありそうだと思った。実際うちのチームも含めて、15チーム全チームがこの脆弱性を修正していなかった。1時間ぐらい経つ。2日目終了間際だったものの、結構PoVを書き進められていたが、Heapのアドレスを決め打ちしないとexploitが成功しないという壁にぶち当たって、悩んでいる間に2日目が終了した。CGC環境は"ASLRが存在していないはず"なので、Heapのアドレス決め打ちは本来は問題ないのだが、当然ちょっと考えればmitigationでASLRを追加する事ができる。どのようにやるかというと、プログラムの開始時にallocate(mmapに当たるsyscall)をランダムなサイズで呼んでやればよい。後はheap allocatorが使う領域が勝手にズレてくれて、Heap ASLRが実現される。まあ徹夜したらなんとかなるやろと思い、晩飯を皆で食べた後、部屋に戻ってもこうの動画を見ながら作業する。ちなみに終了時点でうちのチームは8位だった。昨日の12位に比べればマシ。

 

3日目

案の定徹夜した。朝5時ぐらいになっても、Heap ASLRをどうにかする方法は思いつかず、結局Heap ASLRに対応しないままExploitが完成した。僕はHeap ASLRをかなり懸念していたし、PPPとCGCに至っては、Heap ASLRに加えて、アドレスを完全に再配置してASCII ARMORを実装していた。本当にかんべんしてくれ。もこうの動画とか見てる場合じゃなかった。

10時競技開始。

完成したPoVを投げてみると8/14だった。まあまあ、考えていたよりは通っている。

30分ぐらいして、7問目が追加される。6問目が2日目の終盤に追加されていたが、IPCだったので僕は完全にノータッチにすることにしていた。Heap ASLRをbypassする方法が全く思いつかないので7問目を見ることにする。意味分からん難読化がされてる、というかこれはCでは書かれてなさそうだった。すぐに193sがやばそうなクラッシュを見つけたので、それを解析する。なんか自明にtype2 PoVが書けそうだったので、パッチ班に、「mitigationでtransmitの引数をチェックして」という風に言っておく。1時間ぐらいしてPoV書ける。type2 PoVの癖に最後にバイナリをクラッシュさせてしまうので、他のチームにはバレやすいかと思ったが、個人的には自明だと思っていたので、どちらかというとすぐにでも投げたかった。13/14に通る。同じ問題でtyageが別のクラッシュを見つけていたので、それも解析する。未初期化な値を処理に使っているっぽくて、これもtype2 PoVが書けそうな感じがした。12時ぐらいになって、徹夜で書いたPoVが全チームに通らなくなった。つらい。でもこれはどのチームもpatchはしていなくて、Heap ASLRの実装されたバイナリを各チームがパクリあいをした結果なので、僕にもっと実力があってどうにかHeap ASLRをbypass出来ていれば、全チームにずっと通っていたのかもしれない。これはどうにもならないけど惜しいことをしたと思う。30分ぐらいでtyageのクラッシュでPoVを書く。残り2時間は7問目を解析しつつ、任意のアドレスに0xffを1byteだけ書きこめる脆弱性でexploit出来ないか考えてたんだけど、結局何も進捗がないまま終わった。ちなみに8問目が12時ぐらいに公開されたらしいけどノータッチ。終了2時間前に公開はマジで意味が分からん。

 

まとめ

1日目のパフォーマンスがあまりにも悪かったこと以外は、まあまあの立ち回りが出来たのではないかなと思う。ミーティングで決めていた役割分担では僕はパケットからの脆弱性解析ということになっていて、実際にその役目はかなり果たしていたと思うし、自明なしょぼい脆弱性をパケットリプレイやmitigationで対処しているということは、人間がPoVに関してやるべきなのは、exploitableかunexploitableか微妙なラインのめちゃくちゃ難しい脆弱性を対処して、大量得点を狙うことな気がしていて、実際にそうしていたから、まあ間違ってはいないと思う。惜しむらくはやはりHeap ASLRかなあという感じで、あれは今でもunexploitableに思えるんだけど、発見された状態や各チームのパッチ状況的には、それさえbypass出来てれば、2,3個順位は変わっていたのではないかという気がしてならない。とりあえず、全体的な実力がまだまだなのが成績に反映されてるのは明らかで、そこをある程度ツールでカバー出来たのが良かったのではないか。まあツールもかなりケツファイアな状態で作ったからあれでは不完全だけど、来年も出られるとすれば、もっとマシになってくれると信じたい。

 

おまけ

Heap ASLRがbypass出来なかったpov。

LEGIT_00006 pov

livectf スケジュール3

わこつw

 

第3回livectf、明日やるぞい

URL: https://www.youtube.com/watch?v=8-yULuEkkEk

 

内容はDEFCON近いのでCGCとかそこら辺どす。と言いつつ何やるか全く決めてないのやばいかも。

 

以下第2回の反省

放送自体に関して

・話すことがなさすぎる

・というよりはどういうことを話すと便利情報なのか分かってない感じがあった

 

問題に関して

・悪くなかったが、やはり遅い

livectf スケジュール2

わこつw

 

第2回livectfを明日やることにしたのでここに報告したいと思います。

URL: https://www.youtube.com/watch?v=SJvvMVfNfsw

9447 CTFのclassyという問題をやります。

5時間で解ききれるかは知りませんが、予定は明日の22時から27時までです。

 

以下第1回の反省

放送自体に関して

・マイクの拾う音がやはり小さいらしい

・やっぱり普段黙ってやってる作業を喋りながらやるというのは大変。sy**u_gameみたいになってしまう。

 

問題に関して

・あまりにも鈍りが深刻。

・変なところで手間取り過ぎた気がする。頭も流石に回らなさすぎ。放送の弊害か。

・経験でカバーこそしたが、入出力が上手く来なかったりなど、参考となる放送としてはよろしくなかった。