私はバイナリをリバースエンジニアリングしようとしていますが、次の指示は私を混乱させます。
=>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」が関係しているのかはよくわかりません。
コードをCに戻すことで説明します。
Intelの命令セットリファレンス(ボリューム開発2 ソフトウェア開発者マニュアル )は、この種のリバースエンジニアリングに非常に役立ちます。
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--;
_
注意事項:
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;
}
_
AL
はメモリをスキャンしてscas
の値を探すため、AL
が関係します。 AL
はゼロに設定されているため、命令は文字列の最後で終了ゼロを見つけます。 scas
自体は自動的にインクリメント(または方向フラグに応じてデクリメント)EDI
します。 REPNZ
接頭辞(REPNE
形式で読みやすい)は、比較が偽である限りscas
を繰り返します([〜#〜] rep [ 〜#〜]食べながら[〜#〜] n [〜#〜] ot [〜#〜] e [〜#〜] qual)とECX > 0
。また、反復ごとにECX
を自動的に減分します。 ECX
は、ループが早く終了しないように、可能な限り長い文字列に初期化されています。
ECX
は0xffffffff
(-1とも呼ばれます)、結果の長さは-1-ECX
これは、2の補数演算の特殊性により、NOT
命令を使用して計算できます。
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
。