3

好吧,我正在研究 Delphi 中的一些内联程序集,并且程序集加密例程一切都很好,直到我尝试将 ShortString 解析到文本框中。

我得到的违规如下: 错误

完整代码在这里:

procedure TForm2.Button1Click(Sender: TObject);

var
len,keylen:integer;
name, key:ShortString;

begin

name :=  ShortString(Edit1.Text);
key := '_r <()<1-Z2[l5,^';
len := Length(name);
keylen := Length(key);

nameLen := len;
serialLen := keyLen;

asm

  XOR EAX,EAX
  XOR ESI,ESI
 XOR EDX,EDX
 XOR ECX,ECX


  @loopBegin:

        MOV EAX,ESI
        PUSH $019
        CDQ
        IDIV DWORD PTR DS:[serialLen]
        MOV EAX,ESI
        POP EBX
        LEA ECX,DWORD PTR DS:[key+EDX]
        CDQ
        IDIV DWORD PTR DS:[nameLen]
        LEA EAX,DWORD PTR DS:[name]
        MOVZX EAX,BYTE PTR DS:[name+EDX]
        MOVZX EDX,BYTE PTR DS:[ECX]
        XOR EAX,EDX
        CDQ
        IDIV EBX
        ADD DL,$041
        INC ESI
        CMP ESI,DWORD PTR DS:[serialLen]
        MOV BYTE PTR DS:[ECX],DL

        JL @loopBegin


end;

edit2.Text:= TCaption(key);


end;

如果我在“edit2.Text:= TCaption(key);”行上放置一个断点 我可以看到 ShortString “密钥”确实已正确加密,但背后也有很多奇怪的字符。

前 16 个字符是真正的加密。

加密 http://img831.imageshack.us/img831/365/29944312.png

bigger version: http://img831.imageshack.us/img831/365/29944312.png

谢谢!

4

4 回答 4

12

代码的作用

对于那些不会说汇编的人,这可能是代码在 Pascal 中应该做的事情。“可能”是因为原版包含一些错误:

procedure TForm14.Button1Click(Sender: TObject);
var KeyLen:Integer;
    Name, Key:ShortString;
    i:Integer;
    CurrentKeyByte:Byte;
    CurrentNameByte:Byte;
begin
  Name := ShortString(Edit1.Text);
  Key := '_r <()<1-Z2[l5,^';
  keyLen := Length(key);

  asm int 3 end; // This is here so I can inspect the assembler output in the IDE
                 // for the "Optimised" version of the code

  for i:=1 to Length(Name) do
  begin
    CurrentKeyByte := Byte(Key[i mod KeyLen]);
    CurrentNameByte := Byte(Name[i]);
    CurrentNameByte := ((CurrentKeyByte xor CurrentNameByte) mod $019) + $041;
    Name[i] := AnsiChar(CurrentNameByte);
  end;

  Caption := Name;

end;

启用优化后,由此生成的汇编代码实际上比建议的代码更短,不包含冗余代码,我敢打赌更快。以下是我在 Delphi 生成的代码中注意到的一些优化(与 OP 提出的汇编代码相比):

  • Delphi 反转了循环(下降到 0)。这节省了一条“CMP”指令,因为编译器可以简单地“DEC ESI”并在零标志上循环。
  • 使用“XOR EDX”和“DIV EBX”进行二分法,节省了一些周期。

为什么提供的汇编代码失败?

这是原始的汇编代码,带有注释。错误出现在例程的末尾,在“CMP”指令处——它将 ESI 与 KEY 的长度进行比较,而不是与 NAME 的长度进行比较。如果 KEY 比 NAME 长,“加密”会在 NAME 的顶部继续,覆盖内容(被覆盖的内容是字符串的 NULL 终止符,导致调试器在正确字符之后显示有趣的字符)。

虽然不允许覆盖 EBX 和 ESI,但这不是导致代码 AV 的原因,可能是因为周围的 Delphi 代码没有使用 EBX 或 ESI(刚刚尝试过)。

asm

 XOR EAX,EAX ; Wasteful, the first instruction in Loop overwrites EAX
 XOR ESI,ESI
 XOR EDX,EDX ; Wasteful, the first CDQ instruction in Loop overwrites EDX
 XOR ECX,ECX ; Wasteful, the first LEA instruction overwrites ECX


 @loopBegin:
       ; Etering the loop, ESI holds the index for the next char to be
       ; encrypted.

       MOV EAX,ESI ; Load EAX with the index for the next char, because
                   ; we intend to do some divisions (setting up the call to IDIV)
       PUSH $019   ; ? pushing this here, so we can pop it 3 lines later... obfuscation
       CDQ         ; Sign-extend EAX (required for IDIV)
       IDIV DWORD PTR DS:[serialLen] ; Divide EAX by the length of the key.
       MOV EAX,ESI ; Load the index back to EAX, we're planning on an other IDIV. Why???
       POP EBX     ; Remember the PUSH $019?
       LEA ECX,DWORD PTR DS:[key+EDX] ; EDX is the result of "ESI mod serialLen", this
                                      ; loads the address of the current char in the
                                      ; encryption key into ECX. Dividing by serialLen
                                      ; is supposed to make sure we "wrap around" at the
                                      ; end of the key
        CDQ ; Yet some more obfuscation. We're now extending EAX into EDX in preparation for IDIV.
            ; This is obfuscation becasue the "MOV EAX, ESI" instruction could be written right here
            ; before the CDQ.
        IDIV DWORD PTR DS:[nameLen] ; We divide the current index by the length of the text
                                    ; to be encrypted. Once more the code will only use the reminder,
                                    ; but why would one do this? Isn't ESI (the index) always supposed to
                                    ; be LESS THEN nameLen? This is the first sign of trouble.
        LEA EAX,DWORD PTR DS:[name] ; EAX now holds the address of NAME.
        MOVZX EAX,BYTE PTR DS:[name+EDX] ; EAX holds the current character in name
        MOVZX EDX,BYTE PTR DS:[ECX]      ; EDX holds the current character in Key
        XOR EAX,EDX ; Aha!!!! So this is an obfuscated XOR loop! EAX holds the "name[ESI] xor key[ESI]"
        CDQ         ; We're extending EAX (the XOR result) in preparation for a divide
        IDIV EBX    ; Divde by EAX by EBX (EBX = $019). Why????
        ADD DL,$041 ; EDX now holds the remainder of our previous XOR, after the division by $019;
                    ; This is an number from $000 to $018. Adding $041 turns it into an number from
                    ; $041 to $05A (ASCII chars from "A" to "Z"). Now I get it. This is not encryption,
                    ; this is a HASH function! One can't un-encrypt this (information is thrown away at
                    ; the division).
        INC ESI     ; Prep for the next char


        ; !!! BUG !!!
        ;
        ; This is what's causing the algorithm to generate the AV. At this step the code is
        ; comparing ESI (the current char index) to the length of the KEY and loops back if
        ; "ESI < serialLen". If NAME is shorter then KEY, encryption will encrypt stuff beyond
        ; then end of NAME (up to the length of KEY). If NAME is longer then KEY, only Length(Key)
        ; bytes would be encrypted and the rest of "Name" would be ignored.
        ;
        CMP ESI,DWORD PTR DS:[serialLen]


        MOV BYTE PTR DS:[ECX],DL ; Obfuscation again. This is where the mangled char is written
                                 ; back to "Name".

        JL @loopBegin            ; Repeat the loop.

我的 2 美分建议

汇编程序应该用于SPEED 优化,仅此而已。在我看来,好像 OP 试图使用 Assembler 来混淆代码正在做什么。没有帮助,我只花了几分钟就弄清楚代码在做什么,而且我不是汇编专家。

于 2010-07-19T07:39:30.587 回答
6

首先,您需要保留 EDI 和 ESI。只有 EAX、EDX 和 ECX 可以在不保存的情况下使用(除非您加载它并需要保存它)。

尝试在代码周围添加一些 PUSH EDI、PUSH ESI 和 POP ESI、POP EDI。

于 2010-07-18T23:07:51.637 回答
3

在不保留(保存和恢复)寄存器内容的情况下,您不能简单地在内联 ASM 中为您自己的目的选择寄存器。

在您的代码中,您正在践踏 EAX(包含“self”)和 EDX(其中 - 使用默认的寄存器调用约定 - 很可能包含“Sender”)。

据我了解,其他寄存器也可用于局部变量。

于 2010-07-18T23:29:50.557 回答
2

提示:如果 ESI 或 EAX 或任何持有 Self 的东西怎么办?您的汇编程序将其丢弃。下一行您将尝试使用 Edit2,它需要访问 Self,这是......好吧,不再与我们在一起。

编译器和您都使用寄存器。您需要玩得好并与编译器合作,这意味着保存/恢复寄存器的值。

否则我认为您需要将汇编代码卸载到单独的例程中,因此不会有 Pascal 代码,它也可能使用寄存器。请注意,您仍然需要遵守调用约定协议:并非所有寄存器都可以自由使用。

于 2010-07-18T23:17:47.730 回答