如何暴力破解有损和例程?

Posted

技术标签:

【中文标题】如何暴力破解有损和例程?【英文标题】:How to bruteforce a lossy AND routine? 【发布时间】:2020-12-08 17:56:49 【问题描述】:

我想知道是否有任何标准方法可以通过蛮力来反转 AND 例程。 例如我有以下转换:

MOV(eax, 0x5b3e0be0)  <- Here we move 0x5b3e0be0 to EDX.
MOV(edx, eax)  # Here we copy 0x5b3e0be0 to EAX as well.
SHL(edx, 0x7)  # Bitshift 0x5b3e0be0 with 0x7 which results in 0x9f05f000
AND(edx, 0x9d2c5680)  # AND 0x9f05f000 with 0x9d2c5680 which results in 0x9d045000
XOR(edx, eax)  # XOR 0x9d045000 with original value 0x5b3e0be0 which results in 0xc63a5be0

我的问题是如何暴力破解并反转此例程(即将 0xc63a5be0 转换回 0x5b3e0be0)

我的一个想法(没有奏效)是使用 PeachPy 实现:

#Input values
MOV(esi, 0xffffffff) < Initial value to AND with, which will be decreased by 1 in a loop.
MOV(cl, 0x1) < Initial value to SHR with which will be increased by 1 until 0x1f.
MOV(eax, 0xc63a5be0) < Target result which I'm looking to get using the below loop.
MOV(edx, 0x5b3e0be0) < Input value which will be transformed.

sub_esi = peachpy.x86_64.Label()
with loop:
    #End the loop if ESI = 0x0
    TEST(esi, esi)
    JZ(loop.end)
    #Test the routine and check if it matches end result.
    MOV(ebx, eax)
    SHR(ebx, cl)
    TEST(ebx, ebx)
    JZ(sub_esi)
    AND(ebx, esi)
    XOR(ebx, eax)
    CMP(ebx, edx)
    JZ(loop.end)
    #Add to the CL register which is used for SHR.
    #Also check if we've reached the last potential value of CL which is 0x1f
    ADD(cl, 0x1)
    CMP(cl, 0x1f)
    JNZ(loop.begin)

    #Decrement ESI by 1, reset CL and restart routine.
    peachpy.x86_64.LABEL(sub_esi)
    SUB(esi, 0x1)
    MOV(cl, 0x1)
    JMP(loop.begin)

#The ESI result here will either be 0x0 or a valid value to AND with and get the necessary result.
RETURN(esi)

也许您可以为此推荐一篇文章或一本书?

【问题讨论】:

您可以将其建模为线性方程组,并使用高斯消元法轻松求解。回想一下,and 是乘法,xor 是 Z2(位字段)中的加法。 【参考方案1】:

不是有损的,最后的运算是异或。 整个例程可以在 C 中建模为

#define K 0x9d2c5680
uint32_t hash(uint32_t num)

  return num ^ ( (num << 7) & K);

现在,如果我们有两个位 xy 并且操作 x XOR y,当 y 为零,结果为 x。 因此,给定两个数字 n1n2 并考虑它们的异或,与 n2中的零配对的位或 n1 > 会得到结果不变(其他的会被翻转)。

因此,在考虑num ^ ( (num &lt;&lt; 7) &amp; K) 时,我们可以将num 识别为n1,将(num &lt;&lt; 7) &amp; K 识别为n2。 由于 n2 是 AND,我们可以看出它必须至少具有与 K 相同的零位。 这意味着num 的每一位对应于常量 K 中的零位将使其在结果中保持不变。 因此,通过从结果中提取这些位,我们已经有了一个部分反函数:

/*hash & ~K extracts the bits of hash that pair with a zero bit in K*/
partial_num = hash & ~K

从技术上讲,因子num &lt;&lt; 7 还会在 AND 的结果中引入其他零。我们确定最低 7 位必须为零。 然而 K 已经有最低 7 位零,所以我们不能利用这个信息。 所以我们将在这里只使用 K,但如果它的值不同,您需要考虑 AND(在实践中,这意味着将 K的低 7 位归零>)。

这给我们留下了 13 位未知位(对应于在 K 中设置的位)。 如果我们暂时忘记 AND,我们就会有 x ^ (x &lt;&lt; 7) 的意思

hi = numi for i 从 0 到 6(含) hi = numi ^ numi-7 for i 从 7 到 31(含) (第一行是因为右手的低7位为零)

由此,从 h7 开始向上,我们可以将 num7 检索为 h7 ^ num0 = h7 ^ h0. 从第 7 位开始,等式不起作用,我们需要使用 numk(对于合适的 k),但幸运的是我们已经在上一步中计算了它的值(这就是为什么我们从低到高开始)。

AND 对此所做的只是限制索引 i 运行的值,特别是仅限于在 K 中设置的位。

所以要填写剩下的 13 位,你必须要做的:

part_num7 = h7 ^ part_num0 part_num9 = h9 ^ part_num2 part_num12 = h12 ^ part_num5 ... part_num31 = h31 ^ part_num24

请注意,我们利用了 part_num0..6 = h0..6 这一事实。

这是一个反转函数的 C 程序:

#include <stdio.h>
#include <stdint.h>


#define BIT(i, hash, result) ( (((result >> i) ^ (hash >> (i+7))) & 0x1) << (i+7) )
#define K 0x9d2c5680

uint32_t base_candidate(uint32_t hash)

  uint32_t result = hash & ~K;

  result |= BIT(0, hash, result);
  result |= BIT(2, hash, result);
  result |= BIT(3, hash, result);
  result |= BIT(5, hash, result);
  result |= BIT(7, hash, result);
  result |= BIT(11, hash, result);
  result |= BIT(12, hash, result);
  result |= BIT(14, hash, result);
  result |= BIT(17, hash, result);
  result |= BIT(19, hash, result);
  result |= BIT(20, hash, result);
  result |= BIT(21, hash, result);
  result |= BIT(24, hash, result);

  return result;


uint32_t hash(uint32_t num)

  return num ^ ( (num << 7) & K);




int main()


  uint32_t tester = 0x5b3e0be0;
  uint32_t candidate = base_candidate(hash(tester));

  printf("candidate: %x, tester %x\n", candidate, tester);
  
  return 0;


【讨论】:

我不会说谎,我一定已经阅读了这个解释至少 50 次而没有得到它。最终我意识到缺少了什么。找到丢失位的方法是:partial_num = hash & ~K unknown_partial_num0 = num7(the original input hash 0xc63a5be0) ^ (partial_num0) 这对我来说很关键,因为我一直在想 part_num7 = h7 ^ part_num0 即 h7 = part_num7 ^ part_num0 这显然不是真的。尽管如此,一个惊人的答案。非常感谢【参考方案2】:

由于最初的问题是如何“暴力破解”而不是解决这里的问题,我最终想出了一个同样有效的方法。显然它容易出错,具体取决于输入(可能超过 1 个结果)。

from peachpy import *
from peachpy.x86_64 import *

input = 0xc63a5be0

x = Argument(uint32_t)
with Function("DotProduct", (x,), uint32_t) as asm_function:
    LOAD.ARGUMENT(edx, x)  # EDX = 1b6fb67c
    MOV(esi, 0xffffffff)

    with Loop() as loop:
        TEST(esi,esi)
        JZ(loop.end)
        MOV(eax, esi)
        SHL(eax, 0x7)
        AND(eax, 0x9d2c5680)
        XOR(eax, esi)
        CMP(eax, edx)
        JZ(loop.end)
        SUB(esi, 0x1)
        JMP(loop.begin)
    RETURN(esi)

#Read Assembler Return
abi = peachpy.x86_64.abi.detect()
encoded_function = asm_function.finalize(abi).encode()
python_function = encoded_function.load()
print(hex(python_function(input)))

【讨论】:

以上是关于如何暴力破解有损和例程?的主要内容,如果未能解决你的问题,请参考以下文章

如何防止暴力破解?

BurpSuite 暴力破解之绕过 token

如何暴力破解 RSA 私钥的密码?

暴力破解与验证码安全

WINDOS 远程桌面总被人暴力破解 如何限制登录次数

暴力破解原理与过程详解