逆アセンブルで遊んでみる(8)long long型の自動変数
スタック上に確保した自動変数領域に、long long型(16バイト)のデータを格納する。です(エピローグとプロローグ、SSPのスタック破壊検知コードについては省略しています)。
1 void func() 2 { 3 long long n = 0x1234567890ABCDEF; 4 char str[] = "01234567"; 5 }
00000000 <func>: void func() { 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 28 sub $0x28,%esp 6: 65 a1 14 00 00 00 mov %gs:0x14,%eax c: 89 45 f4 mov %eax,-0xc(%ebp) f: 31 c0 xor %eax,%eax long long n = 0x1234567890ABCDEF; 11: c7 45 e0 ef cd ab 90 movl $0x90abcdef,-0x20(%ebp) 18: c7 45 e4 78 56 34 12 movl $0x12345678,-0x1c(%ebp) char str[] = "01234567"; 1f: c7 45 eb 30 31 32 33 movl $0x33323130,-0x15(%ebp) 26: c7 45 ef 34 35 36 37 movl $0x37363534,-0x11(%ebp) 2d: c6 45 f3 00 movb $0x0,-0xd(%ebp) } 31: 8b 45 f4 mov -0xc(%ebp),%eax 34: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 3b: 74 05 je 42 <func+0x42> 3d: e8 fc ff ff ff call 3e <func+0x3e> 42: c9 leave 43: c3 ret
自動変数の領域確保とカナリア値の埋め込み
3: 83 ec 28 sub $0x28,%esp 6: 65 a1 14 00 00 00 mov %gs:0x14,%eax c: 89 45 f4 mov %eax,-0xc(%ebp) f: 31 c0 xor %eax,%eax
自動変数の領域として、40バイトの空間を確保しています。また、-0x0C(%ebp)にカナリア値を埋め込み、媒介領域として使用したeaxレジスタを0に初期化しています。
ebp | offset | Stack | esp |
---|---|---|---|
-0x28 | (空き) | ← | |
-0x24 | (空き) | ||
-0x20 | (空き) | ||
-0x1C | (空き) | ||
-0x18 | (空き) | ||
-0x14 | (空き) | ||
-0x10 | (空き) | ||
-0x0C | ★カナリア値 | ||
-0x08 | (空き) | ||
-0x04 | (空き) | ||
→ | -0x00 | Saved EBP | |
+0x04 | Saved EIP | ||
+0x08 | … | ||
… | 呼出元関数のスタックベース |
long long型のデータの格納
long long n = 0x1234567890ABCDEF; 11: c7 45 e0 ef cd ab 90 movl $0x90abcdef,-0x20(%ebp) 18: c7 45 e4 78 56 34 12 movl $0x12345678,-0x1c(%ebp)
見ての通り、上位4バイト・下位4バイトに分割して、2回のmovl命令で値を格納しています。リトルエンディアンなので、アドレスとは逆順に値を格納しています。かつ、下位4バイトを低位アドレスに、上位4バイトを高位アドレスに格納しています。
この時点での、スタックの内容。
ebp | offset | Stack | esp |
---|---|---|---|
-0x28 | (空き) | ← | |
-0x24 | (空き) | ||
-0x20 | ★EF CD AB 90 | ||
-0x1C | ★78 56 34 12 | ||
-0x18 | (空き) | ||
-0x14 | (空き) | ||
-0x10 | (空き) | ||
-0x0C | カナリア値 | ||
-0x08 | (空き) | ||
-0x04 | (空き) | ||
→ | -0x00 | Saved EBP | |
+0x04 | Saved EIP | ||
+0x08 | … | ||
… | 呼出元関数のスタックベース |
char型配列のデータの格納
char str[] = "01234567"; 1f: c7 45 eb 30 31 32 33 movl $0x33323130,-0x15(%ebp) 26: c7 45 ef 34 35 36 37 movl $0x37363534,-0x11(%ebp) 2d: c6 45 f3 00 movb $0x0,-0xd(%ebp)
char型配列に関しても、上位4バイト・下位4バイトに分割して、2回のmovl命令で値を格納しています。当たり前ですが、char型配列なので、整数型とは違い、アドレスと正順に格納されます。どーも、CPUはint型・char型を識別できないので、何でもかんでもリトルエンディアンを適用して、アドレスとは逆順のバイト配列でデータを格納するようです。それを逆手に取って、ニーモニックコードではオペランドを逆順に並べています(逆順の逆順で格納すると、正順に並ぶ)。
つぎに、null terminatorとして、movb命令で0x00を末尾に格納しています。
この時点での、スタックの内容。
ebp | offset | Stack | esp |
---|---|---|---|
-0x28 | (空き) | ← | |
-0x24 | (空き) | ||
-0x20 | EF CD AB 90 | ||
-0x1C | 78 56 34 12 | ||
-0x18 | ★__ __ __ 30 | ||
-0x14 | ★31 32 33 34 | ||
-0x10 | ★35 36 37 00 | ||
-0x0C | カナリア値 | ||
-0x08 | (空き) | ||
-0x04 | (空き) | ||
→ | -0x00 | Saved EBP | |
+0x04 | Saved EIP | ||
+0x08 | … | ||
… | 呼出元関数のスタックベース |
ところで、2つ以上の自動変数が存在する場合、定義された順番が早いほど、高位アドレスに格納される。と思っていましたが、カナリア値が埋め込まれる場合、カナリア値の直上にchar型配列の領域が配置されるように、char型配列が優先して高位アドレスに配置されるようです。