ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

構造体の参照演算子は定数にも使える

C言語やC++でペリフェラルへのアドレスに構造体としてアクセスする際、以前は次のように書いていました.

#define PERI_ADDR (0x0000400)

typedef struct myst{
    int member1;
    int member2;
} myst_t;

int main(void)
{
  volatile myst_t *pSt = (myst_t*)PERI_ADDR;
  pSt->member1 = 1;
  pSt->member2 = 2;

一度変数にアドレスの定数を代入してから変数を介してアクセスする形ですね.

最近知ったのですが、以下のように変数に格納せずとも直接アドレスの定数を利用できるらしいです.

#define PERI_ADDR (0x0000400)

typedef struct myst{
    int member1;
    int member2;
} myst_t;

int main(void)
{
  ((myst_t*)PERI_ADDR)->member1 = 1;
  ((myst_t*)PERI_ADDR)->member2 = 2;

ソースコードの行数は減りますね.

ちなみに最適化無し(-O0)の場合スタックの確保の解放がある分後者の方が小さいバイナリが出力されましたが、最適化有り(-O3)だと全く同じバイナリが出力されました. なのでどちらの書き方をするかは完全に好みで決めて良さそうです.

なお、以上の結果はすべてSTM32CubeIDEでの結果です. 環境によっては異なる結果になるかもしれないので悪しからず.

検証環境

項目 バージョン
OS Windows
IDE STM32CubeIDE1.17.0
ツールチェーン GNU Tools for STM32(12.3rel1)

WaitForMultipleObjectsがうまくいかない

次のWindows用マルチスレッドのプログラムは、「hoge」というメッセージを表示するスレッドを100個ディスパッチし、全てのスレッドが処理を終えたらプログラムを終了することを期待したプログラムです. つまり、コマンドプロンプトにhogeが100個表示されるはずですが、実行してみると期待通りに動きません.

#include <iostream>
#include <Windows.h>

#define THREAD_NM   (100)

CRITICAL_SECTION g_CS;

DWORD WINAPI WorkThreadFunc(LPVOID lpParam);

int main()
{
    HANDLE hThreadArray[THREAD_NM];

    // クリティカルセクション初期化(この記事の本質ではない)
    if (!InitializeCriticalSectionAndSpinCount(&g_CS, 0x00000400)) {
        return 1;
    }

    for (int i = 0; i < THREAD_NM; i++) {
        hThreadArray[i] = CreateThread(
            NULL,                          // default security attributes
            0,                             // use default stack size  
            WorkThreadFunc,                // thread function name
            NULL,                          // argument to thread function 
            0,                             // use default creation flags 
            NULL);                         // returns the thread identifier 
    }
    WaitForMultipleObjects(
        THREAD_NM,
        hThreadArray,
        TRUE,
        INFINITE
    );

    //DeleteCriticalSection(&g_CS); // 訳あってコメントアウト
}

DWORD WINAPI WorkThreadFunc(LPVOID lpParam)
{
    // そのままだと標準出力のメッセージが崩れるのでクリティカルセクションではさむ
    EnterCriticalSection(&g_CS);
    printf("echo hoge\n");
    LeaveCriticalSection(&g_CS);

    return 0;
}


数回実行するとわかりますが、実行する度に表示されるhogeの数が異なります. 試しに自分の環境で何度か実行した結果が以下になります.

n回目 hogeの表示数
1 46
2 37
3 44


この原因はWaitForMultipleObjectsが対応しているハンドルの最大数がMAXIMUM_WAIT_OBJECTSであるためです. 私の環境では64でdefineされていました. すなわち、 WaitForMultipleObjectsは64スレッドまでにしか対応しておらず、それを超える数を第一引数に代入するとブロッキングが不完全になります. それを確認するためにmain関数のDeleteCriticalSectionのコメントアウトを解除すると確率で例外が発生します.

クリティカルセクションで例外発生

これはWaitForMultipleObjectsのブロッキングが不完全なためにmain関数(メインスレッド)ないでクリティカルセクションを解放したのちにWorkThreadFunc関数(ワーカースレッド)でクリティカルセクションを利用しようとしたために発生します.

参考

learn.microsoft.com

learn.microsoft.com

情報処理安全確保支援士に合格しました

2024年4月の試験にて情報処理安全確保支援士試験に合格しました.

勉強方法や使用した参考書を備忘録として残しておきます.

使用した参考書

勉強は主に以下の3冊の本を利用しました.


オールインワンは教本と過去問が7:3くらいの構成、午後問題徹底攻略は用語のまとめと午後問題の解き方の詳細、ポケット攻略本は要点だけをまとめたものとなっています.

勉強に割けられる時間が限られていたため、教本の中でも午後問題を扱っているものを選びましたが、その分教本としての内容が薄くなっている感は否めませんでした.

時間がある方、初回受験で絶対合格したいというわけではない方は教本としての側面が強い参考書を利用するのが良いかと思いました. 逆に時間が無い方、一か八か受かればよいという方はオールインワンのような過去問演習多めの参考書がおすすめです.

上記3冊の中でも個人的に買ってよかったと思ったのはポケット攻略本です. この本は要点のまとめとして一番最後の試験1ヶ月前に購入したのですが、紛らわしい用語の区別や覚え方などが分かりやすくかつコンパクトにまとまっていたため、学習初期にこそ購入すべきだったと思いました.


また、勉強の息抜きに以下の本も読んでいました.

暗号と認証回りが図で解説されていて、息抜きにはちょうどよかったです. 特に暗号に関してはどの教本よりもごまかしがなく書かれている感じがしました.

勉強の方法

応用情報の合格が分かった2023年12月下旬に参考書を買って勉強を始めました. 平日や休日はカフェで勉強してました.

情報処理安全確保支援士試験の勉強で個人的に最も重要なのは、似たような概念や似たような概念の中にある複数のモードを区別して理解することです. 具体的には

  • SPFとDKIMの違い
  • VPNにおけるトランスポートモードとトンネルモード
  • TLS1.2とTLS1.3

などを区別して理解することです. 試験勉強の中終はこういった紛らわしいものの違いをまとめたリストなどを自分で作って理解を深めていました.

試験に合格した感想

自分自身セキュリティ分野に関わっているわけではないので直接役立つことは少ないと思いますが、情シスの苦悩やセキュリティのための技術を広く浅く知れただけでも良かったです.

資格なんて意味がないという人がいて、自分もどちらかというとその考えに賛成ですが、それでも受かっていたらやっぱりうれしいものです.

SK-AM64BでLinuxを動かす

TIのARMコア搭載プロセッサの評価ボードでuSDカードからLinuxを起動させたときのメモ. 公式ガイダンスの手順を追っただけ.

ソフトウェアの準備

SDカードイメージ

TIから配布されているSDカード用のイメージ(.wic)をダウンロードします.

以下の評価ボードの商品説明ページからPROCESSOR-SDK-LINUX-AM64Xのダウンロードオプションをクリックし、tisdk-default-image-am64xx-evm.wic.xzをダウンロードします. 解凍するとwicファイルが入っています.

www.ti.com

blenaEtcher

イメージファイルを外部記憶媒体に書き込むためのソフトblenaEtcherをインストールします.

etcher.balena.io

ブートメディアの作成

blenaEtcherを利用してuSDカードにwicファイルを書き込みます.

uSDに書き込んだ直後の状態

基板との接続

micro USB-Bケーブルでシリアルを接続しておきます. Type-Cの電源用のポート付近にmicro USB-bの口が2つありますが、Type-Cに近い方が出力用シリアルポートになってます.

設定はボーレート115200、データ8bit、パリティnone、ストップビット1bit、フロー制御なし.

ICが2つ口を持っているので、シリアルコンソールからは2つCOMポートが見えますが、いい感じに表示される方を選びましょう.

Linux起動(失敗)

準備が整ったのでuSDを指した状態で電源をつけます. すると次のようなエラーメッセージでブートが止まりました.

ti_csi_get_response: Message receive failed. 
ret = -110
no sysreset
### ERROR ### Pleaes RESET the board ###

変なエラーが出ちゃいました.

Linux起動(成功)

TIのドキュメントを漁ってみると次のURLの"Boot SD card on AM64x SR1.0"の項目に書いてありました. 雑な翻訳をするとこの評価基板に乗っているプロセッサにはエディションが複数あり、(よくわからんが)SR2.0とSR1.0というエディションではブートに使用すべきbinファイルが異なるとのこと.

https://dev.ti.com/tirex/explore/content/am64x_academy_9_02_00_00_v1/_build_am64x_academy_9_02_00_00_v1/source/linux/ch-eval/eval-flash-sdcard.html#boot-sdcard-on-am64x-sr10


なのでドキュメントに従いSDカードのルートディレクトリにあるtiboot3.binを他の名前にリネームし、代わりにtiboot3-am64x-gp-evm.binをtiboot3.binという名前にリネームすると無事Linuxをブートさせることが出来ました.

起動に成功したときのuSDの状態

Linuxのブートシーケンス

以下のTIのドキュメントを見るに、次のようなフローでブートが行われるらしい.(ブラウザで見ると図表が崩れてるが、VSCodeで見るといい感じの表になる)

  1. RBL(Rom Boot Loader)がDMSC(M3)上で起動する
  2. DIPスイッチに基づいた外部メディアからtiboot3.bin(R5 SPL + SYSFW image)を探してSRAMにロード.認証もする.
  3. M3上でSYSFW、R5上でSPL実行(同時?順序有?)
  4. A53 SPLが起動しu-boot.imgをロード
  5. A53でU-Boot実行、Linuxカーネルをロード
  6. LinuxがA53で起動

https://dev.ti.com/tirex/explore/node?node=A__ARmRwo4dDbrvgRErwdqn-g__AM64-ACADEMY__WI1KRXP__LATEST

Windowsで別プロセスのメモリを書き換える

複数のプロセス間でメモリを共有する方法としてよく使われるのは共有メモリですが、Win32APIの WriteProcessMemory() を用いると異なるプロセスのメモリを書き換えることができます. これを使ってグローバル変数を他プロセスから書き換えてみましょう.

環境

項目 説明
OS Windows11
Visual Studio 17.6.2(2022)

ソリューションの作成

Visual Studioで実験用のソリューションを作成します. その中にChild(書き換えられる方)とParent(書き換える方)というC++の空のプロジェクトを作ります.

Childプロジェクトの設定

デフォルトではASLR(アドレス空間レイアウトのランダム化)が有効になっているため、プログラムを実行するたびに仮想アドレスが変化してしまいます. セキュリティ強化のための機能ですが、本プログラムの場合他プロセスのグローバル変数のアドレスが毎回変化しては困るためこの機能を無効にします.

Chileプロジェクトのプロパティ->リンカー->詳細設定の「ランダム化されたベースアドレス」を「いいえ」にします.

ASLRを無効にする

また、リンク後の仮想アドレスを知りたいので、mapファイルを出力する設定にしておきましょう.

Chileプロジェクトのプロパティ->リンカー->デバッグの「マップファイルの作成」を「はい」にします.

マップファイルを作成する

ソースコードの作成

Childプロジェクトのソースフォルダにmain.cppを追加し、下のようにコーディングします.

/* Childプロジェクトのmain.cpp */
#include<Windows.h>
#include<iostream>

using namespace std;

int g_val = 1;

int main()
{
    /* 初期値の表示 */
    cout << &g_val << endl;
    cout << "initial val:" << g_val << endl;

    /* イベント待ち */
    HANDLE hEvent = CreateEvent(
        NULL,               /* セキュリティ属性   */
        FALSE,              /* 手動リセットオフ   */
        FALSE,               /* 初期状態          */
        L"EVENT.HOGE"        /* イベント名        */
    );

    if (hEvent) {
        WaitForSingleObject(hEvent, INFINITE);
    }

    /*操作後の値の表示 */
    cout << "updated val:" << g_val << endl;

    return 0;
}

Parentプロジェクトでも同様にソースフォルダにmain.cppを追加し以下のようにコーディングします.

/* Parentプロジェクトのmain.cpp */
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>
#include <vector>

/* 表示用 */
#include<iostream>

using namespace std;

/* 別プロセス(Child.exe)のグローバル変数のアドレス */
const long* pVal = (long*)(0x000000014001D000);

void GetPidByName(const TCHAR* targetName, vector<DWORD>& ret)
{
    /* システム内のプロセス識別子を取得 */
    DWORD pids[1024], byteNeeded;

    if (!EnumProcesses(pids, sizeof(pids), &byteNeeded))
    {
        return;
    }

    /* プロセスの総数を計算 */
    DWORD processNum;
    processNum = byteNeeded / sizeof(DWORD);

    /* 各プロセスごとにループ */
    for (unsigned int i = 0; i < processNum; ++i) {

        DWORD pid = pids[i];

        /* プロセス識別子からプロセスハンドル取得 */
        HANDLE hProcess = OpenProcess(
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
            FALSE,
            pid
        );

        if (NULL != hProcess)
        {
            HMODULE hMod;
            DWORD cbNeeded;
            TCHAR processName[MAX_PATH];

            /* 本来はhModは配列にすべきだがプロセス名さえ取得できれば十分とする */
            if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
            {
                /* プロセス名取得 */
                GetModuleBaseName(
                    hProcess,
                    hMod,
                    processName,
                    sizeof(processName) / sizeof(TCHAR)
                );

                /* プロセス名が一致するか */
                if (!_tcscmp(targetName, processName)) {
                    ret.push_back(pid);
                }
            }

            /* ハンドルクローズ */
            CloseHandle(hProcess);
        }
    }

}


int main(void)
{
    /* 結果を入れるベクタ */
    vector<DWORD> pids;

    /* プロセス名からpidを検索 */
    GetPidByName(TEXT("Child.exe"), pids);

    /* pidを表示 */
    for (const DWORD pid : pids) {
        cout << "Child.exe's PID:" << pid << endl;
    }

    /* プロセス識別子からプロセスハンドル取得 */
    HANDLE hProcess = OpenProcess(
        PROCESS_VM_WRITE | PROCESS_VM_OPERATION,
        FALSE,
        pids.front()
    );

    /* Child.exeの準備ができるまでスリープ */
    Sleep(2000);

    DWORD newVal = 2;
    size_t retBytes = 0;

    /* 別プロセスのメモリ書き換え */
    WriteProcessMemory(
        hProcess,
        (LPVOID)pVal,
        &newVal,
        sizeof(newVal),
        &retBytes
    );

    /* イベントセット */
    HANDLE hEvent = CreateEvent(
        NULL,               /* セキュリティ属性   */
        FALSE,              /* 手動リセットオフ   */
        FALSE,               /* 初期状態          */
        L"EVENT.HOGE"        /* イベント名        */
    );

    if (hEvent) {
        SetEvent(hEvent);
    }

    /* 書き換えられたバイト数表示. 4なら成功 */
    cout << "written:" << retBytes << endl;

    /* 入力があるまで待機 */
    int a;
    cin >> a;

    return 0;
}

なお、自作関数GetPidByName()の詳細は下記をご参照下さい.

prupru-prune.hatenablog.com

スタートアッププロジェクトの設定

上記でプログラムは完了ですがVisual Studioからデバック起動できるようにスタートアッププロジェクトとして2つのプロジェクトを設定しましょう.

ソリューションエクスプローラーからソリューションを右クリック->プロパティを選択すると共通プロパティ->スタートアッププロジェクトという欄があるので、そこのマルチスタートアッププロジェクトにチェックを入れ、それぞれのプロジェクトのアクションを開始に選択します.

ソリューションのスタートアッププロジェクト設定

これでVisual Studioから2つのプロジェクトを同時にデバッグできるようになりました.

実行

ソリューション全体をビルドしてデバッグを開始すると、上手くいけば以下のような実行結果となります. g_valの値が変化していることから、別プロセスからメモリを書き換えられていることがわかります.

実行結果

参考

learn.microsoft.com

learn.microsoft.com

Windowsでプロセス名からpidを検索する

C/C++のプログラムにおいてプロセス名からpidを取得するプログラムを作りました.

環境

項目 説明
OS Windows11
Visual Studio 17.6.2(2022)

ソースコード

以下は「chrome.exe」というプロセスのプロセス識別子を表示するためのソースコードです.

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>
#include <vector>

/* 表示用 */
#include<iostream>

using namespace std;

void GetPidByName(const TCHAR *targetName, vector<DWORD>& ret)
{
    /* システム内のプロセス識別子を取得 */
    DWORD pids[1024], byteNeeded;

    if (!EnumProcesses(pids, sizeof(pids), &byteNeeded))
    {
        return;
    }

    /* プロセスの総数を計算 */
    DWORD processNum;
    processNum = byteNeeded / sizeof(DWORD);

    /* 各プロセスごとにループ */
    for (unsigned int i = 0; i < processNum; ++i) {

        DWORD pid = pids[i];

        /* プロセス識別子からプロセスハンドル取得 */
        HANDLE hProcess = OpenProcess(
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
            FALSE, 
            pid
        );

        if (NULL != hProcess)
        {
            HMODULE hMod;
            DWORD cbNeeded;
            TCHAR processName[MAX_PATH];

            /* 本来はhModは配列にすべきだがプロセス名さえ取得できれば十分とする */
            if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
            {
                /* プロセス名取得 */
                GetModuleBaseName(
                    hProcess, 
                    hMod, 
                    processName,
                    sizeof(processName) / sizeof(TCHAR)
                );

                /* プロセス名が一致するか */
                if (!_tcscmp(targetName, processName)) {
                    ret.push_back(pid);
                }
            }

            /* ハンドルクローズ */
            CloseHandle(hProcess);
        }
    }

}


int main(void)
{
    /* 結果を入れるベクタ */
    vector<DWORD> pids;

    /* プロセス名からpidを検索 */
    GetPidByName(TEXT("chrome.exe"), pids);

    /* pidを表示 */
    for (const DWORD pid : pids) {
        cout << pid << endl;
    }

    return 0;
}

// 実行結果(例)
19212
19252
19452
18492
18684
18588
20624
7156
22944
22668
14912
17884
20672
11160
22356
23096
22132
10768
9800
15228
7632
260
10724
21760
10568

解説

当然ながらWinAPIのオンパレードです. まず EnumProcessModules() を用いてシステム内のプロセス識別子をDWORDの配列に格納します. その次に OpenProcess() でプロセス識別子に対応したプロセスへのハンドルを取得、 EnumProcessModules() を用いてそのプロセス内の(最初の)モジュールへのハンドルを取得、最後に GetModuleBaseName() をコールしてモジュールの名前を取得します.

実行ファイル名とプロセス(モジュール)名は必ずしも一致しないので注意が必要です.

参考

learn.microsoft.com

learn.microsoft.com

learn.microsoft.com

www.t-net.ne.jp

WinMergeでバイナリを比較する

実行環境

項目 説明
OS Windows11
WinMerge version 2.16.30.0

ã‚„ã‚Šæ–¹

ファイル->形式を指定して再比較->バイナリ を選択するとhex形式で表示してくれる

バイナリ形式で表示する

参考

ftsh.hateblo.jp