Разборка x86, включает модульное хеширование - PullRequest
1 голос
/ 09 апреля 2020

Я прокомментировал, что, по-моему, делает код.

Я попытался поставить aaaaaaaaa, AAAAAAAAA, !!!!!!!!! и 000000000, все они работают. Но он не примет bbbbbbbbb или 111111111. Кажется, пароль принимает, что все символы ascii соответствуют 0x21, 0x31, 0x41, ...

Первоначально он вызывает <getchar@plt>, и он входит в для l oop , который принимает 10 символов.

После этого запускается новый l oop, которого я не понимаю. Можете ли вы объяснить это l oop? Это делает модульное разделение? Заранее спасибо!

 8048443:   88 44 1d e5             mov    %al,-0x1b(%ebp,%ebx,1)
 8048447:   83 45 f4 01             addl   $0x1,-0xc(%ebp)
 804844b:   83 7d f4 09             cmpl   $0x9,-0xc(%ebp)                  # x ($ebp - 0xc) counter 9
 804844f:   7e ea                   jle    804843b <puts@plt+0xe7>
 8048451:   8b 45 f4                mov    -0xc(%ebp),%eax
 8048454:   c6 44 05 e5 00          movb   $0x0,-0x1b(%ebp,%eax,1)
 8048459:   c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%ebp)                  # counter = 1
 8048460:   eb 15                   jmp    8048477 <puts@plt+0x123>         # start of the loop
 8048462:   8b 45 f4                mov    -0xc(%ebp),%eax
 8048465:   83 e8 01                sub    $0x1,%eax
 8048468:   0f b6 44 05 e5          movzbl -0x1b(%ebp,%eax,1),%eax
 804846d:   0f be c0                movsbl %al,%eax
 8048470:   01 45 f0                add    %eax,-0x10(%ebp)
 8048473:   83 45 f4 01             addl   $0x1,-0xc(%ebp)
 8048477:   83 7d f4 0a             cmpl   $0xa,-0xc(%ebp)                  # 10
 804847b:   7e e5                   jle    8048462 <puts@plt+0x10e>         # end loop
 804847d:   8b 45 f0                mov    -0x10(%ebp),%eax
 8048480:   89 c2                   mov    %eax,%edx
 8048482:   c1 fa 1f                sar    $0x1f,%edx
 8048485:   c1 ea 1c                shr    $0x1c,%edx
 8048488:   01 d0                   add    %edx,%eax
 804848a:   83 e0 0f                and    $0xf,%eax                        # only look at the lowest four bits
 804848d:   29 d0                   sub    %edx,%eax
 804848f:   89 45 f0                mov    %eax,-0x10(%ebp)
 8048492:   83 7d f0 03             cmpl   $0x3,-0x10(%ebp)                 # compare to 3
 8048496:   75 16                   jne    80484ae <puts@plt+0x15a>         # go to wrong answer
 8048498:   b8 b4 85 04 08          mov    $0x80485b4,%eax
 804849d:   8d 55 e5                lea    -0x1b(%ebp),%edx
 80484a0:   89 54 24 04             mov    %edx,0x4(%esp)
 80484a4:   89 04 24                mov    %eax,(%esp)
 80484a7:   e8 98 fe ff ff          call   8048344 <printf@plt>             # correct answer
 80484ac:   eb 0c                   jmp    80484ba <puts@plt+0x166>
 80484ae:   c7 04 24 e2 85 04 08    movl   $0x80485e2,(%esp)
 80484b5:   e8 9a fe ff ff          call   8048354 <puts@plt>               # wrong answer```

1 Ответ

2 голосов
/ 09 апреля 2020

Я собирался опубликовать ответ о sar / shr / add / и / sub части этого кода на Обратный инженерный код, который делает SAR на 31, shr, затем добавил и вычел это вокруг AND но он был удален примерно за минуту до того, как я закончил. Эта часть кода, вероятно, всплывает достаточно часто, чтобы стоило ответить:


Код вокруг AND выполняет n %= 16; для 32-битного целого типа со знаком (например, int), реализуя C семантику остатка со знаком. Для неотрицательных целых чисел это эквивалентно n &= 0xf;, т. Е. Сохраняются только младшие 4 бита.

Вы можете заставить GCC4.7 испускать именно этот код ( Обозреватель компилятора Godbolt ) для этой функции:

int mod16(int n) {
    return n % 16;
}
# gcc4.7.4 -O1 -m32 -mregparm=1  (first arg in EAX instead of on the stack)
mod16(int):
    movl    %eax, %edx
    sarl    $31, %edx
    shrl    $28, %edx
    addl    %edx, %eax
    andl    $15, %eax
    subl    %edx, %eax
    ret                     # return with n%16 in EAX.

Интересный факт: gcc4.8 и новее используют cdq вместо mov/sar $31 для подписания расширения EAX в EDX : EAX. т.е. установить все биты EDX = знаковый бит EAX.

Сохранение / перезагрузка в вашем коде и использование EBP в качестве указателя кадра указывают, что он был скомпилирован без (дополнительной) оптимизации. G CC больше, чем большинство компиляторов по-прежнему часто используют эффективные способы выполнения действий в одном выражении. Я использовал -O1 на Godbolt, чтобы избежать сохранения / перезагрузки компилятором памяти.


В C, % должен произвести отрицательный остаток для отрицательного n, но он все еще гораздо эффективнее эмулировать это вокруг инструкции AND для реализации семантики деления со знаком-целым числом, чем использовать idiv для степеней 2. (То же самое для не степеней 2 констант времени компиляции с мультипликативным обратным .)

Напомним, что (n/16)*16 + n%16 = n для всех n и что C деление со знаком усекается до 0. (Это относится к любому делителю, а не только к положительным степеням 2). Так например -22/16 = -1 и -22 % 16 = -6.
-1 * 16 + -6 = -22. Компилятор должен выдавать код, который работает для всех возможных n; он (обычно) не знает, что ваш int никогда не бывает отрицательным при выполнении кода.

Arithmeti c сдвиг вправо в сторону -Inf, поэтому для реализации семантики / необходимы аналогичные исправления.

Вот почему вы должны использовать unsigned или n &= 15;, когда вам не нужно не нужны отрицательные остатки, и вы хотите, чтобы он эффективно компилировался вместо этого слишком сложного sequence. Это дополнительное искажение вокруг and $0xf, %eax может оптимизировать, если компилятор может доказать, что значение неотрицательно, но это не всегда может происходить (и никогда в отладочной сборке).


Это часть кода из чужого вопроса пару часов go, поэтому мы можем предоставить недостающую первую инструкцию, которая загружает EAX из -0x10(%ebp)

 804847d:   8b 45 f0                mov    -0x10(%ebp),%eax    # EAX = n;  you left out this insn

 8048480:   89 c2                   mov    %eax,%edx
 8048482:   c1 fa 1f                sar    $0x1f,%edx         # edx = 0 or -1  (sign bit of n)

 8048485:   c1 ea 1c                shr    $0x1c,%edx         # edx = 0 or 15

Логическое смещение вправо на 28 всегда смещается в нули вместо копий знакового бита, так что -1 (0xffffffff) превращается в 15. (32-28 = 4 бита осталось внизу регистра). И, конечно, оставляет 0 как 0.

Таким образом, EDX = 0 или 15, для неотрицательных или отрицательных n.

 8048488:   01 d0                   add    %edx,%eax
 804848a:   83 e0 0f                and    $0xf,%eax                        
# only look at the lowest four bits
 804848d:   29 d0                   sub    %edx,%eax

Это добавляет / вычитает 15 (или 0) вокруг AND.

Это оптимизированный способ сделать -((-eax) & 15) для отрицательных входов, основанный на идентичности дополнения 2, которая -x = ~x + 1 в сочетании с &.

Обратите внимание, что x & 0xf приводит к числу в диапазоне [0..15], так что минус 15 всегда будет отрицательным или нулевым. Если входное значение уже было кратно 16 (даже если оно отрицательное, потому что так работает дополнение 2), младшие биты будут начинаться с 0. Так (n + 15) & 15 = 15 и 15-15 = 0, например, правильный результат для -128 % 16.

Для первоначально отрицательных входных данных это просто добавление / вычитание 0, аддитивная идентичность, которая оставляет значение без изменений. Таким образом, все это эквивалентно n & 0xf для неотрицательных n.

Все это эквивалентно (eax < 0) ? -((-eax) & 15) : (eax & 15)

G CC, использующим его также для n % 16 показывает, что он дает правильный результат для всех возможных int. (Нет значений n, которые имеют C неопределенное поведение для n % 16).


 804848f:   89 45 f0                mov    %eax,-0x10(%ebp)    # store back into where we loaded from

Итак, мы знаем, что это n %= 16;, потому что он сохраняет результат на вершине слота стека, из которого он загружен изначально.

 8048492:   83 7d f0 03             cmpl   $0x3,-0x10(%ebp)                 
# compare to 3

Да, все верно. Флаги установлены в соответствии с n - 3, поскольку синтаксис AT & T задом наперед. Но ZF устанавливается, если они равны.

...