srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

自己書き換えコード(self-modifying code)

自己書き換えコードとは, 実行時に自分自身の命令を書き換えるコードのことである.
以下のコードでは, foo() 関数の i++ の命令を i += 2に自己書き換えしている.
順に説明していくと, まず mprotect() 関数でfoo関数の命令が書かれているページに読み, 書き, 実行の権限を与える. foo() 関数を書き換えるので, 書き換え権限を与えなければならない. 通常セキュリティーの観点から関数などのコードが書かれている領域は書き込み権限がない.
次に, 普通に foo() を呼び出した後, foo() 関数の先頭から 18 byte進んだ位置のbyteに 0x2 を書き込んでいる. これは foo()アセンブラを見ればわかるように, 18 byteのところが add DWORD PTR [rbp-0x4],0x10x1 に対応しているので, そこを 0x2 に書き換えている.
これで自己書き換えができたので, その後に foo を呼び出すと出力結果が変わる.

プログラム

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

void foo() {
    int i = 0;
    i++;
    printf("i: %d\n", i);
}

/*
000000000000072a <foo>:
 72a:  55                      push   rbp
 72b:  48 89 e5                mov    rbp,rsp
 72e:  48 83 ec 10             sub    rsp,0x10
 732:  c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
 739:  83 45 fc 01             add    DWORD PTR [rbp-0x4],0x1
 73d:  8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
 740:  89 c6                   mov    esi,eax
 742:  48 8d 3d 4b 01 00 00    lea    rdi,[rip+0x14b]        # 894 <_IO_stdin_used+0x4>
 749:  b8 00 00 00 00          mov    eax,0x0
 74e:  e8 8d fe ff ff          call   5e0 <printf@plt>
 753:  90                      nop
 754:  c9                      leave  
 755:  c3                      ret    
*/

int main() {
    int page_size = getpagesize();
    void *foo_addr = (void*)foo;
    printf("foo_addr:  %p\n", foo_addr);
    void* page_addr = (void*)((unsigned long)(foo_addr) & ~(page_size - 1));
    printf("page_addr: %p\n", page_addr);

    mprotect(page_addr, page_size, PROT_READ|PROT_WRITE|PROT_EXEC);

    printf("Call original foo()\n");
    foo();

    // Change code
    // add    DWORD PTR [rbp-0x4],0x1 => add    DWORD PTR [rbp-0x4],0x2
    ((unsigned char*)foo_addr)[18] = 0x2;

    printf("Call modified foo()\n");
    foo();

    return 0;
}

出力結果

foo_addr:  0x555992c1672a
page_addr: 0x555992c16000
Call original foo()
i: 1
Call modified foo()
i: 2