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