0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

x86-64 Linuxで32ビットシステムコールを追う: INT 0x80のIDTハンドラを解析する

Posted at

初めに

x86のLinux32ビットのシステムコール呼び出しにはINT 0x80が使われていました。
筆者は32ビットの端末は所有していないため、64ビットのLinuxから32ビットのプログラムを動かし、INT命令がどのような仕組みになっているのかを調べます。

test@test-ThinkPad-X280:~/test/nasm$ nasm -f elf32 test.asm && ld -m elf_i386 test.o -o test
test@test-ThinkPad-X280:~/test/nasm$ ./test 
Hello, int80!
test.asm
section .data
msg db "Hello, int80!", 0xA
len equ $ - msg

section .text
global _start

_start:
    mov eax, 4      ; sys_write
    mov ebx, 1      ; stdout
    mov ecx, msg
    mov edx, len
    int 0x80

    mov eax, 1      ; sys_exit
    xor ebx, ebx
    int 0x80

IDTについて

INT0x80の割り込みは割り込み記述子(IDT)の128(=0x80)番目に登録されている。
先ずはIDTが保存されている先頭アドレス及び、全体の大きさを調べます。

ユーザモード(Ring 3)では取得ができないため、カーネルモード(Ring 0)で調べます。

Makefile.
obj-m := idt_show.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
idt_show.c
#include <linux/module.h>
#include <asm/desc.h>

static int __init idt_show(void)
{
    struct desc_ptr idtr;
    store_idt(&idtr);
    pr_info("IDT base = 0x%lx, limit = 0x%x\n",
            (unsigned long)idtr.address, idtr.size);
    return 0;
}

static void __exit idt_exit(void)
{
    pr_info("idt_show unloaded\n");
}

module_init(idt_show);
module_exit(idt_exit);
MODULE_LICENSE("GPL");

test@test-ThinkPad-X280:~/test/nasm2$ make
make -C /lib/modules/6.8.0-85-generic/build M=/home/test/test/nasm modules
make[1]: 进入目录“/usr/src/linux-headers-6.8.0-85-generic”
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04.2) 12.3.0
  You are using:           gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04.2) 12.3.0
  CC [M]  /home/test/test/nasm/idt_show.o
  MODPOST /home/test/test/nasm/Module.symvers
  CC [M]  /home/test/test/nasm/idt_show.mod.o
  LD [M]  /home/test/test/nasm/idt_show.ko
  BTF [M] /home/test/test/nasm/idt_show.ko
Skipping BTF generation for /home/test/test/nasm/idt_show.ko due to unavailability of vmlinux
make[1]: 离开目录“/usr/src/linux-headers-6.8.0-85-generic”

test@test-ThinkPad-X280:~/test/nasm$ sudo insmod idt_show.ko

test@test-ThinkPad-X280:~/test/nasm$ sudo dmesg | tail -n 3
[ 9440.007193] idt_show: loading out-of-tree module taints kernel.
[ 9440.007199] idt_show: module verification failed: signature and/or required key missing - tainting kernel
[ 9440.009049] IDT base = 0xfffffe0000000000, limit = 0xfff

test@test-ThinkPad-X280:~/test$ sudo rmmod show_int80_handler

上で調べたところIDTの先頭アドレスは0xfffffe0000000000
大きさは0x0fff= 4095。4096バイト = 256エントリ × 16バイト(64bit環境では)

よってIDTは以下のように並んでいる。

IDT base = 0xfffffe0000000000
                 ↓
0xfffffe0000000000 ─ [0x00]  Divide Error
0xfffffe0000000010 ─ [0x01]  Debug
...
0xfffffe0000000800 ─ [0x80]  INT 0x80  ←★ここ
...
0xfffffe0000000ff0 ─ [0xFF]  最後のエントリ

IDT[0x80] のアドレス
= IDT base + (割り込み番号 × 1エントリの大きさ)
= 0xfffffe0000000000 + (0x80 × 0x10)
= 0xfffffe0000000000 + 0x800
= 0xfffffe0000000800

INT0x80のアドレス

Linuxのカーネルを見ると1エントリはこのように16バイトになっている。
https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/desc_defs.h

desc_defs.h
//...(省略)...

struct idt_bits {
	u16		ist	: 3,
			zero	: 5,
			type	: 5,
			dpl	: 2,
			p	: 1;
} __attribute__((packed));

//...(省略)...

struct gate_struct {
	u16		offset_low;
	u16		segment;
	struct idt_bits	bits;
	u16		offset_middle;
#ifdef CONFIG_X86_64
	u32		offset_high;
	u32		reserved;
#endif
} __attribute__((packed));

//...(省略)...

このエントリ内の情報を基に以下の式を用いて64ビットの仮想アドレスを組み立てられる。

handler_address = offset_low 
                | (offset_middle << 16) 
                | ((u64)offset_high << 32)
Makefile.
obj-m := show_int80_handler.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
show_int80_handler.c
#include <linux/module.h>
#include <asm/desc.h>

static int __init show_int80_handler(void)
{
    struct desc_ptr idtr;
    struct gate_struct *idt;

    // IDT base を取得
    store_idt(&idtr);
    idt = (struct gate_struct *)idtr.address;

    // INT 0x80 のハンドラアドレスを組み立てる
    u64 handler = (u64)idt[0x80].offset_low
                | ((u64)idt[0x80].offset_middle << 16)
                | ((u64)idt[0x80].offset_high << 32);

    pr_info("INT 0x80 handler: 0x%llx\n", handler);
    return 0;
}

static void __exit exit_func(void)
{
    pr_info("Module unloaded\n");
}

module_init(show_int80_handler);
module_exit(exit_func);
MODULE_LICENSE("GPL");
test@test-ThinkPad-X280:~/test/nasm2$ make
make -C /lib/modules/6.8.0-85-generic/build M=/home/test/test/nasm2 modules
make[1]: 进入目录“/usr/src/linux-headers-6.8.0-85-generic”
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04.2) 12.3.0
  You are using:           gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04.2) 12.3.0
make[1]: 离开目录“/usr/src/linux-headers-6.8.0-85-generic”

test@test-ThinkPad-X280:~/test/nasm2$ sudo insmod show_int80_handler.ko

test@test-ThinkPad-X280:~/test/nasm2$ sudo dmesg | tail -n 1
[13446.441809] INT 0x80 handler: 0xffffffffb6e00bd0
test@test-ThinkPad-X280:~/test/nasm2$ 

test@test-ThinkPad-X280:~/test$ sudo rmmod show_int80_handler

よってint 0x80の入り口のアドレスは以下だと分かる。(仮想アドレス)

INT 0x80 handler: 0xffffffffb6e00bd0

int 0x80冒頭をダンプしてみる

Makefile.
obj-m := show_int80_handler.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
dump_int80.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init dump_int80(void)
{
    unsigned char *p = (unsigned char *)0xffffffffb6e00bd0;
    int i;

    pr_info("INT 0x80 dump:\n");
    for (i = 0; i < 64; i++) {
        pr_cont("%02x ", p[i]);
        if ((i+1) % 16 == 0) pr_cont("\n");
    }
    return 0;
}

static void __exit dump_exit(void)
{
    pr_info("Module unloaded\n");
}

module_init(dump_int80);
module_exit(dump_exit);
MODULE_LICENSE("GPL");
test@test-ThinkPad-X280:~/test$ make
make -C /lib/modules/6.8.0-85-generic/build M=/home/test/test modules
make[1]: 进入目录“/usr/src/linux-headers-6.8.0-85-generic”
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04.2) 12.3.0
  You are using:           gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04.2) 12.3.0
  CC [M]  /home/test/test/dump_int80.o
  MODPOST /home/test/test/Module.symvers
  CC [M]  /home/test/test/dump_int80.mod.o
  LD [M]  /home/test/test/dump_int80.ko
  BTF [M] /home/test/test/dump_int80.ko
Skipping BTF generation for /home/test/test/dump_int80.ko due to unavailability of vmlinux
make[1]: 离开目录“/usr/src/linux-headers-6.8.0-85-generic”

test@test-ThinkPad-X280:~/test$ sudo insmod dump_int80.ko

test@test-ThinkPad-X280:~/test$ sudo dmesg | tail -n 5
[14456.175446] INT 0x80 dump:
[14456.175450] 0f 01 ca fc 6a ff e8 d5 09 00 00 48 89 c4 48 8d 
[14456.175465] 6c 24 01 48 89 e7 e8 c5 0f 00 00 e9 10 0b 00 00 
[14456.175479] 0f 01 ca fc 6a ff f6 44 24 10 03 75 12 e8 4e 08 
[14456.175492] 00 00 48 89 e7 e8 26 15 e2 ff e9 31 09 00 00 e8 


test@test-ThinkPad-X280:~/test$ sudo rmmod dump_int80

int0x80を逆アセンブルすると以下の通り

オフセット バイト列 アセンブリ命令 注釈
0x00 0f 01 ca swapgs 64bit専用、カーネルスタック切替用
0x03 fc cld 方向フラグクリア
0x04 6a ff push 0xFF
0x06 e8 d5 09 00 00 call 0x9D5 相対アドレス
0x0B 48 89 c4 mov rsp, rax
0x0E 48 8d 6c 24 01 lea rbp, [rsp+0x1]
0x13 48 89 e7 mov rdi, rsp
0x16 e8 c5 0f 00 00 call 0xFC5
0x1B e9 10 0b 00 00 jmp 0xB2B 相対ジャンプ
0x20 0f 01 ca swapgs カーネル用の再切替
0x23 fc cld
0x24 6a ff push 0xFF
0x26 f6 44 24 10 03 test BYTE PTR [rsp+0x10], 0x3
0x2B 75 12 jnz 0x2F テスト結果がゼロでなければジャンプ
0x2D e8 4e 08 00 00 call 0x84E
0x32 48 89 e7 mov rdi, rsp
0x35 e8 26 15 e2 ff call 0xFFE21561 相対アドレス
0x3A e9 31 09 00 00 jmp 0x970 相対ジャンプ
0x3F e8 call (途中) バイト列不足で未完

考察

INT 0x80は、**割り込み記述子(IDT)**の128番目のエントリ(0xfffffe0000000800)を参照します。
このエントリが指す実際のカーネル内の処理開始アドレスは、本環境では0xffffffffb6e00bd0でした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?