Intel Pinでlivestringsを作ってみる
「Intel Pinでcmptraceを作ってみる」では、x86/x64用のDBIツールであるIntel Pinを使ってcmp命令のトレースを行うコードを書いた。 ここでは、同様にIntel Pinを使い、メモリ上に展開されているASCII文字列を列挙するlivestringsを作ってみる。
環境
Ubuntu 14.04.4 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.4 LTS Release: 14.04 Codename: trusty $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4
なお、Intel Pinの実行ファイルが32 bit用バイナリであるため、ここでは「64 bitのUbuntu Linuxで32 bitの実行ファイルを動かす方法のメモ」に書いた方法で32 bit用バイナリも実行できるようにしてある。
プログラムコードを書いてみる
「Intel Pinを使ってみる」でも取り上げたpinatraceのコードを参考に、読み書きが行われたメモリアドレスにある4文字以上の文字列を表示するコードを書くと次のようになる。
// livestrings.cpp
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include "pin.H"
const int MIN_STR_LENGTH = 4;
int nullfd;
int IsValidPointer(char *addr)
{
if (write(nullfd, addr, 1) < 0) {
return 0;
}
return 1;
}
int GetPrintableLength(char *addr)
{
int i = 0;
while (IsValidPointer(&addr[i]) && isprint(addr[i])) {
i++;
}
return i;
}
VOID RecordMemRead(VOID *ip, char *addr)
{
int i;
int len = GetPrintableLength(addr);
if (len >= MIN_STR_LENGTH) {
for (i=0; i<len; i++) {
fprintf(stderr, "%c", addr[i]);
}
fprintf(stderr, "\n");
}
}
VOID RecordMemWrite(VOID *ip, char *addr)
{
int i;
int len = GetPrintableLength(addr);
if (len >= MIN_STR_LENGTH) {
for (i=0; i<len; i++) {
fprintf(stderr, "%c", addr[i]);
}
fprintf(stderr, "\n");
}
}
VOID Instruction(INS ins, VOID *v)
{
UINT32 memOperands = INS_MemoryOperandCount(ins);
for (UINT32 memOp = 0; memOp < memOperands; memOp++) {
if (INS_MemoryOperandIsRead(ins, memOp)) {
INS_InsertPredicatedCall(ins, IPOINT_BEFORE, (AFUNPTR)RecordMemRead, IARG_INST_PTR, IARG_MEMORYOP_EA, memOp, IARG_END);
}
if (INS_MemoryOperandIsWritten(ins, memOp)) {
INS_InsertPredicatedCall(ins, IPOINT_BEFORE, (AFUNPTR)RecordMemWrite, IARG_INST_PTR, IARG_MEMORYOP_EA, memOp, IARG_END);
}
}
}
int main(int argc, char *argv[])
{
PIN_Init(argc, argv);
nullfd = open("/dev/null", O_WRONLY);
INS_AddInstrumentFunction(Instruction, 0);
PIN_StartProgram();
return 0;
}
上のコードにおいて、RecordMemRead、RecordMemWrite関数は読み書きされたメモリアドレスの直後にあるASCII文字列を標準エラー出力に表示する関数である。 また、IsValidPointer関数は与えられたアドレスが読み取り可能なアドレスかどうかを返す関数である。
コードをコンパイルして実行してみる。
$ make livestrings.test TARGET=intel64 $ ../../../pin -t obj-intel64/livestrings.so -- /bin/ls 2>livestrings.log buffer_linux.cpp emudiv.cpp fork_jit_tool.cpp inscount_tls.cpp livestrings.log obj-intel64 stack-debugger-tutorial.sln thread_unix.c buffer_windows.cpp fibonacci.cpp imageload.cpp invocation.cpp makefile pinatrace.cpp stack-debugger-tutorial.vcxproj thread_win.c countreps.cpp follow_child_app1.cpp inscount0.cpp isampling.cpp makefile.rules proccount.cpp stack-debugger-tutorial.vcxproj.filters w_malloctrace.cpp detach.cpp follow_child_app2.cpp inscount1.cpp itrace.cpp malloc_mt.cpp replacesigprobed.cpp statica.cpp divide_by_zero_linux.c follow_child_tool.cpp inscount2.cpp little_malloc.c malloctrace.cpp safecopy.cpp staticcount.cpp divide_by_zero_windows.c fork_app.cpp inscount.out livestrings.cpp nonstatica.cpp stack-debugger.cpp strace.cpp $ head livestrings.log x86_64 x86_64 86_64 6_64 XDG_SESSION_ID=1 TERM=screen-256color-bce SHELL=/bin/bash SSH_CLIENT=192.168.56.1 50977 22 OLDPWD=/home/user/tmp SSH_TTY=/dev/pts/0
上の結果から、実行時にメモリ上に展開されているASCII文字列が出力されていることがわかる。
使い道について
たとえば、入力したパスワード文字列を何らかの文字列と比較し、認証の成否を判定する次のようなコードを考える。
/* test.c */
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[256];
char secret[] = "~bcyucyuyoixo~";
printf("Enter password: ");
scanf("%s", buf);
memfrob(secret, strlen(secret));
if (strcmp(buf, secret) == 0) {
puts("auth succeeded!");
} else {
puts("auth failed.");
}
return 0;
}
上のコードをコンパイルし、livestringsを適用してみると、実際に比較されている文字列が出力できていることがわかる。
$ gcc test.c -o test $ ../../../pin -t obj-intel64/livestrings.so -- ./test 2>livestrings.log Enter password: AAAA auth failed. $ cat livestrings.log x86_64 x86_64 86_64 6_64 (snip) AAAA THIS_IS_SECRET AAAA THIS_IS_SECRET (snip) $ ./test Enter password: THIS_IS_SECRET auth succeeded!