Hackconに参加した
HackCon writeup
72位、一人じゃキツイなぁ。 Web問全然分からなかった。
rev
Key-gen1
match_meという実行ファイルが渡されるので、リバーシング。 見てみると最後の方にstrncmpしてるところがあるので、gdbで実行しつつ入力によって引数がどのように変化するか観察した。 しばらくやってると、2文字の入力が1文字にマッピングされているようであると分かった。 その上、位置に関係なく同じ2文字は同じ1文字に変換されるようなので、必要な文字に変換される組合せを見つければいい。 しかも、f以上の文字は使えないことが分かった(実際は16進数として解釈されているらしいことに後で気付いた)。 対応する文字に変換される組合せを探して、サーバに入力するとフラグが返された。
Key-gen2
match_meに受理される10個のキーを探す問題。 16進数に解釈されることに気付いたので、6組の異なる変換テーブルを作り、適当に組合せた。 これで通った。
crypto
RSA-2
今度はn, e, cだけが渡される。 eの値が異常に大きく、wiener-attackが出来そう。 求めたdで正常に復号化できた。
(ql:quickload :hackrsa) ;; hackrsa is in my tool set, https://github.com/4hiziri/tktools (setf d (hackrsa:wiener-attack n e)) (setf m (mod-expt c d n)) (hackrsa:decode m)
Bacche
Rotate it
crypto
“q4ex{ju0_tvir$_pn3fne_va_PGS???}p0qr"が渡されるので、ぱっと見でrot13だと分かる。
flag形式が"d4rk{xxx}c0de"なので、鍵は13で良さそう。
単純にnkfでrot13してフラグゲット。
# echo "q4ex{ju0_tvir$_pn3fne_va_PGS???}p0qr" | nkf -r
ALL CAPS
crypto
“OF EKBHMGUKZHJB, Z LWALMOMWMOGF EOHJTK OL Z DTMJGX GY TFEGXOFU AB NJOEJ WFOML GY HSZOFMTVM ZKT KTHSZETX NOMJ EOHJTKMTVM, ZEEGKXOFU MG Z YOVTX LBLMTD; MJT "WFOML” DZB AT LOFUST STMMTKL (MJT DGLM EGDDGF), HZOKL GY STMMTKL, MKOHSTML GY STMMTKL, DOVMWKTL GY MJT ZAGRT, ZFX LG YGKMJ. MJT KTETORTK XTEOHJTKL MJT MTVM AB HTKYGKDOFU MJT OFRTKLT LWALMOMWMOGF. MJZFQL YGK KTZXOFU MJZM, JTKT'L BGWK YSZU: X4KQ{MKB_YZEEJ3_OYMJOL_MGG_LODHTS}E0XT"
渡された文章、フラグ形式っぽいものもあり、一文字だけのZが複数回出てたり、記号はそのままだったりすることから単一換字式暗号かと思った。 とりあえず、フラグ形式のところは確定できるので確定させる。
“OF crBHMGUrZHJB, Z LWALMOMWMOGF cOHJer OL Z DeMJGd GY eFcGdOFU AB NJOcJ WFOML GY HSZOFMeVM Zre reHSZced NOMJ cOHJerMeVM, ZccGrdOFU MG Z YOVed LBLMeD; MJe "WFOML” DZB Ae LOFUSe SeMMerL (MJe DGLM cGDDGF), HZOrL GY SeMMerL, MrOHSeML GY SeMMerL, DOVMWreL GY MJe ZAGRe, ZFd LG YGrMJ. MJe receORer decOHJerL MJe MeVM AB HerYGrDOFU MJe OFRerLe LWALMOMWMOGF. MJZFkL YGr reZdOFU MJZM, Jere\‘L BGWr YSZU: d4rk{MrB_YZccJ3_OYMJOL_MGG_LODHeS}c0de"
そうするとZreだったりJereだったり英単語が類推できる箇所だったりが出てくるので、それを埋めていく作業を繰り返す。 文字の出現頻度でやってもいいが、あまり参考にならなかった。 pythonでdict使って変換テーブルを作って、逐一復号しながら解いた。 復号すると、暗号学における単一換字式暗号の説明みたいな文章とフラグがでてくる。
high bass
crypto
“VGhpcyB3YXMgaW4gYmFzZS02NDogZDRya3t0aGF0XyRpbXBsXzNuMHVnaDRfVX1jMGRl"が渡される。 よく分からなかったのでBase64したら解けた。 印字可能で空白がないなら取り敢えず試す。
flag.txt
web
URLが渡されるので行ってみると、リンクが一つだけあった。 踏んでみるとロボットが人間をどうたらみたいな外部のサイトに飛ばされた。 外部サイトにフラグはもちろん無いので戻る。 リンク先にロボットの話題が出てたので、robots.txtを開いてみる。 ハッシュ値みたいな名前のディレクトリがDisallowになっていた。 問題のタイトルがflag.txtだったので、そのディレクトリ直下のflag.txtにアクセスしたらflagがでてきた。
one
なんだろう、rev?
バイナリファイルを渡されたのでfileで形式を調査する。 elf形式の実行ファイルなので、とりあえず実行する(アブナイ)。 フラグが出力された。
cave
crypto
象形文字みたいなものの画像が渡される。 名前が思い出せなかったが、これはヒエログリフである。 ヒエログリフタイピング的なサイトが見つかり、おそらくそのサイトで生成したであろう問題であることが分かった。 とりあえず復号するとflag_isなんたらかんたらみたいになるので、flag_isのあとをフラグの形式にして提出すると通った。
needle
foren
zip形式のファイルを渡され、解凍するとクソ長いテキストファイルが出てくる。 フラグ形式でgrepしたら該当する部分があったので、提出して終了。
RAS-1
crypto
RSAに関する暗号問題。
p, q, c, eが渡されるので、普通に復号するだけでいい。
ed = 1 mod (p - 1)(q - 1)となるdを求めて、cd mod pqを計算すると平文mが得られる。
ed = 1 mod (p - 1)(q - 1)は拡張ユークリッドの互除法で求められる。
ed + k(p-1)(q-1) = 1となるk, dを求めればいい。この式のmodを取れば元の式になることが分かる。
(setf p 152571978722786084351886931023496370376798999987339944199021200531651275691099103449347349897964635706112525455731825020638894818859922778593149300143162720366876072994268633705232631614015865065113917174134807989294330527442191261958994565247945255072784239755770729665527959042883079517088277506164871850439) (setf q 147521976719041268733467288485176351894757998182920217874425681969830447338980333917821370916051260709883910633752027981630326988193070984505456700948150616796672915601007075205372397177359025857236701866904448906965019938049507857761886750656621746762474747080300831166523844026738913325930146507823506104359) (setf c 8511718779884002348933302329129034304748857434273552143349006561412761982574325566387289878631104742338583716487885551483795770878333568637517519439482152832740954108568568151340772337201643636336669393323584931481091714361927928549187423697803637825181374486997812604036706926194198296656150267412049091252088273904913718189248082391969963422192111264078757219427099935562601838047817410081362261577538573299114227343694888309834727224639741066786960337752762092049561527128427933146887521537659100047835461395832978920369260824158334744269055059394177455075510916989043073375102773439498560915413630238758363023648) (setf e 65537) (defun extend-gcd (a b) "return (x . y) | ax + by = 1" (flet ((next-val (x1 x2 q) (- x1 (* x2 q)))) (loop for q = (/ (- a (mod a b)) b) then (/ (- z1 (mod z1 z2)) z2) for ztmp = (next-val a b q) then (next-val z1 z2 q) for z1 = a then z2 for z2 = b then ztmp for xtmp = (next-val 1 0 q) then (next-val x1 x2 q) for x1 = 1 then x2 for x2 = 0 then xtmp for ytmp = (next-val 0 1 q) then (next-val y1 y2 q) for y1 = 0 then y2 for y2 = 1 then ytmp when (= z2 1) do (if (< x2 0) (return (cons (+ x2 b) (- y2 a))) (return (cons x2 y2)))))) (defun mod-expt (base exp modulus) "more effective expotential and modulus. calculate mod at every step of exp." (if (< exp 0) ;; if exp < 0, cannot calc mod so simply return base^exp (expt base exp) (loop for acc = 1 then (if (evenp e) acc (mod (* acc b) modulus)) for b = (mod base modulus) then (if (evenp e) (mod (expt b 2) modulus) b) for e = exp then (if (evenp e) (/ e 2) (1- e)) when (= e 0) do (return acc)))) (defun decode (encoded-num) (labels ((inner-loop (num acc) (if (> num 0) (inner-loop (truncate num (expt 2 8)) (cons (mod num (expt 2 8)) acc)) acc))) (inner-loop encoded-num nil)))
stego
standard stego
画像にデータを隠すpythonプログラムと画像ファイルが落ちてくるので、逆算する。 どうやら文字列をバイナリ表現に変換して、画像ファイルの先頭からRGBのいずれかの末尾に1bitずつセットしているようである。 なので、画像の先頭から対応するRGBの値の末尾のbitを取り出すプログラムを書いて、マーカービット列の間を取り出した。 あとは1byte単位で取り出して文字に戻すだけである。
from PIL import Image def getLSB(target): binary = str(bin(target)) return binary[-1] def binToAscii(bin): ret = '' length = len(bin) // 8 for i in range(length): binary = bin[i*8:i*8 + 8] ret += chr(int(binary, 2)) img = Image.open('Secret.png') pixels = list(img.getdata()) mode = img.mode ret = [] for i in range(10000): newPixel = list(pixels[i]) ret.append(getLSB(newPixel[i % len(mode)])) header, trailer = 2 * "11001100", 2 * "0101010100000000" bin_str = "" for b in ret: bin_str += b tmp = bin_str[:bin_str.find(trailer)] tmp = tmp.replace(header, '')
まとめ
全然分からんかった。
Lua
tags: programming-language
lua
Lua
コメント
-- comment --[[ multiple line comment ok. ]]
変数
-- num x = 10 x = 1.0 x = 10e-1 -- string s = "AAA" s = 'AAA\n' -- function function square(x) return x * x end -- table t = {} -- boolean b = true b = false
テーブル
t = {} t["str"] = "string" t["boolean"] = false t["number"] = 1 -- number is accepted as index -- index should start from 1, lua expects that beginning of index is 1. t[1] = "one" t[2] = true t[3] = 3
イテレータ
t = {} t["1"] = "one" t["2"] = "two" t["3"] = "three" for i, val in pairs(t) do -- i <- index, val <- value print(i, val) end t[1] = "one" t[2] = "two" t[3] = "three" for i, val in ipairs(t) do -- if index is num, can use ipairs print(i, val) -- output "one", "two", "three" end
演算子
-- math 1 + 3 1 - 3 1 * 2 1 / 13 1 ^ 0 1 % 2 -- cmp 1 < 2 1 > 4 1 <= 2 1 >= 1 1 == 1 1 ~= 1 -- not equal -- logical true and true true or false not false -- string "str1" .. "str2" -- "str1str2"
if
a = 3 b = 2 if a > b then print("a") end if a > b then print("a") elseif a == b then print("ab") else print("b") end
for
for i = 0, 4, 1 do print(i) -- 0, 1, 2, 3, 4 end a = 0 b = 4 while a <= b do print(a) -- 0, 1, 2, 3, 4 a = a + 1 end a = 0 b = 5 repeat print(a) -- 0, 1, 2, 3, 4 a = a + 1 until a == b while true do break -- break should be before end end while true do do break end a = 10 end
関数
function funcname (arg) return arg + 1 end function returnmultival(arg1, arg2) return arg1, arg2 end a, b = returnmultival(1, 2) -- extendable arg function func(...) t = {...} end -- lambda f = function(args) return args end f(3)
コルーチン(co-routine)
function col(a, b, c) sum = 0 sum = sum + a coroutine.yield(sum) sum = sum + b coroutine.yield(sum) sum = sum + c return sum end local co = ccoroutine.wrap(col) ret = co(3, 2, 1) -- return a ? ret = co(3, 2, 1) -- return a + b ret = co(3, 2, 1) -- return a + b + c
バッファオーバーフローでeipが書き換えられない
自分の環境で脆弱性のあるプログラムを再現できなかった
pwnの練習をしようとして脆弱性のあるプログラムを書いたら、何故かその脆弱性を(幸か不幸か)exploitできなかった。 解決するまでに結構かかってしまった上、日本語の情報が見つからなかったから残しておく。 参考というか、丸パクリ元はここ。
環境
自分の環境は以下の通りである。
x86_64 GNU/Linux Ubuntu LTS-16.04
やろうとしたこと
やろうとしていたことはバッファオーバーフローによるeipを書き換えだったけど、実際にやるとクラッシュしてもeipが書き換えられてなかった。 プログラム自体はfgetsしてバッファ書き換えるだけの簡単もので、32bitアーキテクチャでSSPは無効にしていた。 ASLRは無効にしてなかったけど、gdbで実行していたので無効の状態のはず。
#include <stdio.h> int main(int argc, char** argv){ char buffer[32]; fgets(buffer, 128, stdin); return 0; }
コンパイルは、gcc -m32 -fno-stack-protector -o overflow overflow.c
gdbで調べたり、逆アセンブルして解析した結果、どうもなんらかのセキュリティが働いているような感じだった。 逆アセンブルしてみてみると、main関数の最初とret周りの処理が怪しい。
0804843b <main>: 804843b: 8d 4c 24 04 lea ecx,[esp+0x4] ;; 1 804843f: 83 e4 f0 and esp,0xfffffff0 8048442: ff 71 fc push DWORD PTR [ecx-0x4] 8048445: 55 push ebp 8048446: 89 e5 mov ebp,esp 8048448: 51 push ecx 8048449: 83 ec 24 sub esp,0x24 804844c: a1 20 a0 04 08 mov eax,ds:0x804a020 8048451: 83 ec 04 sub esp,0x4 8048454: 50 push eax 8048455: 68 80 00 00 00 push 0x80 804845a: 8d 45 d8 lea eax,[ebp-0x28] 804845d: 50 push eax 804845e: e8 ad fe ff ff call 8048310 <fgets@plt> 8048463: 83 c4 10 add esp,0x10 8048466: b8 00 00 00 00 mov eax,0x0 804846b: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4] ;; 2 804846e: c9 leave 804846f: 8d 61 fc lea esp,[ecx-0x4] ;; 3 8048472: c3 ret 8048473: 66 90 xchg ax,ax 8048475: 66 90 xchg ax,ax 8048477: 66 90 xchg ax,ax 8048479: 66 90 xchg ax,ax 804847b: 66 90 xchg ax,ax 804847d: 66 90 xchg ax,ax 804847f: 90 nop
- 1で[esp+0x4]をecxに格納
- 2でecxが復帰
- 3のret前でespに[ecx-0x4]を格納
こんな感じの処理がされているらしい。 つまり、一番最初に格納したスタックポインタの値をleaveとretの間で復帰させることで、書き換えられたリターンアドレスがeipに格納されることを防いでいるのではないかと思われる。
また、callした後のスタックの状態は以下のようになるので、ebpを変更せずにリターンアドレスを変更することはできない(ebpの整合性を保つような値で上書きすることは可能かな?)。 したがって、リターンアドレスがバッファオーバーフローで書き換えられていると、ebpが異常値になって正しくespを復帰させられなくなる。 その結果としてeipが書き換えられなくなっている。
stack |
---|
argn |
... |
arg1 |
return-address |
saved-ebp |
local-variable |
解決策(?)
解決というか問題の再発というか、脆弱性を仕込むことができなければ練習できないので調べた。 幸いにも参考にしたページのコメントに解決できそうなやり方が載っていたので、ありがたく参考にさせてもらう。 このespの保存と復帰はmain関数でしか行なわれないらしいので、別の関数に脆弱性のある処理を書いてmainから呼べばいいらしい。
早速試す。 プログラムを書き換えて、以下のような感じにした。 main関数の中身を丸ごと取り出しただけで、処理は同じになる。
#include <stdio.h> void vulnerable_func(){ char buffer[32]; fgets(buffer, 128, stdin); return; } int main(int argc, char** argv){ vulnerable_func(); return 0; }
gdbで実行し、50文字の文字列を入力するとeipが書き変わっているのを確認できた。 これで問題なく練習できるはず。
C++ 記法
C++
C言語は分かっているものとして書いているので、変数の宣言なんかは説明しない。 自分向け。 参考文献は、猫でもわかる C++プログラミング
ライブラリのインクルード
#include <iostream>
Cのライブラリは、xxx.hならcxxxとしてインクルードできる。
ネームスペース
using namespace std;
型
- Integer
- short
- int
- long
- Floating Point
- float
- double
- long double
- Char
- char
- Boolean
- bool
- true
- false
- bool
制御構文
for
for(int i = 0; i < 10: i++){ cout << i << endl; }
初期化部で変数宣言が可能に
関数
プロトタイプ宣言
int add(int);
引数の指定は型名だけで十分
デフォルト値
int func(int a = 100, int b = 100){ ... }
引数が渡されなかった場合、指定した値が使われる。 関数プロトタイプ宣言のときは、プロトタイプ宣言でデフォルト値を指定する。
int func(int = 10, int = 3); ... int func(int a, int b){ ... }
オーバーロード
引数の個数や型が異なれば同じ名前の関数を定義できる。 ただし、戻り値だけが異なる場合は同じものと見做される。 デフォルト引数を取る同名の関数との間で分かりづらいコンパイルエラーや動作を引き起しうる。 同時に使用しないようにするべき。
const
引数にconstを付けると
記憶クラス指定子
- static
- プログラム終了まで同一の参照を保持する
- extern
- 別ファイルで宣言された変数を参照したいときに
- auto
- 自動変数はスタック領域に保存される、ブロック内で有効
- たんなるローカル変数
- register
ポインタ
参照
別名を付けられる
int a; int &alias = a; /* int a; int &alias; alias = a; error */
変更は元の引数にも反映される 関数を呼び出した先でも同じことができる
関数の呼び出し先で変数の値を変更して欲しくないときは、constを付ける
メモリ
動的確保
int *a; a = new int[4]; // malloc delete [] a; // free
クラス
宣言
class Test { private: char* private_name = "private"; char* privateFunc() { return private_name; } public: char* public_name = "public"; void printPrivate(){ std::cout << private_name << endl; return; } void callPrivate(){ std::cout << privateFunc() << endl; return; } };
次の様に関数の中身を分離できる。
class Test { private: char* private_name = "private"; char* privateFunc(); public: char* public_name = "public"; void printPrivate(); void callPrivate(); }; char* Test::privateFunc(){ return private_name; } void Test::printPrivate(){ std::cout << private_name << endl; return; } void Test::callPrivate(){ std::cout << privateFunc() << endl; return; }
inlineを付ければ、クラス内に書いたのと同じ扱いになる。
変数宣言
Test t;
コンストラクタ
コンストラクタはオーバロードできる
引数なし
class Const { public: int a; Const(); }; Const::Const() { a = 100; } int main(){ Const c; std::cout << c.a << endl; return 0; }
引数あり
class Const { public: int a; Const(int); }; Const::Const(int i) { a = i; } int main(){ Const c(101); std::cout << c.a << endl; return 0; }
コピーコンストラクタ
代入の際には、tのコピーコンストラクタが呼ばれる
Test t; Test t_copy = t;
定義は以下
class Test { public: int* array = {0, 1, 2, 3, 4, 5}; Test(); Test(const Test &t); }; Test::Test(){ array[0] = 10; } Test::Test(const Test &t){ array[0] = t.array[0]; }
自分と同じ型の参照を受け取るコンストラクタとして実装される
デストラクタ
class Test { public: Test(); ~Test(); // destractor }
thisポインタ
メンバ関数内で、自分自身への参照を保持する
static
クラス関数、クラス変数を定義する
メンバポインタ
int x::*ptr; ptr = &x::a;
継承
class Parent { private: int p = 1; protected: int protect = 2; public: int pub = 3; }; class Child : public Parent { ... }
コンストラクタ
コンストラクタを指定する
Test() : Base(4) { ... }
仮想関数
virtualと付ける こうすると、ポインタが実際に指している型のメンバにアクセスできる
純仮想関数
virtual funcName = 0;
抽象クラスのような使い方
フレンド関数
friendと付けて指定する。
クラス内で定義することで、そのクラスの非公開メンバにアクセスすることのできる関数を指定する
friend int funcName(int arg);
フレンドクラス
クラスA内で定義する
friend class B;
B内ではAの非公開メンバにアクセスできる。 Bを継承したクラスではB経由でAにアクセスすることはできるが、Aに直接アクセスすることはできない。
多重継承
できる
菱形継承
できる
演算子オーバーロード
予約語operatorを使う
加算はこんな感じ
int operator + (int a, int b){return a + b}
代入演算子も複合代入演算子もオーバーロードできる。 メンバ関数として宣言することも、通常の関数としても宣言でき、中置記法で記述できる。
フレンド関数
通常の関数として宣言し、クラス内でフレンド関数としておけば、非公開メンバへのアクセスもしやすい。
単項演算子
クラス内では引数なしの関数のように宣言するとできる。
インクリメンタル
Vector:operator ++(); Vector:operator ++(int);
ファイルI/O
lib-fstream
#include <fstream> int main(){ ofstream of; of.open("filename", ios:app) // append if(!of) return -1; of << "test" << endl; of.close(); return 0; }
テンプレート
型抽象化
予約語templateを使う
template <typename T> return-type funcname(args){......}
テンプレートクラス
同様 typeidを使うと、現在の型が分かる
標準テンプレートライブラリ
例外処理
try{ if(...) throw val; } catch(type <val-name>) { error-handling }
ラムダ式
[]でラムダ式の宣言を始める。 続いて引数、返り値の型(省略可能)、関数本体を定義する。 呼び出すときは通常の関数のように引数を渡す。
int main(){ [](int a, int b) -> int {return a + b;}(1, 3); return 0; }
の中に&を付けると、スコープ内の変数を参照として補足する。 =を付けると、値をコピーする。=を付けたとき、引数の()の後にmutableと付けると、補足した変数を変更できるようになる。ただし、参照元は変更されない。 の中では、変数ごとに参照かコピーかを決定できる。参照の場合は&を変数名のあとに付ける。