Nimでプロセスメモリエディタを作る①

##プロセスメモリエディタ
#### まずプロセスメモリエディタとは？
動作中のプロセスのプロセスメモリ内を、
検索・編集できるエディタのことです（ざっくり）。

####なぜプロセスメモリエディタなのかというと
- Nimでの**ポインタ**や**プロセス操作**を学習したい
- CTFやセキュリティ関連で、**うさみみハリケーン**（プロセスメモリエディタとして有名）のようなツールを<br>**CUI**かつ**MacOS**でも使いたい

からです。
とはいえ全部作るのは結構時間かかりそうなので、
①としてまずは大枠を作っていこうと思います。

####プロセスメモリエディタとして最低限の機能
・プロセスメモリ内の検索
・プロセスの停止、再開
・プロセスメモリの書き換え

本当に最低限ですがこんな感じでしょうか。
これらの関数は後で定義したいと思います。

## CLIツール作成
CLIフレームワークはdocoptモジュールを使いました（cligenでも良いと思います）。
まずはCLIツールとしてのオプション引数などの定義をします。

```nim:main.nim
# docopt用のコマンドライン引数定義
const doc = """
  Usage:
    pme_nim <action> [<content>]

  Options:
    <action>    ToDo Action
    <content>   ToDo Content
"""
```

pmeはProcessMemoryEditorの略として付けました。
んー分かりづらいかな。まあ一旦これで。
そしてコマンドツールとして動作するようにmain関数を書いていきます。

```nim:pme_nim.nim
import system, os, options, strUtils
import docopt

proc main =
  # まずはここでコマンドライン引数やオプションを変数に格納
  let
    args = docopt(doc, version = "1.0.0")
    action: string = $args["<action>"]
    content: string = $args["<content>"]

  # 第一コマンドライン引数によって処理を分岐
  case action:
    # プロセスメモリ内の検索
    of "search":
      # contentがnilでなければ追加を実行
      # 注意：stringで定義したのでnilはstringとして渡ってきます。
      if content == "nil":
        echo "content is null"
      else:
        echo "content is: " & content
        # TODO: プロセスメモリ内Search関数

    of "write":
      echo "overwrite memory is complete."
      # TODO: 該当ポインタアドレスに上書き
  
    of "process_list":
      echo "current processes: "
      # TODO: プロセス一覧を表示
  
    else:
      # コマンドが存在しないものの場合
      echo "command does not exists : " & action

when isMainModule:
  main()
```
これで一応CLIツールとしては、
コンパイルできるようになったかと思います。
ちなみにコンパイルと実行は、例によって以下になります。

```terminal
# コンパイル
$ nim c pme_nim.nim

# 実行
$ ./pme_nim search "test"
```

##プロセスメモリとポインタ

それでは本題のポインタをゴニョゴニョする方を考えていきます。

#### memfilesモジュール
メモリマップへアクセスするには[memfilesモジュール](https://nim-lang.org/docs/memfiles.html)を使うようです。
試しにポインタのアドレスを取得してみたいなと思ったので、以下を書いて試しに実行。

```nim:test.nim
import memfiles

when isMainModule:
  try:
    # ここはps aux コマンドなどで得られたプロセスが対象とするファイルでもOK
    # このopenがpointerを持ったMemFilesを返却
    var memMap = memfiles.open(filename = "/usr/bin/vm_stat",  allowRemap = true)
    var memPointer = memMap.mapMem(mappedSize = 1024)
    # ここでmemPointerのアドレスが確認できます
    echo repr(memPointer)
    # MemFilesは
    for line in lines(memMap):
      echo line
  except:
    echo("error!!!")
    raise
```

echo repr(memPointer)の箇所でうまく行けばアドレスが表示されます。

ああっ、自動公開の時間がきてしまった。。
構わず追記していきます笑

#### posix_utilsモジュール
では次にポインタの値を書き換える関数を定義してみます。
通常プロセスメモリは他プロセスから書き換えられないようにロックされているので
まずアンロックする必要がありますが、[posix_utils](https://nim-lang.org/docs/posix_utils.html)モジュールにその関数が定義されています。

先ほど取得したMemFilesから取得したポインタを使うとこのような感じに

```
# こちらは先ほどの取得
var memMap = memfiles.open(filename = "/usr/bin/vm_stat",  allowRemap = true)
var memPointer = memMap.mapMem(mappedSize = 1024)

# 1と書いた引数はメモリのページサイズを記載するようです。
memPointer.memoryUnlock(1)
```

memoryUnlockAllもあるみたいですが、
全部アンロックするのは危険なので対象メモリだけアンロックしようと思います。
memoryUnlockの説明に

```
Locks pages starting from a1 for **a1** bytes and prevent them from being swapped. 
```

と書いてあるのですが、多分 from a1 for **a2** bytesの間違いだと思われます。

これをもとにポインタのメモリを書き換える関数を作ってみると

```
proc overwrite(p: pointer, page_size, data: string): bool =
  # メモリをアンロック
  p.memoryUnlock(page_size)
  
  # メモリ上書き処理
  # p.c_memset(0000000, 1)
  # p.zeroMem(1)
  
  # メモリをロック
  p.memoryLock(page_size)
```

メモリを上書きするための処理は本当に情報が少ないですね。。。
c_memsetがsystemモジュールにあるようですが、
importしてもダメで再定義しないとでした。
どうやらzeroMemを使う方法もあるようですが、英語の記事すら全然ない。。

#### psutilモジュール
こちらは[johnscillieri](https://github.com/johnscillieri)さん作成のプロセスを扱うためのモジュールで
Pythonのpsutilライブラリを参考にしているみたいです。
Posix, Linux, Windowsと対応しているみたいなのでとてもありがたい。。
（これを自作しようと思って試行錯誤して時間が。。）
ただこちらはまだ作成途中のようでnim v1.0.2で動かしてみたら
コンパイルエラーになっちゃいますね。

次回②はこちらをフォークさせてもらって参考にしつつ、

- psutilを使ってプロセスリスト取得
- メモリ上書きの処理

をやっていこうと思います。