fr33f0r4ll

自分用雑記

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が書き変わっているのを確認できた。 これで問題なく練習できるはず。

Boostnoteからメモを取り出す

その名の通りBoostnoteからメモを取り出す。 ホームディレクトリにあるBoostnote内にデータがあるが、ディレクトリやファイル名はハッシュか何かで変更されているため、そのままじゃ識別できない。 しかし、jsonとcsonで管理しているようなので、pythonで取り出してくるスクリプトを書いた。 githubにあげておく。

cl-mstdn

cl-mstdn

common-lisp製mastodonAPI用ライブラリ。 どんどん変更されるはず。

(defparameter client-token (request-client-token "インスタンス名")) ;; クライアントごとに一回でオーケー
(defparameter access-token (register-client "インスタンス名" client-token "たぶん登録したメアド" "パスワード")) ;; このトークンは以後インスタンスサーバへのアクセスで使う、保存しておく(保存するメソッドはない)

随時加筆

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

制御構文

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と付けると、補足した変数を変更できるようになる。ただし、参照元は変更されない。 の中では、変数ごとに参照かコピーかを決定できる。参照の場合は&を変数名のあとに付ける。