Delphi: Нарушение прав доступа при помещении строки в поле ввода? - PullRequest
3 голосов
/ 19 июля 2010

Хорошо, я изучаю некоторую встроенную сборку в Delphi, и криптографическая подпрограмма сборки отлично работает, пока я не попытаюсь проанализировать ShortString в Textbox.

Нарушение, которое я получаю, заключается в следующем: Ошибка http://img828.imageshack.us/img828/2410/43832949.png

Полный код здесь:

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 ]

12 голосов
/ 19 июля 2010

Что делает код

Для тех из вас, кто не говорит на ассемблере, это то, что код, вероятно, должен делать в Паскале.«Вероятно», потому что оригинал содержит некоторые ошибки:

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 цента

Ассемблер должен использоваться для СКОРОСТИ оптимизаций и ничего больше .Мне кажется, что ОП пытался использовать Ассемблер, чтобы запутать то, что делает код.Не помогло, мне потребовалось всего несколько минут, чтобы понять, что именно делает код, и я НЕ эксперт по ассемблеру.

6 голосов
/ 19 июля 2010

Прежде всего, вам нужно сохранить EDI и ESI.Только EAX, EDX и ECX можно использовать без сохранения (кроме случаев, когда вы загружаете его и должны его сохранить).

Попробуйте добавить несколько PUSH EDI, PUSH ESI и POP ESI, POP EDI вокруг вашего кода.

3 голосов
/ 19 июля 2010

Вы не можете просто использовать регистры для своих собственных целей во встроенном ASM, не сохраняя (сохраняя и восстанавливая) содержимое регистра.

В вашем коде вы попираете EAX (который содержит «self») и EDX (который - по умолчанию регистр соглашение о вызовах - скорее всего, содержит «Отправитель»).

И, насколько я понимаю, другие регистры также могут использоваться для локальных переменных.

2 голосов
/ 19 июля 2010

Подсказка: что если ESI, EAX или что-то еще содержит Self? Ваш ассемблер это мусор. В следующей строке вы пытаетесь использовать Edit2, который требует доступа к Self, который ... ну, больше не с нами.

И компилятор, и вы используете регистры. Вы должны хорошо играть и сотрудничать с компилятором, что означает сохранение / восстановление значений регистров.

В противном случае я думаю, что вам нужно разгрузить ассемблерный код для разделения подпрограмм, поэтому не будет кода на Паскале, который также может использовать регистры. Обратите внимание, что вам все равно нужно будет соблюдать протокол соглашения о вызовах: не все регистры могут использоваться свободно.

...