64ビットのシェルコードがどのように機能するかを理解するために、私はこれに従います チュートリアル 。
だから、私はこのシェルコードをコーディングし、それは動作します:
BITS 64
xor rax, rax
mov qword rbx, '//bin/sh'
shr rbx, 0x8
Push rbx
mov rdi, rsp
Push rax
mov rdx, rsp
Push rdi
mov rsi, rsp
mov al, 59
syscall
2つの指示を除いて、このコードのすべてを理解しています。
mov qword rbx, '//bin/sh'
shr rbx, 0x8
Qwordがサイズ64ビットのデータであることを理解していますが、qwordを削除するとシェルコードが機能しません。 qwordを追加する必要があるのはなぜですか?
そして、命令shrの後、私の文字列は '%00 // bin/s'のようになりますか? shrの代わりにshlを使用しないのはなぜですか?
Qwordがサイズ64ビットのデータであることを理解していますが、qwordを削除するとシェルコードが機能しません。 qwordを追加する必要があるのはなぜですか?
Qword定数を移動しているからです。 '//bin/sh'
と入力すると、8文字の配列を表します。 qwordを前に付けることで、右側のオペランドをインライン64ビット整数として扱うようにアセンブラに指示しています。デフォルトでは、おそらくそれをその文字列へのポインタまたはその他の動作として扱います。
そして、命令shrの後、私の文字列は '%00 // bin/s'のようになりますか?
X86アーキテクチャプロセッサの整数は little-endian として格納されます。コンパイラが文字どおりに解釈した場合、文字列は次のようにASCIIでエンコードされます。
/ / b i n / s h
2f 2f 62 69 6e 2f 73 68
これは、次の指示でも表示できます。
mov rbx, 0x2f2f62696e2f7368
これは次のようにエンコードします。
48 BB 68 73 2F 6E 69 62 2F 2F
これを3つのセクションに分割できます。
48 BB 68 73 2F 6E 69 62 2F 2F
| | |---------------------|
\ \ literal
64-bit B8 = MOV instr
prefix + reg operand
+ r/m operand
この場合、mov命令バイト(BB
)には、移動先のレジスタ(rbx
)と右側のオペランドのタイプ(リテラル)を説明する7ビットフィールドも含まれています。 。
しかし注意してください:リテラルはではありません上記のASCII表現と同じ順序です。代わりにリトルエンディアンのために逆転しました。
もちろん、ここでの問題は、シェルコードでこれが発生する可能性がないことです。名前がバイトで逆になっている場合、呼び出す関数はhs/nib//
を参照して失敗します。代わりに、アセンブラは、これらのASCII文字をバイト順で必要とすることを認識するのに十分スマートです。つまり、実際に次の命令をエンコードします。
mov rbx, 0x68732f6e69622f2f
これにより、次のエンコードされた命令が生成されます。
48 BB 2F 2F 62 69 6E 2F 73 68
ご覧のとおり、//
はバイト順表現で最初(2F 2F
)になります。
Shrの代わりにshlを使用しないのはなぜですか?
あなたは両方を行います。コードには、チュートリアルで確認できる命令がありません。 shl rbx, 0x8
の直後にshr rbx, 0x8
が続きます。
彼らの説明はそれを正当化します:
0x08を左にシフトした後(文字列内の各数値プレースホルダーを4と考えると、8バイト移動すると、実際には2つのスペースが移動します)、11が最後から押し出されます。今、これがあります:
0x68732f6e69622f00
次に、同じ値でshrを使用して以前のように戻し、これを取得します。
0x0068732f6e69622f
これにより、マシンコードで単一のnullbyteを生成することなく、nullbyteで終了する文字列が得られます。
最初のスラッシュは、shl
の間に破棄される単なる犠牲文字です。
最初にnullをロードするか、右端のバイトをゼロでマスクする代わりにこのトリックを使用する理由は、エクスプロイトペイロードにnullバイトを必要としないためです。これは、nullの後に入力が無視される場合(たとえば、文字列はstrcpyでコピーされています)。