CTFのwrteup等を読んでいるとシェルコードやバイナリの読み込み、書き出しに
pythonのstructモジュールを利用しているケースが多く、
自分は使ったことがなかったので、調べてまとめてみました。
structモジュールとは
このモジュールは、Python の値とPython上で文字列データとして表される
Cの構造体データとの間の変換を実現します。このモジュールでは、C構造体の
レイアウトおよびPythonの値との間で行いたい変換をコンパクトに表現するために
フォーマット文字列を使います。このモジュールは特に、ファイルに保存されたり
ネットワーク接続を経由したバイナリデータを扱うときに使われます。
なにやら書いてありますが、すんごくざっくり言うと、
- 文字列(や数値) -> バイナリ (pack)
- バイナリ -> 文字列(や数値) (unpack)
を行ってくれるモジュールらしいです。
基本的な使い方
よく使うメソッドは2つだけ!(全部で3つしかないっぽいけど)
1 2 3 4 |
import struct struct.pack(フォーマット, 値) struct.unpack(フォーマット, 値) |
フォーマットには
- pack() -> 値をどのようなデータ型と評価してバイナリに変換するか
- unpack() -> バイナリをどのようなデータ型に変換するか
をそれぞれ指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import struct def main(): print "main" binary_data_int = 'xFFxFF' binary_data_char = 'x41' binary_data_string = 'x61x62x63' print struct.unpack("H", binary_data_int) print struct.unpack("h", binary_data_int) print struct.unpack('c', binary_data_char) print struct.unpack("3s", binary_data_string) c_data_int_1 = 65535 c_data_int_2 = -1 c_data_char = 'A' c_data_string = "abc" print repr( struct.pack("H", c_data_int_1) ) print repr( struct.pack("h", c_data_int_2) ) print repr( struct.pack("c", c_data_char) ) print repr( struct.pack("3s", c_data_string) ) if __name__ == '__main__': main() """ output: main (65535,) (-1,) ('A',) ('abc',) 'xffxff' 'xffxff' 'A' 'abc' """ |
こんな感じ。フォーマットの記述方法は後ほど。
フォーマット指定
format | c data | python | size |
---|---|---|---|
pad | bbyte | no value | |
c | char | 長さ1の文字列 | 1 |
b | signed char | 整数型 | 1 |
B | unsigned char | 整数型 | 1 |
? | _Bool | 真偽値型 | 1 |
h | short | 整数型 | 2 |
H | unsigned short | 整数型 | 2 |
i | int | 整数型 | 4 |
I | unsigned int | 整数型 | 4 |
l | long | 整数型 | 4 |
L | unsigned long | 整数型 | 4 |
q | long long | 整数型 | 8 |
Q | unsigned long long | 整数型 | 8 |
f | float | 浮動小数点型 | 4 |
d | double | 浮動小数点型 | 8 |
s | char[] | 文字列 | |
p | char[] | 文字列 | |
P | void* | 整数型 |
この表を見ると、Cのデータ型とpythonのデータ型の相互変換に
利用できる、ということがわかりますね。
(単純にバイナリと値の相互変換というだけでなく)
文字列指定をするときはsやpの前に文字数を指定してやる必要が
あります。指定しないと1文字以上の文字列はエラーになっちゃいます。
文字列のサイズより大きな数字を指定したときは
nullバイトで埋められます。
フォーマット文字列の前に整数をつけると、繰り返しを表現できます。
ex) ‘4h’ == ‘hhhh’
バイトオーダー
リトルエンディアンとか、ビックエンディアンとか。
format | byte order | size | alignment |
---|---|---|---|
@ | native | native | native |
= | native | standard | none |
< | little endian | standard | none |
> | big endian | standard | none |
! | network | standard | none |
指定がなければ@が利用され、ホストPCの環境にあわせてオートで
やってくれる。
対象のシステムでのエンディアンはsys.byteorderで調べることができます。
1 2 3 4 5 6 |
import sys print sys.byteorder """ output little """ |
‘!’ 表記法はネットワークバイトオーダがビッグエンディアンかリトルエンディアンか忘れちゃったという熱意に乏しい人向けに用意されています。
python structモジュール 公式
だそうです。ありがとうございますありがとうございます。
アラインメントに関しては、僕も後で読む用に
検証コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import sys from struct import * print sys.byteorder print repr( pack('i', 65) ) print repr( pack('hh', 1,2) ) print repr( pack('l', 3) ) print repr( pack('hhl', 1,2,3) ) print repr( pack('h', 2) ) print repr( pack('5s', 'hello') ) print repr( unpack('3s', 'x45x46x47') ) print repr( unpack('i', 'x65x00x00x00') ) print repr( unpack('c', 'x61') ) print unpack('i', 'xffxffxffxff') print unpack('I', 'xffxffxffxff') print unpack('h', 'xffxff') print unpack('H', 'xffxff') print repr( unpack('3s', 'x45x46x47') ) data = '0x809F23' print repr( pack("<l ", int(data, 16) +8 ) ) print repr( pack("<L",0x804cff4) ) |
参考になりました。ありがとうございます。
h short 整数型 1
なのですが、sizeは2ではないでしょうか。
https://msdn.microsoft.com/ja-jp/library/s3f49ktz.aspx
ありがとうございます!修正しました!
私の環境だと、
In [7]: print(struct.pack(‘5s’, ‘hello’))
—————————————————————————
error Traceback (most recent call last)
in ()
—-> 1 print(struct.pack(‘5s’, ‘hello’))
error: argument for ‘s’ must be a bytes object
とエラーが出ました。
In [8]: print(struct.pack(‘5s’, ‘hello’.encode(‘utf-8′)))
b’hello’
としたら解決できました。結構はまったので、備忘録としてここに記載させていただきました。