逆アセンブルで遊んでみる(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型配列が優先して高位アドレスに配置されるようです。