web-dev-qa-db-ja.com

REPNZ SCAS組み立て手順の詳細

私はバイナリをリバースエンジニアリングしようとしていますが、次の指示は私を混乱させます。

=>0x804854e:    repnz scas al,BYTE PTR es:[edi]
  0x8048550:    not    ecx

どこ:

EAX: 0x0
ECX: 0xffffffff
EDI: 0xbffff3dc ("aaaaaa\n")
ZF:  1

ECXが反復ごとに1ずつ何らかの方法で減少していること、そしてEDIは文字列の長さに沿って増加していることがわかります。文字列の長さを計算しますが、正確にはHOWそれは起こっており、なぜ「al」が関係しているのかはよくわかりません。

19
Michael Scott

コードをCに戻すことで説明します。

Intelの命令セットリファレンス(ボリューム開発2 ソフトウェア開発者マニュアル )は、この種のリバースエンジニアリングに非常に役立ちます。

REPNE SCASB

REPNEとSCASBを組み合わせたロジック:

_while (ecx != 0) {
    temp = al - *(BYTE *)edi;
    SetStatusFlags(temp);
    if (DF == 0)   // DF = Direction Flag
        edi = edi + 1;
    else
        edi = edi - 1;
    ecx = ecx - 1;
    if (ZF == 1) break;
}
_

またはもっと簡単に:

_while (ecx != 0) {
    ZF = (al == *(BYTE *)edi);
    if (DF == 0)
        edi++;
    else
        edi--;
    ecx--;
    if (ZF) break;
}
_

文字列の長さ

ただし、上記では文字列の長さの計算方法を説明するには不十分です。あなたの質問の_not ecx_の存在に基づいて、私はスニペットが_REPNE SCASB_を使用して文字列長を計算するためのこのイディオム(または類似の)に属していると想定しています:

_sub ecx, ecx
sub al, al
not ecx
cld
repne scasb
not ecx
dec ecx
_

Cに変換し、前のセクションのロジックを使用すると、次の結果が得られます。

_ecx = (unsigned)-1;
al = 0;
DF = 0;
while (ecx != 0) {
    ZF = (al == *(BYTE *)edi);
    if (DF == 0)
        edi++;
    else
        edi--;
    ecx--;
    if (ZF) break;
}
ecx = ~ecx;
ecx--;
_

_al = 0_および_DF = 0_を使用した単純化:

_ecx = (unsigned)-1;
while (ecx != 0) {
    ZF = (0 == *(BYTE *)edi);
    edi++;
    ecx--;
    if (ZF) break;
}
ecx = ~ecx;
ecx--;
_

注意事項:

  • 2の補数表記では、ecxのビットを反転することは_-1 - ecx_と同等です。
  • ループでは、ループが中断する前にecxが減分されるため、合計でlength(edi) + 1だけ減分されます。
  • 文字列はアドレス空間全体を占める必要があるため、ecxをループでゼロにすることはできません。

したがって、上記のループの後、ecxには-1 - (length(edi) + 1)と同じ-(length(edi) + 2)が含まれます。これは、ビットを反転してlength(edi) + 1を与え、最後にlength(edi)を与えるデクリメント。

またはループを再配置して簡素化する:

_const char *s = edi;
size_t c = (size_t)-1;      // c == -1
while (*s++ != '\0') c--;   // c == -1 - length(s)
c = ~c;                     // c == length(s)
_

そしてカウントを逆にします:

_size_t c = 0;
while (*s++ != '\0') c++;
_

これはCのstrlen関数です。

_size_t strlen(const char *s) {
    size_t c = 0;
    while (*s++ != '\0') c++;
    return c;
}
_
27
QuasarDonkey

ALはメモリをスキャンしてscasの値を探すため、ALが関係します。 ALはゼロに設定されているため、命令は文字列の最後で終了ゼロを見つけます。 scas自体は自動的にインクリメント(または方向フラグに応じてデクリメント)EDIします。 REPNZ接頭辞(REPNE形式で読みやすい)は、比較が偽である限りscasを繰り返します([〜#〜] rep [ 〜#〜]食べながら[〜#〜] n [〜#〜] ot [〜#〜] e [〜#〜] qual)とECX > 0。また、反復ごとにECXを自動的に減分します。 ECXは、ループが早く終了しないように、可能な限り長い文字列に初期化されています。

ECX0xffffffff(-1とも呼ばれます)、結果の長さは-1-ECXこれは、2の補数演算の特殊性により、NOT命令を使用して計算できます。

17
Jester

es:[edi]のバイトとalのバイトを比較し、ecxがゼロになるか、es:[edi]の値がalの値と一致するまでこの手順を繰り返します。各ステップの後、ediがインクリメントされ、メモリ内の次のバイトを指します。プログラムは、次の命令に基づいて、notを後でカウンター(ecx)に適用します。

repnzは、「ゼロフラグが設定されなくなるまで繰り返すand cx is not zero」を意味します。反復ごとにecxが減少します。 scas、より正確にはscasbは、alの値をメモリオペランドと比較し(常にes:[edi]またはes:[di]がアドレスサイズに応じて)、それに応じてフラグを設定します(2つの値が等しい場合、ゼロフラグが設定されます)。増分(または方向フラグに基づいて減分)edi

3
Powerslave