ホーネットのブレーキピストン揉み出し

ホーネットのブレーキの引きずりを確認したので、ピストンの揉み出してごまかしたという話です。
(あまり良くない部分があるので参考にする場合は気をつけてください)

経緯

買った時からなんですが、押し歩きすると「スー…」とブレーキローターとパッドが触れ合う音が聞こえます。
ブレーキキャリパを取り外して押し歩きしてみましたが、大して重く感じません。なので直さなきゃならないほど引きずってるワケではないと思うのですが、どうにも気持ち悪いです。
ジャッキアップしてタイヤを空転させてみたところ、少し回って止まっちゃうので、影響はないわけではないようです。

本来、油圧に押されて少し飛び出したピストンは、変形したピストンシールが戻ろうとする力で、少し引っ込みます。(ピストンシールの働きを読んでください)
ゆえに、ピストンシールとピストンの摩擦を少なくする「揉み出し」はやるべきでないという意見があります。

私もそう思うのですが、キャリパーのOHは面倒なので揉み出しでごまかしときます。
 

まずリアから。

パッドは…外せなかった

パッドピンを最初に外すべきです。
パッドピンのキャップだけは外せましたが、、パッドピン自体が固着気味で外せそうにないため、以降はパッドを外さずに行います。

リアキャリパーを外します


リアキャリパーのアップ。
ピストンは期待に反し、ちゃんと少しだけ戻るようになってました。つまり、普通に機能しているみたい。
ブレーキパッドが変な減り方をしています。上が薄く、下が厚いという。なぜ?

リアキャリパーをバイク側に固定してるボルトを外したところのアップです。    
片押しキャリパー特有のブッシュ(正式名称不明)は片方錆びており、グリスはガチガチに固くなっております。
 
太い方の固定ボルトは摺動部に段付き摩耗があり、グリスがガチガチに固くなっております。
交換すべきですが換えの用意がないので続投。グリスを削り落とします。


ピストンのお手入れ

ピストンをギリギリまで出し、ブレーキクリーナーと歯ブラシで綺麗にします。
ピストンを回す、ピストンプライヤーがないので回せません。手で回すには重すぎます。
よって裏側は洗いにくいのですが、まぁそこは適当に…。
 
シリコングリスを塗るべきですが、買ってないのでリチウムグリスを薄〜く塗ります。
リチウムグリスはゴムにはあまりよろしくないらしいのですが…ブレーキフルードに耐えるピストンシールなら大丈夫だろうと期待します。
 

次はフロント。

パッドは外せず…。

パッドピンのキャップが固着しており、マイナスドライバーでは外せません。556をかけて放置したりしてもダメでした。
マイナスドライバーより回転方向と軸方向に力をかけやすい、ソケットレンチで回すべきかも。

買ってから一度も使ってないショックドライバーを試しても良かったけど面倒なのでやめました
 

キャリパーを外しました

普通にボルト緩めれば取れます。

意外とローターから外すのが大変でした。さすが、引きずってるだけはあってブレーキパッドがローターを挟み込んじゃってます。(引きずってなくても大変か?)

なんだか車体側に位置するピストンたち(写真下側)が大きく飛び出てますね。
ピストンが出てくるスピードにばらつきがあって、フォーク側の2つ(写真右)が動きが良いです。そしてフォークから遠い方の2つ(写真左)は、戻る量がほとんどなかったような気がします。

ピストンのお手入れ

片方の2つずつ出して綺麗にしていきます。ピストンツールの類はないのでマイナスドライバーと己の握力が頼りです。

ピストンの出る量の調整の仕方、戻し方(自己流…)

ピストンの掃除の時は、ピストンを押し出しておく必要があります。
こんな風にパッドとキャリパーの間にマイナスドライバーを刺してパッドの動ける範囲をコントロールしつつ片方の2つのピストンを出します。

戻す時も同じようにして、2枚のパッドを中央に持ってきます。そのあと、マイナスドライバーで2枚のパッドの間をこじって開きます。

2枚のパッドの間に隙間ができたら、マイナスドライバーで抉るなり握力に頼るなりして、ディスクローターが入るくらいの隙間を作ります。疲れました。
 
抉る時にドライバーの刃先が滑り、間違って自分の顔を刺すところでした。セーフでしたが右手の親指の爪が一部割れて剥がれました。血は出ないけど痛い。

マイナスドライバーでパッドの間を広げ、ピストンを押し戻し終わったら、
キャリパーを元の位置にセットして、何回かブレーキレバーを握って、ピストンの位置を普段通りに直します。

仕組み上、多少は引きずってても仕方ない?

ブレーキキャリパーを戻して押し歩きしてみると…残念、擦れてます
 
実際、ブレーキレバーを放すとピストンはちゃんと少しだけ戻ってるのですが、
以下で示す場所で、パッドとキャリパーが強く擦れているため、パッドは戻りません。(擦れてなくても、パッドを戻すバネとか無いので戻らないかも?)

 
強い力で押し付けられているわけではないので強い制動力は生じませんが、パッドが戻らないせいでローターと少し擦れてしまうというわけです。
ディスクブレーキはパッドが綺麗にローターと離れるというわけではないと理解できました。(←あってんのかなぁ?)

 
上記工程は2時間かかりました。
今回はややマシですが、爪の間に黒い汚れが残り、汚らしく見えてしまうのをなんとかしたいです。
今回は使い捨てビニール手袋を使って作業を始めたのですが、すぐ破れてしまうし、今手に油が付いているかどうかわかりにくいので油っ気がつくとまずいブレーキ装置は扱いにくいので、ゴム手袋にでもしようかと思います。
 
 
このあとエンジンやエキパイをシリコンオイルで拭いてたらいつの間にか6時になってました。

存在しないホスト名についての問い合わせをしているカミンスキー攻撃が有効なわけ

カミンスキー攻撃について、
「FQDNのホスト名部分を、標的ドメインに存在しないホスト名にして次々問い合わせるため、TTLに関係なくクエリIDの総当たりができ云々ーーー」
という話はよく読むけど、

なんで存在しないFQDNに対する偽のIPアドレスをDNSキャッシュサーバにキャッシュさせると嬉しいんだ?
と思ってました。

情報処理教科書 ネットワークスペシャリスト 2012年版のp192の図に載ってました。
(Google Booksの当該ページ)

権威DNSサーバからの偽の応答として
「1.example.com(存在しないFQDN)の名前解決のためには、www.example.com(存在するFQDN)というサーバに尋ねてください。IPアドレスはx.x.x.x (偽のもの)です。」

と応答すると、www.example.comとx.x.x.xが対応づけてキャッシュされてしまうらしいのです。

よくは知らんけど本にはそう書いてあります。

検証せず本という権威に盲目的に従う、よくない姿勢ですが目が疲れたので寝ます。

ちなみに、なぜ「標的ドメインに存在しないホスト名」と標的ドメインをくっつけたFQDNを問い合わせなくちゃならないかというと、
ここに詳しく乗ってますが、「委任先のサブドメインを管理するネームサーバーの名前が委任先のドメイン名ではない場合、グルーレコードは不要になる」からです。
(グルーレコードがないとIPアドレスを知るために問い合わせが必要になり、以下ループ…ということになる)

picoCTF2017 choose解説

pwnable.twに比べればpicoCTFは易しめらしいですがそれでさえ私は解答できません

hgarrereynのwriteup
Caesurusのwriteup

ソースコードの中には、
「//TODO: These are all the same size, so remove in future version for brevity
read」
とか

//Can use an orc ptr as all the structs are the same size.
        orc * tempEnemy = (orc *)(enemies + enemOffset);

とか、ドラゴンを除く全てのモンスターの構造体のサイズが同じとして考えているようなコメントがあります。
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #008400; background-color: #ffffff}

公式ヒントの指摘している、「間違った前提」とはまさにこのことで、構造体の中に変数を記述する順番が違うだけで、アラインメントのため、構造体のサイズはしばしば異なるものになります。
冒頭のhgarrereynのwriteupが詳しく説明しています。
アラインメントについてはこちら。ハードウェアを意識したプログラミングの基礎 データ型のアラインメントとは何か,なぜ必要なのか?

ユニコーンとケンタウロスのみが、他のモンスターの構造体と変数の宣言の順が違うため、サイズが違います。24バイトです。
しかし、11匹のモンスターたちを格納する配列enemiesは次のように宣言されており、20バイトのオークを基準としてサイズを計算し、確保しています。

#define NUMMONSTERS 11
#define ENEMSIZE sizeof(orc)

char enemies[ENEMSIZE * NUMMONSTERS];

そして、enemiesに実際にデータを格納してゆくcollectEnemData関数は次のように実装されているため、ユニコーンかケンタウロスを指定した場合、バッファオーバーフローします。

void collectEnemData(char * enemies, char * enemyChain){
    unsigned int offset = 0;
    for(int i = 0; i < NUMMONSTERS; i++){
        char index = enemyChain[i];
        switch(index){
            case 'O':
            case 'o':
                buildOrc((orc *)(enemies + offset));
                offset += sizeof(orc);
                break;
            case 'G':
            case 'g':
                buildGoblin((goblin *)(enemies + offset));
                offset += sizeof(goblin);
                break;
            //省略
        }

    }
}

各モンスターには12バイト、name配列が用意されており好きな名前をつけられます。これらを利用し、バッファオーバーフローを利用して、enemies配列が確保されているrunGame関数のスタックフレームでのold EBPやリターンアドレスを書き換えられます。

たぶん想定解

hgarrereynのwriteupで説明されている、想定解と思われる解法です。
シェルコードとして思いつくのはexecveに”/bin/sh”とかを渡してシェルの起動をするものです。
しかしこれはname配列の12バイト(末尾ヌルバイトを除き11バイト)に収まりません。
なので、複数のモンスターたちのname配列に、シェルコードを細切れにして格納し、ジャンプ命令で飛び飛びに実行して行きます。

ASLRがオフなのでスタックアドレスのリークは一度すればよく、これはprintEnemy関数の最後で次のように”魔法使いの視力”を持ってればアドレスを表示することができることを利用します。

void printEnemy(char * enemy){
    //...省略...
    if(ctfer.wizardSight){
        printf("Your sight shows the enemy at %pn", enemy);
    }
}

そして、勝利したときの処理が書いてあるprocessWinnings関数をみると、トロールに勝利したときにこれがもらえることがわかります。
よって、モンスター指定時の1匹めにトロールを指定、残りは適当に選び、一匹めのトロールに勝利することで、アドレスが表示されます。

(アドレスはローカルで試して調べても同じなのでは…?と思って試しましたが、ASLRをオフにした手持ちのubuntu x64環境では0xffffce94、picoCTFのサーバでは0xffffdba4でした。なぜ違いが出るんでしょうか。よくわかりませんでした。)

もう一つの解法

Caesurusのwriteupで説明されています。 結構難しいので、こっちじゃない解法を思いつく人の方が多いはず
自分的には結構難しいです。

readInput関数は、
//Reads input from user, and properly terminates the string
unsigned int readInput(char * buff, unsigned int len){
   //...省略...
}

のように、バッファのアドレスと長さを受け取り、入力を受け付ける関数です。

   0x08048eb7 <+28>:    pushl  0xc(%ebp)    ;readInputの第二引数、len
   0x08048eba <+31>:    pushl  0x8(%ebp)    ;readInputの第一引数、buff
   0x08048ebd <+34>:    call   0x8048e3c <readInput>

readWrapper関数内部で上記のアセンブラのようにreadInput関数を呼んでおり、ここに飛んでくれば、readInput関数が利用できます。そのとき入力先として指定するバッファはスタック上にあるため、渡すアドレスは、EBPレジスタをベースアドレスとしてアドレスが計算されるようです。

バッファオーバーフローで書き換えられるのはEBPレジスタでなくスタック上のold EBPということを覚えておいてください。

流れとしては、
inpuStr配列にシェルコードをいれ、実行することが目的です。

  1. 最初のモンスターたちの名前入力で、バッファオバーフローを使い、runGame関数のスタックフレームの、(スタックピボットのために)old EBPと(最後の異常なreadInput呼び出しの準備のために)リターンアドレスを書き換えます。
  2. そしてドラゴンに負ける前の最後の一回の、コマンド読み込み時readWrapperで通常どおりinputStr(0x080ed044)に書き込みを受付ますので、パディング兼コマンドと1stペイロード(のちの異常なreadInput呼び出し時の引数の準備)を渡します。
  3. ドラゴンに負けた後、リターンアドレスが書き換えられているので通常とは異なり、上記のアセンブリのとこに飛びます。readInputが呼ばれるため、そこで2ndペイロード(シェルコードへのリターンアドレスとシェルコード本体)を積みます。0x80ed040(inputStar近辺)から積むみたい。中途半端に見えますが、ここから積むとぴったりになるように計算されています。
  4. readWrapperからリターンするときにスタックピボットが起こりESPがinputStr配列のあたりを指すようになり、そこに用意しておいたリターンアドレスの指す先にリターンするのでシェルコードが実行されます。

上記の説明でなにがなにやら…な場合は素直にCaesurusさんのエクスプロイトコードを読むとわかると思います。

1.リターンアドレスとold EBPの書き換え

前述したように、一部のモンスターが、アラインメントのせいで想定より大きいサイズであることを利用し、runGame関数のスタックフレームにあるリターンアドレスとold EBPを書き換えます。
全ての戦いが終わりrunGame関数からリターンするときに、readInput関数を呼び出すために、リターンアドレスには上記のアセンブラの場所(0x08048eb7)、スタックピボットをしてESPがinputStr周辺を指すようにするために、old EBPには(0x80ed044)から-6した値を格納します。

2.最後の異常なreadInput関数呼び出し時に渡す引数の準備(1stペイロード)

runGame関数からのリターン時に呼び出されるreadInput関数に渡す引数の準備です。
1stペイロードをつむ段階では、ctfer構造体をダンプすればわかりますが体力はまだ15 (0xf)残っています。
1stペイロードの先頭でパディングついでにコマンドHを入力、次にreadInputの第一引数*bufとしてinputstr周辺(0x80ed040)を、そのあとにreadInputの第二引数lenとしてAAAを入力します。
コマンドはAでも良いと思います。また、lenはAAAでなく十分大きいものならなんでも良いです。
先頭のコマンドを読むので、ドラゴンに負け、runGame関数からリターンします。

3.inputStr周辺にリターンアドレスとシェルコードを入力(2ndペイロード)

runGame関数のスタックフレームのリターンアドレスが書き換えられていたので、リターン時にreadInput呼び出しの手前に飛びます。また、old EBPが書き換えられていたのでEBPレジスタが(0x80ed044)から-6した値になります。
readInput関数への引数は、EBPレジスタをベースアドレスとして指定されるので、1stペイロードで準備したものが引数として渡されることになります。
そこで、readInput関数を用いて、inputStr周辺(0x80ed040)に、パディング2バイト、シェルコードへのリターンアドレス、シェルコード本体、の順に入力します。

4.スタックピボットして、ESPがinputStr周辺を指すようにしてシェルコード起動

readInput関数からリターンする先は、readWrapper関数の、call readInputの次の命令です。
ここで、通常の関数呼び出しの手順を書きますと、ABIによると思いますが今回は:

  1. 呼び出し元で引数をスタックにプッシュしてcall命令。
  2. call命令で、スタックにリターンアドレスをプッシュ。
  3. 呼び出され側で、EBPレジスタをスタックにold EBPとしてプッシュ。
  4. EBPレジスタにESPの値をコピー。
  5. …処理…
  6. 呼び出され側でスタックをいじった場合、ESPを元に戻す。
  7. leave命令でESPをEBPレジスタから復元。次にEBPレジスタにold EBPをポップ。
  8. ret命令でリターンアドレスをポップし、呼び出し元に戻る。
という風になってます。
readWrapper関数からのリターン時に、EBPレジスタの値がESPレジスタにコピーされるので、ESPはinputStr周辺を指すようになります。

つまりleave命令でEBPレジスタの値(0x80ed044 – 0x6 = 0x80ed03e)がESPに復元され、次にold EBPがポップされるため、ESP は+4され、0x80ed042になります。
ret命令でリターンアドレスがポップされるためESPに+4して、ESPは0x80ed046になります。
0x80ed042 ~ 0x80ed045にシェルコードへのリターンアドレス(0x80ed04a)が、そして0x80ed04aからシェルコードが積まれてます。
そのため、readWrapper関数の最後のret命令でシェルコードが実行されます。





ちなみにシェルコードのダンプは以下のようになってます…

(gdb) x/10 0x80ed04a
   0x80ed04a <inputStr+6>:    (bad)  
   0x80ed04b <inputStr+7>:    xor    %ecx,%ecx
   0x80ed04d <ctfer+1>:    xor    %edx,%edx
   0x80ed04f <ctfer+3>:    mov    $0xb,%al
   0x80ed051 <ctfer+5>:    int    $0x80
   0x80ed053 <ctfer+7>:    call   0x80ed048 <inputStr+4>
   0x80ed058:    das    
   0x80ed059:    bound  %ebp,0x6e(%ecx)
   0x80ed05c:    das    
   0x80ed05d:    jae    0x80ed0c7 <_dl_static_dtv+71>

0x80ed04aの中身は(bad)となっているんだけど、中断せずに実行されるのはなぜなんでしょうね。(無知)

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #000000; background-color: #ffffff}

地下鉄怪奇ホラー「FS」後半の攻略

攻略なので当たり前だけどネタバレ注意

まだ体験版だったころの攻略情報はこちら
ホラーゲーム攻略 FS (美夏ルート):くろいのうとの断片のうと
完成版での情報は(詳しくは書いてないけど)こちら
FS:鼠喰いのひとりごと

エレベータ後の流れを説明した攻略情報が見つからなかったので書きます
美夏ルート、そして4人全員生存状態です。
そしてあまり細かくないけど何をやれば良いかを書きます

エレベータを降りた後、通り魔男が追いかけてくるので逃げてる途中、橋を渡ってると1人落ちます。
そして操作がそちらに移ります。

落ちた先は駅の下の排水管…というか下水エリアみたいなところ

落ちている手記によると、ホームレスが生活していたらしいです。
異臭がするとか書いてあったから、死んでしまったのかも。

そのホームレスの成れの果てと思しき黒い影が追いかけてくるので逃げつつ、「ガソリン」と「操作盤の鍵」を集めます。これはシャッターの横の発電機を動かすのに必要です。それと「ハム」を入手します。これはシャッター横の発電機に集まっているクモ型の敵をおびき寄せるために必要です。

ガソリン:確か、出入り口が2つある、中にクモ型の敵が1匹いる部屋にあります。
操作盤の鍵:シャッターのある部屋の右の部屋の、水の中です。さらに右の部屋から階段を降りれば水の中に入れます。
ハム:ホームレス生活エリアの足場を押して足場にし、右上にあるダクトに入ります。その先の部屋にあります。

シャッターを開ければ、橋が落ちて仲間とはぐれたエリアに戻れます。
このエリアの立ち入り禁止の鎖の先で雪乃が発見できます。
連絡通路の鍵」をどこかで入手してください。狭いのでたぶんすぐ見つけられます。

セーブポイントがある小部屋を出て右に行き、連絡通路の鍵を使います。

この廊下で振り向くと死にます。

ホームへ至る駅構内と思われる場所に出ます。

適当に探索してください。
回帰を視ると、でかい怨霊が追いかけてくるエリアが複数(2つくらい?)あります。

セーブポイントのある小部屋を基準にして説明します。

この部屋を出て右、美容室があるエリアで、なんかよくわからん敵が急に出てきて捕まります。すると徐々に暗くなるだだっ広い場所に転送されます。エレベータに乗る前、ツキバク人形を車椅子に乗せてあげている場合だけなのかわかりませんが、この場所にツキバク人形が出ますので、歩きまわって探してください。調べるとポヨッと音がでます。何個かやったら、セーブポイント小部屋の右上の本棚を押すと出る隠し出入り口からいける部屋に飛ばされます。
これ以降、よくわからん敵が出ることはなくなりました。

次に、美容室に入り、右の部屋に入ります。ニセモノの四季のノートがあるので調べると、奥のマネキンの頭が追いかけてきます。
部屋の上側の戸棚に重い金庫があるので調べると、それを投げてマネキンの頭をやっつけます。

どこで手に入れたか忘れましたが、左端の部屋の柵を開けるをどこかで手に入れて、行った先の小部屋に肩車で入ると女子高生の霊みたいなのに接触します。そのあと残り2人を探すことになったはず…。

その辺を歩っていてときどき回帰を使ったりして2人の女子高生の霊みたいなのを見つけてください。エリア右端の「不気味な落書き」があるあたりの部屋とその1つ手前にいたような覚えがあります。「大川桜の記憶」と「秋山七瀬の記憶」が手に入ります。

詳細を失念してしまったけど「改札口の鍵」をどこかで手に入れて改札口に行くと、四季らしきものがいます。
戦うことになるので、さっさと落としてしまった魔除けのナイフを拾い接触を何回かすると倒せます。

倒した後は、駅舎が綺麗なものになります。四季の亡骸を調べると「四季の短刀」が入手できます。

キオスクで回帰を視ると店員さんがいてアイテムの鑑定をしてもらえます。(攻略には関係ない)

あとはホームに行き、仲間の2人と合流します。

そしてラスボス。

ラスボスは回帰を視るモードで女の子に6回くらい接触すればok。
3人目くらいから、敵も出現するので注意。
ゲームオーバーになると、また長いイベントを見なくてはなりません。

ネタバレするとFSは友情、Friend Shipのことだったようです。多分。
4人生存エンドでは4人が仲良くなって遊ぶシーンで終わります。

ホーネット・テストライド

近所の山へ…
凍結が怖いので途中までしか登りませんけどね。

ハブダンパーの隙間を埋めたので調子を見るために走ります

ブレーキローターとパッドが接触しちゃってるように見えるので心配だったんですが、フロントは温まってないので大丈夫っぽい。
リアがほんのり暖かいような気がするけど、これは引きずってるのかそれとも無意識にリアを多めに掛けているのかわかりません。

サイドカウルが黒いと引き締まって見えますね。
白化に対処したのは正解でした。

とあるダムのそば

ムッ

看板をよくみると、タイヤに貼ってある銘柄シールが貼ってあります
NS-2Rの165/55R15、調べると軽自動車などのサイズですね。

他にも、ガードレールの端っこ(袖ビーム)にも大量のシールがあります。
車のタイヤはあまり知りませんが、アドバン ネオバとかあるので、走り屋が好きそうな銘柄たちなのかな。
それにしても、タイヤ販売店で履き替えたのならこんなシール店で捨てられちゃうはず。
ということは、やはり自分でタイヤ交換する「その筋」(走り屋)の人たちが貼っていってるのかも。

前回、ハブダンパーの隙間を埋めたおかげで確かに快適になりました。

しかし、やはりリアサスやスイングアームの動作を改善したいです。
路面の凹凸でわりと衝撃がきてしまい、エストレヤより体が宙に浮きます。
まぁ、ホーネットはもとからこういう乗り心地で、別にスイングアームの動きが渋いとかリアサスがへたってるとかそういうワケじゃないかもしれませんけどね。それをハッキリさせる意味でも、メンテナンスしておきたいというわけです。

状態として、昔よりニュートラルに入れてクラッチつないでるときの異音が大きくなったような気がします。
これギアが入ってる時も耳を清ますとしているような気がします。走行に影響はあまり感じないわりに精神衛生上悪いんですが、原因を特定するのが大変だしホーネットは今年中に乗り換えるかもしれないので放置しときます。

あとこれはホーネットに対する所感です:
高回転型エンジンは峠道で回すと楽しく、1万回転以上からの加速はやはり良いです。
しかし、1速だとローギアードすぎるし、
2速だと1万回転くらいに到達する頃には60km/h出てて、そこから加速を楽しむと法定速度オーバー。曲がれるけど、事故ったら大怪我か死亡。
どうしても楽しい回転数、本領発揮ゾーンを使おうとすると危険走行になってしまいます。
そして、自分はその本領発揮ゾーンを使わないのなら高回転エンジンに乗ってる意味がないと思ってどうしても回したくなってしまう。
→いつかヤバイ事故を起こす

…というわけで、高回転エンジンは250ccですら回すとやっぱり危ない速度になるので、低回転を使いトルクで走る、ゆっくり走って楽しいバイクに乗り換えたいと思います。
巷(というかネット上です)には数字の大小しか見れない人がけっこういて、最高出力の馬力が低いとそれだけで低く評価してしまう人たちが散見され、あまり評価はされていないっぽい車種もありますが、単気筒あたりに乗り換えようと思います。

ホーネットのスピードメーターケーブルの点検とクラッチレバーのグリスアップ

2018/2/23

スピードメーターケーブルを注油しようとおもって外した…
けどグリスがねっちょり。
シリコンスプレーを注したらグリスを流してしまいそうなので辞めました。
ハメるときはバイクを押して、ドリブンスプロケ側の出っ張りを回しながら、ケーブルを差し込むといつかハマります。

クラッチレバーのロックナットが無くなってます。たぶん購入時からです。
ロックナットは緩み止めのためのものなので、なくてもなんとかなります
グリスアップしときました。

偶然、家にM6ナットがあったのでつけました。

ホーネットのハブダンパーを交換…したかったが隙間を埋めるだけ

ODO:33,800kmくらい
2001年式ホーネット

買ったときから思ってたけど、加速時・エンブレ減速時にショックがあり、たぶんハブダンパーが縮んで隙間ができてるんだろ〜な〜って感じてたのでどうにかします。

ハブダンパーにアクセスするまで

アクスルシャフトを外す

アクスルシャフトは固く締められており、長さ30cmくらいのメガネレンチだと緩みません。
前回、量販店でのタイヤ交換時に、電動工具かエアツールでガチガチに締められてしまったのかも…。
556を吹きしばらく浸透させ、メガネレンチをハンマーでがつがつ叩きます。
そのあと手で思いっきり力をかけたら回りました。

前後アクスルのナットは緩み止めナットになっており、手では回せないです。工具が必要。

(本来、この時点でリアブレーキキャリパをキャリパーサポートから外すべきです。アクスルシャフトを外したあとは共締めされたキャリパーサポートがスイングアームから外れるため力が掛けにくいです。後述しますがブレーキキャリパを外さなくともリアタイヤはなんとか外せますが…。)

後輪を持ち上げるために、スイングアームの左右連結部あたりにジャッキを掛けます。

アクスルシャフトを抜きます。ハンマーで右側をゴンゴンやって左側の頭を手で掴める程度出したあと、タイヤを少し持ち上げたり戻したりして引き抜きます。
タイヤ自体は太いわりに意外と軽いので片手で持ち上げられます。

ホイールを外す

(本当は、アクスルシャフトを外す前にキャリパーをキャリパーサポートから外しておけば、もっと楽にホイールを外せます)

キャリパーサポートは2点でバイク側に固定されています。一方はアクスルに共締めされていて、もう一方はスイングアームの出っ張りで保持されているだけので、アクスルシャフトを外すとキャリパーは前後に動かせます。
上手いこと動かしてホイールを前に押し、チェーンを緩めてスプロケから外し、そのあと手前に引き抜くことで、バイクから外します。

スプロケの歯はまだ大丈夫。

ハブダンパーの面倒を見る

ハブダンパーを観察…
ホイールベアリングの品番はNTNの6203LUですね。

1~2ミリほどのガタツキがあるので、ハブダンパーを交換したいです…が、パーツを注文してません。

よって隙間をこのプラスチックシートで埋めます。本当はゴムが良いと思います。
5mmくらいの分厚いゴム板を持ってたんだけど、隙間が思ったほど大きくなかったので使えません。

7枚ずつ入れるとキツキツになりました。

組み付けると狙い通りガタツキがなくなりました。

ついでにチェーンのたるみが3.5mmだったので2.5mmに調節しました。
ホーネットのチェーンたるみ規定値2.5mm~3.5mmです。

乗り味が改善された!

テストライドすると、アクセルオン/オフによるショックがなくなりました。
めでたしめでたし。

スイングアームのピボットのグリスアップもいつかしたいけど、そのためにはソケットレンチが必要です…

pwnable.tw #3 calcの説明

簡単な数値計算をするプログラム、calcが渡されます。

前2問と比べかなり複雑な内容になっております。

デコンパイルを試す

色々writeupを見たところ、今回は逆アセンブルしただけではまだ把握しにくいらしく、そこからソースコードを再現して考えているものが多くありました。
なお、IDAのプラグイン、Hex-Rays Decompiler(有料、たぶん高価)などを用意しておけば、IDAでF5キーを押すと逆コンパイルできます。
持ってませんけどね。

RecStudioっていう逆コンパイラを試したら、分岐がif-elseやswitch-caseになって見やすくなった以外に嬉しいことはあまりない感じの出力で、しかも、目的とすべき場所が逆コンパイルできてませんでした。(使い方がわかってないだけかも)

https://retdec.com/のRetargetable Decompilerっていうのはサーバ上で逆コンパイルしてくれるし結構速いし出すコードもいくぶんかわかりやすいので今後はこちらを使います。しかし5分以上かかるファイルは扱えません。そのときはたぶん公開されているIDA用のプラグインを使えば良いです。

//
// This file was generated by the Retargetable Decompiler
// Website: https://retdec.com
// Copyright (c) 2018 Retargetable Decompiler <info@retdec.com>
//

#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// ------------------------ Structures ------------------------

struct _IO_FILE {
    int32_t e0;
};

// ------------------- Function Prototypes --------------------

int32_t calc(void);
int32_t eval(int32_t * a1, int32_t a2);
int32_t parse_expr(int32_t a1, int32_t * a2);

// --------------------- Global Variables ---------------------

int32_t g1 = 0; // ebp
char * g2 = "0"; // 0x80bf7a8
struct _IO_FILE * g3 = (struct _IO_FILE *)0x80ec200; // 0x80ec4c0

// ------------------------ Functions -------------------------

// Address range: 0x8048ee1 - 0x8048ff7
int32_t eval(int32_t * a1, int32_t a2) {
    int32_t result = (int32_t)a1;
    int32_t v1 = 0x1000000 * a2;
    if (v1 == 0x2b000000) {
        int32_t v2 = result - 4; // 0x8048f2d
        int32_t v3 = *(int32_t *)(v2 + 4 * *a1); // 0x8048f2d
        int32_t v4 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048f3c
        *(int32_t *)(v2 + 4 * *a1) = v4 + v3;
        // branch -> 0x8048fe4
        // 0x8048fe4
        *a1 = *a1 - 1;
        return result;
    }
    // 0x8048ef8
    if (v1 <= 0x2b000000) {
        // 0x8048efd
        if (v1 == 0x2a000000) {
            int32_t v5 = result - 4; // 0x8048f92
            int32_t v6 = *(int32_t *)(v5 + 4 * *a1); // 0x8048f92
            int32_t v7 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048fa1
            *(int32_t *)(v5 + 4 * *a1) = v7 * v6;
            // branch -> 0x8048fe4
        }
        // 0x8048fe4
        *a1 = *a1 - 1;
        return result;
    }
    // 0x8048f07
    switch (v1 / 0x1000000) {
        case 45: {
            int32_t v8 = result - 4; // 0x8048f61
            int32_t v9 = *(int32_t *)(v8 + 4 * *a1); // 0x8048f61
            int32_t v10 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048f70
            *(int32_t *)(v8 + 4 * *a1) = v9 - v10;
            // branch -> 0x8048fe4
            break;
        }
        case 47: {
            int32_t v11 = *a1; // 0x8048fb4
            int32_t v12 = result - 4; // 0x8048fc4
            uint32_t v13 = *(int32_t *)(v12 + 4 * *a1); // 0x8048fc4
            int32_t v14 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048fd3
            *(int32_t *)(v12 + 4 * v11) = (int32_t)((0x100000000 * (int64_t)(v13 / 0x80000000) | (int64_t)v13) / (int64_t)v14);
            // branch -> 0x8048fe4
            break;
        }
    }
    // 0x8048fe4
    *a1 = *a1 - 1;
    return result;
}

// Address range: 0x804902a - 0x8049378
int32_t parse_expr(int32_t a1, int32_t * a2) {
    int32_t v1 = *(int32_t *)20; // 0x8049047
    int32_t v2;
    int32_t v3 = &v2; // 0x804906c_0
    function_8048240(v3, 100);
    int32_t v4 = 0; // 0x804935449
    int32_t v5 = a1; // 0x80491a747
    int32_t v6 = 0;
    // branch -> 0x8049081
    while (true) {
        int32_t v7 = v6 + a1; // 0x804908d
        char * v8 = (char *)v7;
        int32_t v9 = v4; // 0x804935450
        int32_t v10 = v5; // 0x80491a746
        if ((int32_t)*v8 >= 58) {
            int32_t v11 = v7 - v5; // 0x80490b7
            char * mem = malloc(v11 + 1); // 0x80490c7
            int32_t v12 = (int32_t)mem; // 0x80490c7_3
            memcpy(mem, (char *)v5, v11);
            *(char *)(v12 + v11) = 0;
            function_80482a0(v12, (int32_t)&g2);
            int32_t v13;
            if (v12 == 0) {
                // 0x804910d
                puts("prevent division by zero");
                fflush((struct _IO_FILE *)*(int32_t *)0x80ec4c0);
                // branch -> 0x804935f
            } else {
                uint32_t str_as_i = atoi(mem); // 0x8049136
                if (str_as_i >= 1) {
                    int32_t v14 = *a2; // 0x804914a
                    *a2 = v14 + 1;
                    *(int32_t *)((int32_t)a2 + 4 + 4 * v14) = str_as_i;
                    // branch -> 0x8049164
                }
                int32_t v15 = a1 + 1 + v6;
                if (*v8 != 0) {
                    // 0x8049179
                    if ((int32_t)*(char *)v15 > 57) {
                        // 0x80491c0
                        puts("expression error!");
                        fflush(g3);
                        // branch -> 0x804935f
                        // 0x804935f
                        if (*(int32_t *)20 != v1) {
                            // 0x804936b
                            __stack_chk_fail();
                            // branch -> 0x8049370
                        }
                        // 0x8049370
                        g1 = v13;
                        return 0;
                    }
                }
                char * v16 = (char *)(v4 + v3);
                char v17 = *v8; // 0x80491f1
                int32_t v18; // 0x804935451
                if (*v16 == 0) {
                    // 0x80491e3
                    *v16 = v17;
                    v18 = v4;
                    // branch -> 0x804930c
                  lab_0x804930c_5:
                    // 0x804930c
                    if (*v8 == 0) {
                        // 0x8049354
                        if (v18 <= 0) {
                            // 0x804935a
                            // branch -> 0x804935f
                            // 0x804935f
                            if (*(int32_t *)20 != v1) {
                                // 0x804936b
                                __stack_chk_fail();
                                // branch -> 0x8049370
                            }
                            // 0x8049370
                            g1 = v13;
                            return 1;
                        }
                        for (int32_t i = v18; i >= 1; i--) {
                            // 0x8049330
                            eval(a2, (int32_t)*(char *)(i + v3));
                            // continue -> 0x8049330
                        }
                        // branch -> 0x804935f
                        // 0x804935f
                        if (*(int32_t *)20 != v1) {
                            // 0x804936b
                            __stack_chk_fail();
                            // branch -> 0x8049370
                        }
                        // 0x8049370
                        g1 = v13;
                        return 1;
                    }
                    // 0x8049324
                    v4 = v18;
                    v5 = v15;
                    v6++;
                    // branch -> 0x8049081
                    continue;
                } else {
                    // 0x8049203
                    switch ((int32_t)v17) {
                        default: {
                            // 0x80492e8
                            eval(a2, (int32_t)*v16);
                            v18 = v4 - 1;
                            // branch -> 0x804930c
                            goto lab_0x804930c_5;
                        }
                        case 37: {
                            // 0x804926c
                            if (*v16 == 43) {
                              lab_0x804928a_4:;
                                int32_t v19 = v4 + 1; // 0x804928a
                                *(char *)(v19 + v3) = *v8;
                                v18 = v19;
                                // branch -> 0x804930c
                                goto lab_0x804930c_5;
                            }
                            break;
                        }
                        case 42: {
                            // 0x804926c
                            if (*v16 == 43) {
                                goto lab_0x804928a_4;
                            }
                          lab_0x804927b:
                            // 0x804927b
                            if (*v16 == 45) {
                                goto lab_0x804928a_4;
                            }
                          lab_0x80492ab:
                            // 0x80492ab
                            eval(a2, (int32_t)*v16);
                            *v16 = *v8;
                            v18 = v4;
                            // branch -> 0x804930c
                            goto lab_0x804930c_5;
                            break;
                        }
                        case 43: {
                            // 0x804922c
                            eval(a2, (int32_t)*v16);
                            *v16 = *v8;
                            v18 = v4;
                            // branch -> 0x804930c
                            goto lab_0x804930c_5;
                        }
                        case 45: {
                            // 0x804922c
                            eval(a2, (int32_t)*v16);
                            *v16 = *v8;
                            v18 = v4;
                            // branch -> 0x804930c
                            goto lab_0x804930c_5;
                        }
                        case 47: {
                            // 0x804926c
                            if (*v16 == 43) {
                                goto lab_0x804928a_4;
                            }
                            goto lab_0x804927b;
                        }
                    }
                    // 0x804927b
                    if (*v16 == 45) {
                        goto lab_0x804928a_4;
                    }
                    goto lab_0x80492ab;
                }
            }
            // 0x804935f
            if (*(int32_t *)20 != v1) {
                // 0x804936b
                __stack_chk_fail();
                // branch -> 0x8049370
            }
            // 0x8049370
            g1 = v13;
            return 0;
        }
        // 0x8049324
        v4 = v9;
        v5 = v10;
        v6++;
        // branch -> 0x8049081
    }
}

// Address range: 0x8049379 - 0x8049433
int32_t calc(void) {
    int32_t v1 = *(int32_t *)20; // 0x8049383
    int32_t v2;
    int32_t v3 = &v2; // 0x8049395_0
    function_8048240(v3, 1024);
    int32_t v4; // 0x80493c2
    if (get_expr(v3, 1024) == 0) {
        // 0x80493bd
        v4 = *(int32_t *)20;
        if (v4 != v1) {
            // 0x804942d
            __stack_chk_fail();
            // branch -> 0x8049432
        }
        // 0x8049432
        return v4 ^ v1;
    }
    // 0x80493cc
    // branch -> 0x80493cc
    while (true) {
        // 0x80493cc
        int32_t v5;
        init_pool((int32_t)&v5);
        if (parse_expr(v3, &v5) != 0) {
            // 0x80493f6
            printf("%dn", *(int32_t *)(g1 - 1436 + 4 * (v5 - 1)));
            fflush(g3);
            // branch -> 0x804938d
        }
        // 0x804938d
        function_8048240(v3, 1024);
        if (get_expr(v3, 1024) == 0) {
            // break -> 0x80493bd
            break;
        }
        // continue -> 0x80493cc
    }
    // 0x80493bd
    v4 = *(int32_t *)20;
    if (v4 != v1) {
        // 0x804942d
        __stack_chk_fail();
        // branch -> 0x8049432
    }
    // 0x8049432
    return v4 ^ v1;
}

// Address range: 0x8049452 - 0x80494af
int main(int argc, char ** argv) {
    // 0x8049452
    signal(SIGALARM, (void (**)(int32_t))timeout);
    alarm(60);
    puts("=== Welcome to SECPROG calculator ===");
    fflush(g3);
    calc();
    return puts("Merry Christmas!");
}

// --------------- Statically Linked Functions ----------------

// void __stack_chk_fail(void);
// unsigned int alarm(unsigned int seconds);
// int atoi(const char * nptr);
// int fflush(FILE * stream);
// void * malloc(size_t size);
// void * memcpy(void * restrict dest, const void * restrict src, size_t n);
// int printf(const char * restrict format, ...);
// int puts(const char * s);
// __sighandler_t signal(int sig, __sighandler_t handler);

// --------------------- Meta-Information ---------------------

// Detected compiler/packer: gcc (4.8.2)
// Detected functions: 4
// Functions selected to be decompiled but not found: initpool
// Decompiler release: v2.2.1 (2016-09-07)
// Decompilation date: 2018-02-21 06:47:59

とこんな感じになります。試しにやっただけなので、読みやすく書き換えたりしてません。後述の参考URLのソースコードを読んだ方がわかりやすいです。

ちなみに逆コンパイルは、逆アセンブルと同じで、人間がコメントを追加したり変数名・関数名などを書き換えて読みやすいようにする必要があります。変数がv1とかv2とかそのままだと理解しにくいので…。

逆アセンブルを試す

あわせてIDAで逆アセンブルしますが…

私レベルでは逆アセンブルしただけじゃよくワカラナイ…

無理なので他人のWriteupにて再現されたソースコードを見る

他の人のwriteupにてわかりやすく再現されたソースコードを見ます。
各関数はこれ
全体の流れは
どうやら…演算子と被演算子をそれぞれスタックにプッシュして計算するそうです。
先ほどのリンク先でわかりやすく逆コンパイルして書き換えたソースコードが掲載されているのでそちらを見て欲しいです。関数名や変数名もそちらに準じます。

プログラムの流れ

parse_expr関数で入力の内容を判断します。
入力を、数字以外の文字が来るまで読みます。
数字の場合は被演算子スタック(expr->buf配列)に追加し、演算子スタック(op_s配列)の中身があればeval関数で計算します。
演算子の場合は、’+’や’-‘の場合は出現次第eval関数に被演算子スタックと演算子スタックのトップを渡して計算します。つまり、一つ前の演算子の計算を行います。その後で、今読んだ演算子を演算子スタックのトップとすげ替えます。

それ以外、つまり’*’や’/’, ‘%’のときは、’+’や’-‘より優先度が高いので処理が少し複雑です。演算子スタックのトップ(=一つ前に出現した演算子)が’+’や’-‘で無い場合に限り、一つ前に出現した演算子の計算を行います。そして演算子スタックのトップをすげ替えます。もし’+’や’-‘だった場合、こちらの演算子の方が優先順位が高いため、演算子スタックのトップに今回出現した演算子を積みます。
全ての入力を読み終わった後、演算子スタックが空になるまでポップしeval関数で計算を進めていきます。

バグのありか

そして実際に計算を行うeval関数に実装のミスがあります。
渡された演算子に対応する計算を行うのですが、被演算子配列をevalとし、その長さをいれてある変数をeval->bufsizeとします。すると例えば’+’演算子の場合expr->buf[expr->bufsize-2] += expr->buf[expr->bufsize-1]を実行することで所望の計算をしています。
これだともし「+100」のように演算子の左側に被演算子が無い場合、上の計算方法では配列の範囲外を書き換えてしまうことになります。
そして、スタックの状態を調べると、演算子の配列expr->bufは、配列の長さexpr->bufsizeのすぐ次に配置されており、eval関数のバグを利用すれば配列の長さexpr->bufsizeを書き換えられます。
すると
expr->buf[expr->bufsize-2] += expr->buf[expr->bufsize-1]
では、インデックスの指定にexpr->bufsizeを用いているため、好きな場所を書き換えられます。

バグを利用した動作例

例えば「+361 + 1」と入力すると
parse_expr関数で+を読んだときは初めての演算子なのでop_s演算子スタックに突っ込まれるだけです。
次にparse_expr関数で361を読んだあとですが、
expr->buf[0]が361、expr->bufsizeが1になります。
そして前回読んだ+演算子についてeval関数で計算されます。
expr->buf[expr->bufsize -2] += expr->buf[expr->bufsize -1]
により、範囲外アクセスが起こります。つまりexpr->buf[-1] += expr->buf[0]
となります。
このとき&(expr->buf[-1]) == &(expr->bufsize)なので、expr->buf[-1] += expr->buf[0]は1 += 361と同じです。 このように脆弱性を利用しexpr->bufsizeが362になってしまいます。
このあとexpr->bufsizeはデクリメントされ、expr->bufsize == 361になります。
次に2個目の+を読んだときは、op_s演算子スタックに突っ込まれるだけです。
このあとparse_expr関数で1が読まれます。
expr->buf[361]は1、expr->bufsizeは362になります。
そして2個目の+についてeval関数で計算が行われます。
eval関数を呼びますと、expr->buf[expr->bufsize -2] += expr->buf[expr->bufsize -1]
、つまりexpr->buf[360] += expr->buf[361]
が行われます。
このあとexpr->bufsizeはデクリメントされ、expr->bufsize == 361になります。
そして答えとしてexpr->buf[expr->bufsize -1]の値が表示されます。この例でいうとexpr->buf[360]です。
このようにして、「+361+1」と入力したときは、expr->bufのあるアドレスから+361オフセットしたアドレスの指す先を1にし、+360オフセットしたアドレスの指す先に+1してしまいます。答えとして+360オフセットしたアドレスの中身が表示されます。
…という風な感じでメモリを弄っていきます。

検証

上の流れでいくと、「+n」とだけ入力したときは、expr->bufsizeが=nになり、答えとしてexpr->buf[n-1]が表示されます。

試してみると、「+1」でexpr->buf[0]の1が出力されています。expr->buf[]は長さ100の配列なので、expr->buf[100]は範囲外のはずです。「+101」にはちゃんとカナリアが入っており、正しそうです。後述しますが「+357」もカナリアが表示されてます
ちなみに「+0」は0で割るなというメッセージが出てしまいます。なぜか0が被演算子だと、除数のとき以外でもこうなる実装なので「+00」で回避しましょう。

user1@user1-VirtualBox:~/ダウンロード$ ./calc
=== Welcome to SECPROG calculator ===
+00
0
+1
1
+2
0
+3
0
+4
0
+100
0
+101
825241899
+102
0
+356
0
+357
1082368768
+358
0

Merry Christmas!

解法

セキュリティ機構に関して

スタックカナリア

配布されたプログラムcalcが備えたセキュリティ機構を調べてみると、スタックカナリアが使われています。
だいたい書き換えたくなるリターンアドレスの前にカナリアが配置されており、バッファオーバーフローなどでスタックを書き換えて行くと、連続した領域を書き換えることになるのでカナリアも書き換えてしまうので検知されます。
(演算子スタック用の配列は長さが100なので、eval関数においてなぜか実装されていない%演算子を上手に使うことでオーバフローさせて書き換えることもできますがカナリアで検知されるのでダメなのです)
しかし今回のように、アドレスを指定して書き換えができる場合、カナリアを避けることができるので検知されずに済みます。

NX

そしてNXも有効です。
スタックにエクスプロイトコードを積んでも使えません。
よって共有ライブラリ上にあるコードを再利用する、ROPを用いる必要があります。

ランダマイズ関係

ほか、アドレスのランダマイズは一切オフのようです。(自分じゃ調べてないけど、他人のwriteupでアドレス決め打ちな回答があるので)

具体的な解法

具体的に何を入力すれば良いかは
や、長いけど
が詳しいです
前提として、
具体的には、擬似コードでいうとexecve(“/bin/sh”, {“/bin/sh”, null}, null)をROPで実行します。
EAXにシステムコール 番号0x0b、EBXにファイルネーム”/bin/sh”、ECXにargv(渡す引数){“/bin/sh”, null}、EDXにenvp(環境変数へのポインタ、今回はnull)を格納し、INT命令を実行します。
ROPチェインをスタックに格納し、calc関数からのret実行時に開始します。
それにはcalc関数からのret実行時に使うリターンアドレスの位置を調べなくてはなりません。
eval関数のバグで最初に書き換えられる、配列の長さの値の格納されたアドレスから、カナリアまでのオフセットを調べると、1428です。
配列の長さexpr->bufの値に設定すべき値は、(1428-4)/4 = 356 (DWORD、4バイト単位)…に+1して357です。先ほど説明したように、確かに「+357」とするとexpr->bufのある位置から+356オフセットした場所に格納されているカナリアが表示されます。
スタックの状態を調べると、「+357」にはカナリア、「+358」はパディング、「+360」には前回のEBP、「+361」にはリターンアドレスが積まれている…らしいです。
よって「+361」からROPチェインを積んでいきます。
単純にメモリに書き込めるのではなく、その一つ前にも加算されてしまうという(→前述の太字のところを読んでください)ことに注意して書き込みます。

以上です。

pwnable.tw #2 orwの説明

実行すると”Give me your shellcode:”と挑発的なことを言ってくるプログラムが題材

使えれば良いやradare2(静的解析編):拾い物のコンパスによくまとめられている、radar2というフレームワークを使う。
gdbとかobjdumpとか、そういうオーソドックスなものより表示が人間にわかりやすい感じで大変嬉しい。
CTFマニアには常識だったのかもしれないが…ついさっき知った。

$ r2 orw
[0x080483d0]> aaa
<...中略...>
[0x080483d0]> afl
<...中略...>
[0x080483d0]> s sym.main
[0x08048548]> VV
<...中略...>
:> pdf@sym.main
            ;-- main:
/ (fcn) sym.main 81
|   sym.main ();
|           ; var int local_4h_2 @ ebp-0x4
|           ; var int local_4h @ esp+0x4
|              ; DATA XREF from 0x080483e7 (entry0)
|           0x08048548      8d4c2404       lea ecx, [local_4h]         ; 4
|           0x0804854c      83e4f0         and esp, 0xfffffff0
|           0x0804854f      ff71fc         push dword [ecx - 4]
|           0x08048552      55             push ebp
|           0x08048553      89e5           mov ebp, esp
|           0x08048555      51             push ecx
|           0x08048556      83ec04         sub esp, 4
|           0x08048559      e86dffffff     call sym.orw_seccomp
|           0x0804855e      83ec0c         sub esp, 0xc
|           0x08048561      68a0860408     push str.Give_my_your_shellcode: ; 0x80486a0 ; "Give my your shellcode:"
|           0x08048566      e815feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804856b      83c410         add esp, 0x10
|           0x0804856e      83ec04         sub esp, 4
|           0x08048571      68c8000000     push 0xc8                   ; 200
|           0x08048576      6860a00408     push obj.shellcode          ; 0x804a060
|           0x0804857b      6a00           push 0
|           0x0804857d      e8eefdffff     call sym.imp.read           ; ssize_t read(int fildes, void *buf, size_t nbyte)
|           0x08048582      83c410         add esp, 0x10
|           0x08048585      b860a00408     mov eax, obj.shellcode      ; 0x804a060
|           0x0804858a      ffd0           call eax
|           0x0804858c      b800000000     mov eax, 0
|           0x08048591      8b4dfc         mov ecx, dword [local_4h_2]
|           0x08048594      c9             leave
|           0x08048595      8d61fc         lea esp, [ecx - 4]
           0x08048598      c3             ret

なお、参考までにobjdumpコマンドでの逆アセンブルだとこんな感じ。radare2での逆アセンブルの方がパッと見で理解しやすいと思う。

$ objdump -d orw 
 08048548 <main>:
 8048548:    8d 4c 24 04              lea    0x4(%esp),%ecx
 804854c:    83 e4 f0                 and    $0xfffffff0,%esp
 804854f:    ff 71 fc                 pushl  -0x4(%ecx)
 8048552:    55                       push   %ebp
 8048553:    89 e5                    mov    %esp,%ebp
 8048555:    51                       push   %ecx
 8048556:    83 ec 04                 sub    $0x4,%esp
 8048559:    e8 6d ff ff ff           call   80484cb <orw_seccomp>
 804855e:    83 ec 0c                 sub    $0xc,%esp
 8048561:    68 a0 86 04 08           push   $0x80486a0
 8048566:    e8 15 fe ff ff           call   8048380 <printf@plt>
 804856b:    83 c4 10                 add    $0x10,%esp
 804856e:    83 ec 04                 sub    $0x4,%esp
 8048571:    68 c8 00 00 00           push   $0xc8
 8048576:    68 60 a0 04 08           push   $0x804a060
 804857b:    6a 00                    push   $0x0
 804857d:    e8 ee fd ff ff           call   8048370 <read@plt>
 8048582:    83 c4 10                 add    $0x10,%esp
 8048585:    b8 60 a0 04 08           mov    $0x804a060,%eax
 804858a:    ff d0                    call   *%eax
 804858c:    b8 00 00 00 00           mov    $0x0,%eax
 8048591:    8b 4d fc                 mov    -0x4(%ebp),%ecx
 8048594:    c9                       leave  
 8048595:    8d 61 fc                 lea    -0x4(%ecx),%esp
 8048598:    c3                       ret    
 8048599:    66 90                    xchg   %ax,%ax
 804859b:    66 90                    xchg   %ax,%ax
 804859d:    66 90                    xchg   %ax,%ax
 804859f:    90                       nop

このwriteup[pwnable.tw] orw — 100pts : Giuseppeがよくできており、もうこれを見るだけで全部済む。
(この記事はソレのほぼ翻訳版みたいな内容なのでwriteupってタイトルにするのをためらった)

0x08048576から0x0804857dまでで、read関数を用いてobj.shellcodeの指すアドレスに最大200バイト、シェルコードを読み込ませている。(“Give me your shellcode:”の後に入力したものが格納される)

続く0x08048585 ~ 0x0804858aで、obj.shellcodeの指すアドレスにジャンプしている。

特に落とし穴はなく、問題文にある通りopen, read, writeシステムコールのみを使って/home/orw/flagを読めば良い。

実際のシェルコードは先ほどの参考writeupに載ってるのでそちらを参照するとして、
流れ的にはopenで/home/orw/flagのファイルディスクリプタを取得、readでファイルディスクリプタで指定した/home/orw/flagからシェルコードの末尾以降にファイルの内容を読み出し、writeで標準出力にそれを出力する。

pawnable.tw #1 startのwriteup

ファイルが与えられる。
fileコマンドで調べるとELF32 (Linuxでよく使われる実行ファイル)とのこと。
objdumpで逆アセンブルする。

 08048060 <_start>:
 8048060:    54                       push   %esp
 8048061:    68 9d 80 04 08           push   $0x804809d
 8048066:    31 c0                    xor    %eax,%eax
 8048068:    31 db                    xor    %ebx,%ebx
 804806a:    31 c9                    xor    %ecx,%ecx
 804806c:    31 d2                    xor    %edx,%edx
 804806e:    68 43 54 46 3a           push   $0x3a465443
 8048073:    68 74 68 65 20           push   $0x20656874
 8048078:    68 61 72 74 20           push   $0x20747261
 804807d:    68 73 20 73 74           push   $0x74732073
 8048082:    68 4c 65 74 27           push   $0x2774654c
 8048087:    89 e1                    mov    %esp,%ecx
 8048089:    b2 14                    mov    $0x14,%dl
 804808b:    b3 01                    mov    $0x1,%bl
 804808d:    b0 04                    mov    $0x4,%al
 804808f:    cd 80                    int    $0x80
 8048091:    31 db                    xor    %ebx,%ebx
 8048093:    b2 3c                    mov    $0x3c,%dl
 8048095:    b0 03                    mov    $0x3,%al
 8048097:    cd 80                    int    $0x80
 8048099:    83 c4 14                 add    $0x14,%esp
 804809c:    c3                       ret    

 0804809d <_exit>:
 804809d:    5c                       pop    %esp
 804809e:    31 c0                    xor    %eax,%eax
 80480a0:    40                       inc    %eax
 80480a1:    cd 80                    int    $0x80

文字を表示しているのにどこにもcallなどでprintfみたいな感じの関数を呼んでいる気配がない。
実はシステムコールで表示している。
各種レジスタやスタックに引数やシステムコール番号を突っ込んだあとにINT 80hでその機能が実行される。

straceコマンドを使い、システムコール呼び出しを調べると、sys_writeとsys_readが使われている。

gdbなどでretの直前まで実行しスタックをダンプしてみて、図示すると

——
一回前のESP
——
リターンアドレス(0x804809dが入れられている、_exitへ飛ぶため。)
——
20バイトの”Let’s start the CTF”

——

となっている。

なお、スタックの見方は…

$gdb start
(gdb)run
<...中略...>
(gdb)disas
<...中略...>
(gdb) b *0x08048099
Breakpoint 1 at 0x8048099
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/ダウンロード/start 
Let's start the CTF:aaaaaaaaaaaa

Breakpoint 1, 0x08048099 in _start ()
(gdb) i r esp
esp            0xffffd0d4    0xffffd0d4
(gdb) x/50xb 0xffffd0d4
0xffffd0d4:    0x61    0x61    0x61    0x61    0x61    0x61    0x61    0x61
0xffffd0dc:    0x61    0x61    0x61    0x61    0x0a    0x68    0x65    0x20
0xffffd0e4:    0x43    0x54    0x46    0x3a    0x9d    0x80    0x04    0x08
0xffffd0ec:    0xf0    0xd0    0xff    0xff    0x01    0x00    0x00    0x00
0xffffd0f4:    0xc1    0xd2    0xff    0xff    0x00    0x00    0x00    0x00
0xffffd0fc:    0xe6    0xd2    0xff    0xff    0xf1    0xd2    0xff    0xff
0xffffd104:    0x03    0xd3

とやるとスタックの内容がわかる。

sys_writeで標準出力(fd = 1)に出力するのは、「スタックに確保された20バイトの”Let’s start the CTF”」だ。

システムコールでは、EAXにシステムコール番号を、EBX、ECX、EDXに引数を入れるようで、
sys_writeを呼び出す前、
読み込んでほしい場所の開始アドレスを格納するECXレジスタにこの20バイトの先頭アドレスが保存され、次に長さを格納するEDLレジスタに14h(= 20)を入れるので、sys_writeはちゃんと20バイト読んでくれる。

しかし、その後のsys_readを呼び出すときもECXを変えておらず、それなのに長さを入れるEDLレジスタに0x3c ( =60)を入れている。(本来なら、スタックに新たに60バイト、確保してそのアドレスをECXに設定すべきである。)

つまり、20バイト以上のデータを入力するとスタックの内容を破壊してしまう。

1回目のretまで

これを利用してリターンアドレスを書き換える。
20バイト書き込んで、そのあとに、retしたとき行きたい先のアドレスを書き込めば良い。

具体的には、戻る先はsys_writeの読み込み先頭アドレスをECXに格納し準備をしている
0x8048087 mov    %esp,%ecx
だ。

2回目のretまで

ここにretした時点でスタックの一番上には、ESPの値が格納されている。
(一番最初に0x8048060     push   %esp によってESPの値がスタックにプッシュされていたから。そのあとに積まれたリターンアドレスなどはretでpopされた)

0x8048087 mov    %esp,%ecxにより、sys_writeにより読み出される先頭アドレスが格納されるECXには、ESPが格納される。
よってsys_writeによって現在のESPの指すところから20バイト表示されると、その先頭に現在のESPアドレスが表示される。

これでスタックアドレスがリークした。

次にまたsys_readで入力を受け付けてくれる。

またバッファオーバーフローさせ、
最終的に、リターンアドレスにはシェルコードのアドレスを入れたい。
「リークしたスタックアドレスの20バイト先」を指すようにする。そしてそのあとにシェルコードを仕込む。
https://www.reddit.com/r/LiveOverflow/comments/5r47h1/a_little_help_needed_with_a_simple_pwnable/

なぜ「リークしたスタックアドレスの20バイト先」なのか。

他の人のWriteupを見ると、ウマイ人はこんなの当たり前だと思ってるのか書いてなかったりする。

リークしたESP、これをleaked_ESPとすると、リークした時点での現在のESPはESP = -4 + leaked_ESPである。
(-4 + leaked_ESP)から(0 + leaked_ESP)の4バイトには、leaked_ESP自体の値が入っている。

リークしたあとに0x8048099 add    $0x14,%espで+20されているので、ESP = 16+ leaked_ESP。
最後にretすると4バイトのリターンアドレスがpopされてESP=20 + leaked_ESP
となる。

最後のretでシェルコードに飛ぶために、ESP=(16 + leaked_ESP)から(20+leaked_ESP)の4バイトに後続のシェルコードへのリターンアドレス、20+leaked_ESPを積みたい。

0x8048087 mov    %esp,%ecx 以来、書き込み先アドレスが格納されるECXが変更されていないため、シェルコード書き込み時のsys_readは、リークのためretしたあとのESP、つまりESP=-4  + leaked_ESPのときのアドレスから書き込み始める。

2回目のretの直前、スタックのトップのアドレスはESP=16+leaked_ESPである。
retで読み込まれるのは,ESP=16+leaked_ESPから4バイト (ESP=16+leaked_ESP   〜 20+leaked_ESP)である。

つまり、sys_readではアドレスleaked_ESPから16-(-4) = 20バイト、ぶん無駄に書き込み、そのあとに4バイトぶん(開始位置はESP=16 + leaked_ESPで、ESP=20 + leaked_ESPまで)、2回目のretで使うリターンアドレスを書き込む必要がある。そのあとにシェルコードを書き込む。

書き込むリターンアドレスというのは、そのアドレスが書き込まれている場所の次から始まるシェルコードの先頭アドレスであるから、20 + leaked_ESPを指定すれば良い。

これがリークしたスタックアドレスの20バイト先を、リターンアドレスとして指定する理由である。

このへん厳密に計算するのが面倒なら適当にNOPで埋めてジャンプすれば良い…と思う。試してないけど。
(本当は、たくさんのNOPを使うのは、アドレスのランダマイズが有効な時にたくさん注入して適当にジャンプして成功を祈るシェルコード(Heap spray攻撃とか)のわかりやすい特徴なので、ホントに攻撃に使う場合は検知されやすいかもしれない。NOPの代わりに、XORなど結局何もしないけどNOPとは違う系の命令を入れるという手もある。)

さてシェルコード自体はどうするか。

http://hackoftheday.securitytube.net/2013/04/demystifying-execve-shellcode-stack.html
に詳しく書いてある。

シェルを起動する常套手段として、sys_execveを呼びたい。
システムコール番号は11。
execve関数は第一引数が実行するプログラム名、第二引数以降が実行するプログラムに渡す引数の配列となる。この配列は可変長なので最後にnullを入れないといけない。そのあとに環境変数へのポインタの配列。やっぱりこっちも最後はnull。

よってEAX = 11、EBX = (実行するコードの文字列の先頭アドレス、文字列の終わりはちゃんと0x00にする)、ECX=(実行するコードの文字列の先頭アドレスへのアドレス(ポインタなので)、nullと並ぶ配列の先頭アドレス)、EDXにはnull(= 0x00000000)を入れてINT 80hを実行すれば良いようだ。

適当にスタック上にnullや文字列や、そのアドレスを配置し、それらを指すアドレスを各レジスタに格納する。参考URLに詳しく書いてある。

EBXに入れるの は0x6e69622f68732f2fへのアドレスで、バイトオーダーがリトルエンディアンなのに気を付けてASCIIでデコードすると”/bin//sh”となるようだ。余計なスラッシュ”/”が付いているが、これはぴったり8バイトにしてアラインメントを維持するためのものらしい。いやな場合はスペースか何か入れれば良いと思う。さっきも言ったように末尾に0x00をつけてシメなくてはならない。(文字列はヌル文字で終端)

以上が手順の解説で、実際のエクスプロイトコードは貼ったリンクなどを参照して下さい。

なお、このようにスタック上のコードを実行できるとこのような条件ではぽんぽんエクスプロイトされるのでスタック上のコードは実行不可属性(NXビットが1)になってる、つまり実行できないようになっていたりする。今回はそうでないのでできるけど。