作成日:2002.7.14
デバッガやハッキングツールの 1種には実行中の外部プロセスの持っているメモリを読み書きするこのとできるものがある。 ここでは Windows / Linux / Solaris の 3 種類の OS で、外部プロセスからのメモリの読み込みを実現する方法を説明する。
重要な点を挙げると、
- Solaris と Windows では外部プロセス(この場合 java[.exe]) を 停止させることなくメモリイメージを覗くことが可能。
- Linux では標準の機能のみを使うと、ターゲットなるプロセスを 停止させずにメモリを読むことができない。 シグナルによって対象プロセスを止める必要がある。
- Linux でも kernel の loadable module を自前で書けば、 停止させずのメモリ覗きは可能。
Windows
Windows の場合には、プロセス ID から OpenProccess API によってプロセスハンドラを確保し、ReadProcessMemory/WriteProcessMemoryを用いて読み書きを行う。
ただし、Windows NT/2000系で厳密なアクセスコントロールが行われており通常のプロセスでは ReadProcessMemory/WritePorcessMemory を使うことができない。
これを可能にするには監視プロセス側のプロセスのアクセス権限のうち、debug privilege をオンにする必要がある。
bool enable_debug_privilege() { HANDLE hToken; LUID Value; TOKEN_PRIVILEGES tkp; if (!OpenProcessToken (GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { return false; } if (!LookupPrivilegeValue ((LPSTR)NULL, SE_DEBUG_NAME, &Value)){ return false; // error } tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = Value; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges (hToken, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL); if (GetLastError () != ERROR_SUCCESS) { return false; // error } CloseHandle( hToken ); return true; }
Solaris
Solaris で外部プロセスのメモリを覗く方法は 3 つある。
- ptrace API を用いる方法
- /proc を用いてプロセスの情報を取りだす方法。
- process service interfaces (proc_service(3PROC))
1. は監視プロセスを対象プロセスにアタッチしてから(この時点で対象プロセスが停止)、1 ワードづつメモリを読み込む方法。
2. と 3. の手法では実行中のプロセスを停止させずに、ターゲットプロセスのメモリを読み書きが可能。
2. はターゲットプロセスの ID を <pid> とすると、/proc/<pid>/as がプロセスのメモリイメージを保持している。 このファイルを open して後、lseek、read、write によって読み書きすることが可能。
<pid> のプロセスからメモリアドレス offset を読みこむコードは以下のようになる。
char proc_pid_mem[MAX_FILE_PATH]; sprintf(proc_pid_mem, "/proc/%d/as", pid); if( (fd = open(proc_pid_mem, O_CREAT | O_RDONLY)) != -1 ){ off_t p = lseek( fd, offset, SEEK_SET ); if( p == offset ){ size_t size = read(fd, &buffer, sizeof(buffer)); if( size == sizeof(buffer) ){ printf("%02x", buffer); } } }
3. は 2. と原理的同じ手法でデータを転送するが、/proc の下のファイルを操作する代わりに専用の API を用いる。
Linux
Linux は Solaris と同様に procfs 経由のメモリ参照が可能。
覗きたいプロセスの対象スレッドの PID を <pid> とすると、/proc/<pid>/memという仮想ファイルを経由することでアクセスが可能となる。
ただし、Linux kernel の制限上 ptrace API によって、監視対象となるプロセスにアタッチを行ってからでないと/proc/<pid>/memを読み書きできない。
そして ptrace によるアタッチには、ターゲットとなるプロセスのSIGSTOP シグナルによる一時停止が伴う。
つまり、Linux ではターゲットとなるプロセスを一時停止させないとメモリを参照することができない。
/proc/<pid>/mem はwrite の他にmmap によって監視プロセスのメモリ空間内にマッピングする機能が用意されているようだが、kernel のバージョンによっては問題があるようだ。
long result; char proc_pid_mem[MAX_FILE_PATH + 1]; // attach <pid> proccess ptrace(PTRACE_ATTACH, pid, NULL, NULL); #if defined(__READ_BY_PTRACE__) /* ptrace API を使ってメモリを読みこむ */ result = ptrace( PTRACE_PEEKDATA, pid, (void*)offset, NULL); printf("memory: %d\n", (int)result ); #else /* procfs 経由でメモリを読みこむ */ sprintf(proc_pid_mem, "/proc/%d/mem", pid); if( (fd = open(proc_pid_mem, O_RDWR)) >= 0 ){ off_t p = lseek( fd, offset, SEEK_SET ); if( p == offset ){ size_t size = read(fd, buffer, 4); if( size == 4){ for(int i=0 ; i<4; i++){ printf("%02x", buffer[i]); } printf("\n"); } } } #endif // detach the proccess ptrace(PTRACE_DETACH, pid, NULL, NULL);