ゆとりーなの日記

日記的な事を書いて行くと思はれる

PE勉強会(4)に参加してきた

 前から行こうと思って行けてなかったPE勉強会に遂に参加してきました。初参加でセッションなし且つみんなで開発や質問をする形式に耐えるのかという疑念はありましたがそれなりになんとかなったようです。
 VB.NETとか知らないので冷静に勝手にC++0xで遊んでました。前回の資料を漁っていたら、MSYSとか書いてあったのでGCCを使う空気を受信、しかしこの前バーション4.7に上げちゃったけど耐えるのかという疑惑が囁かれましたが耐えた模様です。
 取り敢えず今日の成果。

gccだと予想外に簡単にdllが作れる

gccならこんな適当なソースで作れるらしいです。

#include <iostream>

extern "C" {
void print_number(const int v) {
  std::cout << v << std::endl;
}
}
g++ -shared -o mydll.dll mydll.cc

VC++で悪さしてDLL作ったときは結構面倒だった記憶があった(defファイルとかなんかよく分からない識別子付けなきゃいけなかったりとかエントリポイントがDllMainだったりとか)のでこれはおいしいです。まぁVC++もオプションで制御できるんでしょうけど。あとC++でやる場合はextern "C"を付けないと名前にお飾りが付いてしまって後々残当するみたいですね。ここで大分嵌りました。

簡単なコンパイラ

LET A 1
LET B 2
ADD A B
DISP A

で3が出力されるようなものを取り敢えず作ってみました。出力には色々とよく分からなかったので先に作ったdllのprint_numberを呼ぶことで甘えました。割と適当なので悪しからず。

#include <cstdint>
#include <cstring>
#include <array>
#include <fstream>
#include <string>
#include <stdexcept>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/spirit/include/qi.hpp>
#include <Windows.h>

bool parse_token(const std::string &str, std::vector<std::string> &result) { 
  auto it = str.cbegin();
	return boost::spirit::qi::parse(it, str.cend(), +(boost::spirit::qi::char_ - ' ') % ' ', result) && it == str.cend();
}

template <typename Image>
void write_dos_header(Image &image) {
  auto desh = reinterpret_cast<IMAGE_DOS_HEADER *>(&image[0]);
  desh->e_magic = *reinterpret_cast<const WORD *>("MZ");
  desh->e_cblp = 0x90;
  desh->e_cp = 3;
  desh->e_cparhdr = 4;
  desh->e_maxalloc = 0xFFFF;
  desh->e_sp = 0xB8;
  desh->e_lfarlc = 0x40;
  desh->e_lfanew = 0x80;
  const BYTE kStub[] = {0xB8, 0x01, 0x4C, 0xCD, 0x21};
  std::memcpy(&image[0x40], kStub, sizeof(kStub));
}

template <typename Image>
void write_pe_header(Image &image) {
  auto peh = reinterpret_cast<IMAGE_NT_HEADERS32 *>(&image[0x80]);
  peh->Signature = *reinterpret_cast<const DWORD *>("PE\0\0");
  auto fh = &peh->FileHeader;
  fh->Machine = 0x14C;
  fh->NumberOfSections = 2;
  fh->SizeOfOptionalHeader = 0xE0;
  fh->Characteristics = 0x102;
  auto oh = &peh->OptionalHeader;
  oh->Magic = 0x10B;
  oh->MajorLinkerVersion = 10;
  oh->SizeOfCode = 0x200;
  oh->AddressOfEntryPoint = 0x1000;
  oh->BaseOfCode = 0x1000;
  oh->BaseOfData = 0x2000;
  oh->ImageBase = 0x400000;
  oh->SectionAlignment = 0x1000;
  oh->FileAlignment = 0x200;
  oh->MajorOperatingSystemVersion = 5;
  oh->MinorOperatingSystemVersion = 1;
  oh->MajorSubsystemVersion = 5;
  oh->MinorSubsystemVersion = 1;
  oh->SizeOfImage = 0x3000;
  oh->SizeOfHeaders = 0x200;
  oh->Subsystem = 3;
  oh->DllCharacteristics = 0x8500;
  oh->SizeOfStackReserve = 0x100000;
  oh->SizeOfStackCommit = 0x1000;
  oh->SizeOfHeapReserve = 0x100000;
  oh->SizeOfHeapCommit = 0x1000;
  oh->NumberOfRvaAndSizes = 16;
  oh->DataDirectory[1].VirtualAddress = 0x00002000;
  oh->DataDirectory[1].Size = 0x00001000;
}

template <typename Image>
void write_section_header(Image &image) {
  auto scth = reinterpret_cast<IMAGE_SECTION_HEADER *>(&image[0x178]);
  std::memcpy(scth[0].Name, ".text", 5);
  scth[0].Misc.VirtualSize = 14;
  scth[0].VirtualAddress = 0x1000;
  scth[0].SizeOfRawData = 0x200;
  scth[0].PointerToRawData = 0x200;
  scth[0].Characteristics = 0x60000020;
  std::memcpy(scth[1].Name, ".idata", 6);
  scth[1].Misc.VirtualSize = 0x54;
  scth[1].VirtualAddress = 0x2000;
  scth[1].SizeOfRawData = 0x200;
  scth[1].PointerToRawData = 0x400;
  scth[1].Characteristics = 0xC0300040;
}

template <typename Image>
void write_import_descriptor(Image &image) {
  auto ilt = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR *>(&image[0x400]);
  ilt[0].OriginalFirstThunk = 0x2028;
  ilt[0].Name = 0x2048;
  ilt[0].FirstThunk = 0x2030;
  *reinterpret_cast<DWORD *>(&image[0x428]) = 0x2038;
  *reinterpret_cast<DWORD *>(&image[0x430]) = 0x2038;
  std::strcpy(reinterpret_cast<char *>(&image[0x43A]), "print_number");
  std::strcpy(reinterpret_cast<char *>(&image[0x448]), "mydll.dll");
}

template <typename Data, typename List>
void write_data(List &list, Data data) {
  union {
    Data d;
    BYTE b[sizeof(d)];
  } t = {data};
  boost::copy(t.b, std::back_inserter(list));
}

std::vector<BYTE> compile(const std::string &file_name) {
  std::ifstream fin(file_name);
  if (!fin) {
    throw std::runtime_error("ファイル読み込み失敗");
  }
  std::vector<BYTE> v;
  std::string line;
  while (std::getline(fin, line)) {
    std::vector<std::string> tokens;
    if (parse_token(line, tokens)) {
      if (tokens[0] == "LET") {
        v.push_back(0xB8);
        write_data<int>(v, boost::lexical_cast<int>(tokens[2]));
        v.push_back(0xA3);
        write_data<DWORD>(v, 0x402018 + (tokens[1][0] - 'A') * 4);
      } else if (tokens[0] == "ADD") {
        v.push_back(0xA1);
        write_data<DWORD>(v, 0x402018 + (tokens[2][0] - 'A') * 4);
        write_data<WORD>(v, 0x0501);
        write_data<DWORD>(v, 0x402018 + (tokens[1][0] - 'A') * 4);
      } else if (tokens[0] == "DISP") {
        write_data<WORD>(v, 0x35FF);
        write_data<DWORD>(v, 0x402018 + (tokens[1][0] - 'A') * 4);
        v.push_back(0xA1);
        write_data<DWORD>(v, 0x00402030);
        write_data<WORD>(v, 0xD0FF);
        v.push_back(0x58);
      } else {
        throw std::runtime_error("存在しない命令");
      }
    } else {
      throw std::runtime_error("構\文エラー");
    }
  }
  v.push_back(0xC3);
  return v;
}

template <typename Image>
void output_exe(const Image &image) {
  std::ofstream fout("output.exe", std::ios::out | std::ios::binary);
  if (!fout) {
    throw std::runtime_error("ファイル書き込み失敗");
  }
  boost::copy(image, std::ostreambuf_iterator<char>(fout));
}

int main(int argv, char **argc) {
  try {
    std::array<BYTE, 0x600> image = {};
    write_dos_header(image);
    write_pe_header(image);
    write_section_header(image);
    boost::copy(compile(argc[1]), std::next(image.begin(), 0x200));
    write_import_descriptor(image);
    output_exe(image);
    return 0;
  } catch(const std::exception &error) {
    std::cerr << error.what() << std::endl;
    return 0;
  }
}

使用例的なもの

$ ./compile.exe test.asm
$ ./output.exe
3

 大体こんな感じですかね。なんか出力したexeを実行しようとすると初回のみノートン先生がなんか言ってきますが、取り敢えずスルーしたところ3が出力されました。要するに危なそうな感じがするって事なんです。
 まだ結構分かってないところが多いので、もうちょっと勉強していかなきゃいけないですね。