対象読者: C言語の基本とバイナリ・エンディアン・ビット幅の基礎知識がある方
GNU poke 4.3 時点の情報です。
公式マニュアル: https://jemarch.net/poke-4.3-manual/poke.html
- インストール・起動
- REPLの基本操作(ドットコマンド)
- dump:バイナリを眺める
- 数量と単位(United Values)
- マッピング:バイト列に型を割り当てる
- プリミティブ型一覧
- 配列・文字列
- 構造体(struct)の定義とマッピング
- 書き込み・値の変更
- IOスペース
- Poke言語:変数・制御フロー・関数
- pickle(.pk スクリプト)
- 応用:structの高度な機能
- tips・注意事項
sudo apt install pokeソースからビルドする場合は以下のパッケージが必要です:
sudo apt install libgc-dev gettext libnbd-dev libjson-c-dev tcl-dev tk-dev
wget https://ftp.gnu.org/gnu/poke/poke-4.3.tar.gz
tar xf poke-4.3.tar.gz && cd poke-4.3
./configure && make && sudo make installpoke # ファイルなしで起動(空の状態)
poke hoge.bin # ファイルを開いて起動
poke -L script.pk # スクリプトをバッチ実行して終了(shebang用)poke の REPL では「ドットコマンド(.xxx)」と「Poke言語式」の2種類を入力します。
| コマンド | 説明 |
|---|---|
.help |
ヘルプ表示 |
.exit / .quit |
poke を終了 |
.file <path> |
ファイルを開く(新しい IOスペースとして追加) |
.mem <name> |
メモリバッファの IOスペースを作成 |
.ios <id> |
カレント IOスペースを切り替える |
.info ios |
開いている IOスペース一覧を表示 |
.close <id> |
IOスペースを閉じる |
.load <file.pk> |
pickle(.pkスクリプト)を読み込んで実行 |
.set obase 16 |
出力の基数を変更(2, 8, 10, 16) |
.set endian big |
デフォルトエンディアンを変更(little / big) |
.set pretty-print yes |
構造体をきれいに表示 |
.info types |
定義済み型の一覧 |
.info variables |
定義済み変数の一覧 |
.doc <name> |
型・関数のドキュメントを表示 |
(poke) dump
デフォルトで先頭 128 バイトを hex + ASCII で表示します。
76543210 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 01234567 89ABCDEF
00000000: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 .ELF.... ........
- 左列:ファイルオフセット(16進)
- 中央列:各バイトの16進数
- 右列:ASCII表現(非印字文字は
.)
(poke) dump :from 0x10#B :size 32#B # オフセット 0x10 から 32 バイト
(poke) dump :from 0x50#B :size 16#B # オフセット 0x50 から 16 バイト
(poke) dump :ruler 0 # ルーラーを非表示
(poke) dump :ascii 0 # ASCII 列を非表示
オプションは :名前 値 の形式で渡します。
poke の独自機能。値に「単位」を付けて表現します。
<数量>#<単位>
| 表記 | 意味 |
|---|---|
1#b |
1 ビット |
1#N |
1 ニブル(4 ビット) |
1#B |
1 バイト(8 ビット) |
1#KB |
1 キロバイト(1000 B) |
1#KiB |
1 キビバイト(1024 B) |
1#MB |
1 メガバイト |
1#Mib |
2 メビビット |
(poke) 16#B # 16 バイト
0x10#B
(poke) 1#B + 4#b # 1 バイト + 4 ビット = 12 ビット
12#b
(poke) 1#KB / 1#B # 1 キロバイト ÷ 1 バイト = 1000(無次元数)
1000
(poke) 1#B * 1024 == 1#KiB
1 # 真(true = 1)
(poke) unit sector = 512 * 8 # 512 バイト = 1 セクター
(poke) 2#sector # 2 セクター分のオフセット
バイナリの「ある位置」に「ある型」を割り当てて読み出す操作を マッピング と呼びます。
<型> @ <オフセット>
(poke) byte @ 0#B # 0バイト目を 1 バイト(uint8)として読む
0x7fUB
(poke) int @ 4#B # 4バイト目から int32 として読む
0x00010001
(poke) uint<16> @ 0x10#B # 0x10バイト目を 16 ビット符号なし整数として読む
0x003eUH
マッピングはデータを読むだけでなく、そのまま書き込む左辺値にもなります(後述)。
uint<N> # 符号なし整数(N ビット)
int<N> # 符号あり整数(N ビット)
N は 1〜64 の任意の値が指定できます。
| エイリアス | 実体 | C での対応 |
|---|---|---|
bit |
uint<1> |
_Bool |
nibble |
uint<4> |
— |
byte, char, uint8 |
uint<8> |
uint8_t / unsigned char |
ushort, uint16 |
uint<16> |
uint16_t |
short, int16 |
int<16> |
int16_t |
uint, uint32 |
uint<32> |
uint32_t |
int, int32 |
int<32> |
int32_t |
ulong, uint64 |
uint<64> |
uint64_t |
long, int64 |
int<64> |
int64_t |
| 接尾辞 | 型 |
|---|---|
0x7fUB |
unsigned byte(uint8) |
0x003eUH |
unsigned half-word(uint16) |
0x00000001U |
unsigned word(uint32) |
0x00000001UL |
unsigned long(uint64) |
(poke) type int3 = int<3> # 3ビット符号あり整数
(poke) type myword = uint<32> # uint32 の別名
<要素型>[<個数>] @ <オフセット>
(poke) byte[4] @ 0#B # 先頭 4 バイトを byte 配列として読む
[0x7fUB, 0x45UB, 0x4cUB, 0x46UB]
(poke) char[3] @ 0x52#B # 0x52 バイト目から 3 文字
[0x47UB, 0x4eUB, 0x55UB] # "GNU"
(poke) string @ 0x4c#B
"GCC: (GNU) 15.2.1 20260103"
string 型は C の char*(NULL 終端)と同じ概念です。
NULL 文字 (\x00) が現れるまでを1つの文字列として読みます。
type <型名> = struct {
<型> <フィールド名>;
...
};
(poke) type Elf64_Ehdr = struct {
byte[16] e_ident;
uint<16> e_type;
uint<16> e_machine;
uint<32> e_version;
uint<64> e_entry;
uint<64> e_phoff;
uint<64> e_shoff;
uint<32> e_flags;
uint<16> e_ehsize;
uint<16> e_phentsize;
uint<16> e_phnum;
uint<16> e_shentsize;
uint<16> e_shnum;
uint<16> e_shstrndx;
}
(poke) var ehdr = Elf64_Ehdr @ 0#B
(poke) ehdr.e_machine # フィールドアクセス
0x003eUH # x86-64
(poke) ehdr.e_shnum
0x000cUH # 12 セクション
@ <offset> をフィールドの後ろに付けると、構造体先頭からの相対オフセットを指定できます。
(poke) type CoffHdr = struct {
uint<16> NumberOfSections @ 0x2#B;
uint<16> SizeOfOptionalHeader @ 0x10#B;
byte _end @ (0x14 - 1)#B; # サイズ調整用ダミー
}
type SECTION_HDR = struct { ... byte _ @ (0x28 - 1)#B; };
# SECTION_HDR を単位として配列インデックスを計算
var s = SECTION_HDR @ base + i#SECTION_HDR;
⚠️ 警告:変更はファイルに即時反映されます。undo はありません。
必ず事前にバックアップを取るか、メモリバッファ(.mem)で試してください。
(poke) byte @ 0#B = 0x41 # 0バイト目を 0x41 ('A') に書き換え
(poke) uint<16> @ 0#B = 0x4f47 # 0〜1 バイト目を "GO" に書き換え
プリミティブ型の @ は「値のコピー」を返します。変数を書き換えても IOスペースには反映されません。
(poke) var n = int @ 0#B # コピーを取得
(poke) n = n + 1 # 変数だけ変わる(ファイルは変わらない)
(poke) int @ 0#B = n # 明示的に書き戻す
struct などの複合型は「マップド値」を返します。フィールドへの代入が そのまま IOスペースに反映 されます。
(poke) var hdr = Elf64_Ehdr @ 0#B # マップド値
(poke) hdr.e_type = 0x0002UH # → ファイルの該当バイトが即書き換わる!
poke はファイルやメモリを「IOスペース」として抽象化します。マッピング操作は常に「現在のカレント IOスペース」に対して行われます。
(poke) .file /path/to/file # ファイルを開く(read-write)
(poke) .file /bin/ls # 読み取り専用で開く
(poke) .mem scratch # メモリバッファを作成(名前は任意)
(poke) .info ios # 一覧表示(* がカレント)
(poke) .ios 0 # ID=0 に切り替え
(poke) .close 1 # ID=1 を閉じる
Id Type Mode Bias Size Name
* 1 MEMORY rw 0x00000000#B 0x00001000#B *scratch*
0 FILE rw 0x00000000#B 0x000004c0#B /tmp/hoge.bin
copy :from_ios 0 :from 0#B :size iosize(0) :to_ios 1
| オプション | 説明 |
|---|---|
:from_ios |
コピー元 IOスペース ID |
:from |
コピー元開始オフセット |
:size |
コピーサイズ |
:to_ios |
コピー先 IOスペース ID |
:to |
コピー先開始オフセット(省略時 = 0) |
save :ios 1 :from 0#B :size iosize(0) :file "/path/to/output.bin"
(poke) iosize(0) # ID=0 の IOスペースのサイズを返す
0x000004c0#B
poke の REPL は Poke 言語のインタープリタです。変数・ループ・条件分岐・関数をそのまま書けます。
(poke) var x = 42 # 型推論
(poke) var s = "hello"
(poke) var arr = [1, 2, 3]
(poke) 10 + 3 # 13
(poke) 0x0f & 0x07 # AND → 7
(poke) 1 << 4 # シフト → 16
(poke) 5 == 5 # 1(true)
(poke) 5 != 3 # 1(true)
if (ehdr.e_machine == 0x3e) {
printf "x86-64\n";
} else {
printf "other arch\n";
}
// for-in(コレクション走査)
for (c in arr) { printf "%d\n", c; }
// C スタイル for
for (var i = 0; i < 10; ++i) { printf "%d\n", i; }
// while
var i = 0;
while (i < 5) { printf "%d\n", i; ++i; }
fun greet = (string name) void:
{
printf "Hello, %s!\n", name;
}
fun add = (int a, int b) int:
{
return a + b;
}
(poke) greet ("poke")
Hello, poke!
(poke) add (3, 4)
7
| 指定子 | 説明 |
|---|---|
%d |
10 進数(符号あり) |
%u |
10 進数(符号なし) |
%x |
16 進数 |
%s |
文字列 |
%c |
文字(char) |
%v |
値の型付き表示(デバッグに便利) |
%i8b |
8 ビット 2 進数 |
(poke) printf "%v\n", ehdr.e_type
0x0001UH
(poke) printf "%i8b\n", 0xa8UB
10101000B
// dump_section.pk
// 型定義
type MyHdr = struct {
uint<32> magic;
uint<16> version;
};
// メイン処理(ファイル読み込み時に自動実行)
var hdr = MyHdr @ 0#B;
printf "magic: 0x%08x, version: %d\n", hdr.magic, hdr.version;
(poke) .load dump_section.pk
#!/usr/bin/env poke -L
// このファイル単体でコマンドとして動くchmod +x analyze.pk
./analyze.pk target.bin(poke) load elf # ELF 解析用 pickle を読み込む
(poke) var ehdr = Elf64_Ehdr @ 0#B
poke に付属する主な pickle:
| pickle | 対象フォーマット |
|---|---|
elf |
ELF(実行ファイル・オブジェクトファイル) |
pcap |
libpcap パケットキャプチャ |
jpeg |
JPEG 画像 |
mp3 |
MP3 ID3 タグ |
mbr |
MBR パーティションテーブル |
フィールドの値に制約を設けます。制約違反時はエラーになります。バリデーションや optional フィールドの実現に使えます。
type Elf64_Ehdr = struct {
byte[4] e_magic : e_magic == [0x7f, 'E', 'L', 'F']; // マジック検証
byte e_class;
...
};
メソッドで計算された値をフィールドのように参照できます。
type SECTION = struct {
byte[8] Name;
...
computed string NameStr;
method get_NameStr = string:
{
var s = "";
for (c in Name) {
if (c == 0x00) { break; }
s += c as string;
}
return s;
}
};
(poke) sec.NameStr # → ".text"
type MyStruct = struct {
uint<32> value;
method double = uint<32>:
{
return value * 2;
}
};
(poke) var s = MyStruct @ 0#B;
(poke) s.double
複数のフィールドが同じオフセットを共有します。
type IPv4OrIPv6 = struct {
pinned;
byte[4] as_v4;
byte[16] as_v6;
};
条件に応じて1つのフィールドだけがデコードされます。TLV 形式などに有用です。
type Payload = union {
uint<8> as_byte : size == 1#B;
uint<16> as_short : size == 2#B;
uint<32> as_int : size == 4#B;
};
offset フィールドに型を指定し、ポインタのように参照を辿れます。
type DOS = struct {
offset<uint<32>, B, NT> e_lfanew @ 0x3c#B; // NT 型へのオフセット
};
var dos = DOS @ 0#B;
var nt = dos.e_lfanew'ref; // NT 型のマップド値として取得
変更はファイルに即時反映されます。必ずバックアップを取ってから編集しましょう。
cp target.bin target.bin.bak
poke target.bin(poke) .mem scratch
(poke) copy :from_ios 0 :from 0#B :size iosize(0) :to_ios 1 # ファイルを scratch にコピー
# scratch 上で自由に編集...
(poke) save :ios 1 :from 0#B :size iosize(0) :file "/tmp/output.bin"
(poke) .set endian big # ビッグエンディアン
(poke) .set endian little # リトルエンディアン(デフォルト)
(poke) 0x47UB as int<32> # 型変換
(poke) 0x47UB as string # "G"(1 文字の文字列に変換)
# 先頭 4 バイトをマジックナンバーとして確認
(poke) char[4] @ 0#B
# 任意オフセットの uint32 を読む
(poke) uint<32> @ 0x3c#B
# ELF のマシンアーキテクチャを確認
(poke) uint<16> @ 18#B
# 特定バイトを 16 進で上書き
(poke) byte @ 0x10#B = 0xde
# ファイルサイズを確認
(poke) iosize(0)
| リソース | URL |
|---|---|
| 公式サイト | https://www.jemarch.net/poke |
| 公式マニュアル(最新) | https://jemarch.net/poke-4.3-manual/poke.html |
| GNU サイト | https://www.gnu.org/software/poke/ |
| バグ報告 | https://sourceware.org/bugzilla/ |
| 開発メーリングリスト | poke-devel(GNU Savannah) |
| IRC | #poke @ Libera.Chat |
このチートシートは GNU poke 4.3 時点の情報をもとに作成しました。