Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/332.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 调试器和cpu仿真器don';t检测自修改代码_Python_Debugging_Assembly_Reverse Engineering_Self Modifying - Fatal编程技术网

Python 调试器和cpu仿真器don';t检测自修改代码

Python 调试器和cpu仿真器don';t检测自修改代码,python,debugging,assembly,reverse-engineering,self-modifying,Python,Debugging,Assembly,Reverse Engineering,Self Modifying,问题: 我制作了一个elf可执行文件,可以自我修改其中一个字节。它只是将0更改为1。当我正常运行可执行文件时,我可以看到更改是成功的,因为它完全按照预期运行(更进一步)。调试时会出现问题:调试器(使用radare2)在查看修改的字节时返回错误的值 上下文: 我做了一个逆向工程挑战,灵感来自。您可以在那里看到“源代码”(如果您可以这样称呼它的话) 要组装和执行: nasm -f bin -o tinyelf tinyelf.asm chmod +x tinyelf ./tinyelf [flag]

问题:

我制作了一个elf可执行文件,可以自我修改其中一个字节。它只是将0更改为1。当我正常运行可执行文件时,我可以看到更改是成功的,因为它完全按照预期运行(更进一步)。调试时会出现问题:调试器(使用radare2)在查看修改的字节时返回错误的值

上下文:

我做了一个逆向工程挑战,灵感来自。您可以在那里看到“源代码”(如果您可以这样称呼它的话)

要组装和执行:

nasm -f bin -o tinyelf tinyelf.asm
chmod +x tinyelf
./tinyelf [flag]
如果标志正确,则返回0。任何其他值都表示您的答案是错误的

./tinyelf FLAG{wrong-flag}; echo $?
。。。输出“255”

!解决方案破坏者

可以静态地反转它。完成此操作后,您将发现通过执行此计算找到标志中的每个字符:

flag[i] = b[i] + b[i+32] + b[i+64] + b[i+96];
…其中i是字符的索引,b是可执行文件本身的字节。下面是一个c脚本,它在不使用调试器的情况下解决了该问题:

#include <stdio.h>

int main()
{
    char buffer[128];
    FILE* fp;

    fp = fopen("tinyelf", "r");
    fread(buffer, 128, 1, fp);

    int i;
    char c = 0;
    for (i = 0; i < 32; i++) {
        c = buffer[i];

        // handle self-modifying code
        if (i == 10) {
            c = 0;
        }

        c += buffer[i+32] + buffer[i+64] + buffer[i+96];
        printf("%c", c);
    }
    printf("\n");
}
输出:0。成功

很好,现在让我们尝试使用python和radare2动态解决它:

import r2pipe

r2 = r2pipe.open('./tinyelf')

r2.cmd('doo FLAG{AAAAAAAAAAAAAAAAAAAAAAAAAA}')
r2.cmd('db 0x01002051')

flag = ''
for i in range(0, 32):
    r2.cmd('dc')
    eax = r2.cmd('dr? al')
    c = int(eax, 16)
    flag += chr(c)

print('\n\n' + flag)
它在命令上放置一个断点,用于将输入字符与预期字符进行比较,然后获取与之比较的内容(al)。这应该行得通。然而,以下是结果:

标志{Wh3n0�蒂米扎�ioNGOesT00F4r}

2个不正确的值,其中一个位于索引10(修改的字节)。奇怪,也许是雷达2的窃听器?接下来,让我们试试unicorn(cpu模拟器):

这一次,解算器输出:标志{Wh3n0otiMizaTioNGOesT00F4r}

注意,在索引10处,用“o”代替“p”。这是一个off-by-1错误,正好是字节被修改的地方。这不可能是巧合吧


有人知道为什么这两个脚本都不起作用吗?谢谢。

radare2没有问题,但您对程序的分析不正确,因此您编写的代码处理此问题的方式不正确

让我们从

当i=10时,c=0。这是因为它是在执行期间修改的字节索引

这部分是正确的。它在开始时设置为零,但在每一轮之后都有以下代码:

xor al, byte [esi]                               
or byte [ebx + 0xa], al
让我们了解这里发生了什么
al
是当前计算的标志字符,
esi
指向作为参数输入的标志,在
[ebx+0xa]
处,我们当前有0(在开头设置),因此,只有当计算的标志char与参数中的标志char相等时,索引
0xa
处的char才会保持为零,并且因为运行r2时使用了一个伪标志,这从第6个char开始就是一个问题,但您首先看到的结果是� 在指数10。为了缓解这种情况,我们需要稍微更新您的脚本

eax = r2.cmd('dr? al')
c = int(eax, 16)
r2.cmd("ds 2")
r2.cmd("dr al = 0x0")
我们在这里做的是,在点击brekpoint并读取计算的标志字符后,我们进一步移动两条指令(以达到
0x01002054
),然后将
al
设置为
0x0
,以模拟我们在[esi]的字符实际上与计算的字符相同(因此在这种情况下,
xor
将返回
0
)。通过这样做,我们将
0xa
的值保持为零

现在是第二个字符。这个RE很棘手;)-它会自己读,如果你忘记了,你可能会以这样的情况结束。让我们试着分析为什么这个角色是关闭的。这是标志的第18个字符(所以从0开始索引是17),如果我们检查从二进制读取的字符索引公式,我们注意到索引是:
17(dec)=11(hex)
17+32=49(dec)=31(hex)
17+64=81(dec)=51(hex)
17+96=113(dec)=71(hex)
。但是这个
51(十六进制)
看起来非常熟悉?我们以前不是在什么地方见过吗?是的,它是设置断点以读取
al
值的偏移量

这是中断第二个字符的代码

是的-你的断点。您将设置为在该地址中断,而软断点将在内存地址中放入一个
0xcc
,因此当读取第18个字符的第3字节的操作码到达该位置时,它不会得到
0x5b
(原始值),而是得到
0xcc
。为了解决这个问题,我们需要修正这个计算。在这里,可能可以用更聪明/更优雅的方式来完成,但我选择了一个简单的解决方案,所以我只是这样做:

if i == 17:
  c -= (0xcc-0x5b)
Just subtract是通过在代码中放置断点无意中添加的

最终代码:

import r2pipe

r2 = r2pipe.open('./tinyelf')
print r2

r2.cmd("doo FLAG{AAAAAAAAAAAAAAAAAAAAAAAAAA}")
r2.cmd("db 0x01002051")

flag = ''
for i in range(0, 32):
  r2.cmd("dc")
  eax = r2.cmd('dr? al')
  c = int(eax, 16)   
  if i == 17:
    c -= (0xcc-0x5b)
  r2.cmd("ds 2")
  r2.cmd("dr al = 0x0")
  flag += chr(c)

print('\n\n' + flag)
将打印正确的标志:

标志{WH3N0OptimizationGOEST00F4R}


至于独角兽,您没有设置断点,因此问题2消失了,第10个索引的off-by-1原因与r2相同。

这里可能涉及太多的复杂性。既然我还没有完全阅读你们的问题,我只给你们两分钱:若你们有理由相信你们的工具不能很好地处理SMC,为什么不用一个非常简单的例子来明确地测试它呢?例如,您可以通过翻转单个位将
jz
转换为
jnz
。这将以一种可测量的方式改变流程。这两种工具过去都使用过其他自修改代码,因此它们通常能够处理SMC。我不知道为什么在这种情况下他们不能。确保在执行自修改和修改代码的代码之间有一条控制传输指令。英特尔要求这样做是为了保证修改后的代码被识别。有CTI有效吗?在执行修改后的代码之前,我会使用很多jmp,所以我认为这不是问题所在。。。谢谢你的建议!较旧的x86 CPU有一个预取缓冲区,用于在执行指令之前将指令读入其中。任何改变(E)IP的控制传输指令都将清除预取缓冲区,并开始用新的指令集填充它。苏西
r2.cmd('db 0x01002051')
if i == 17:
  c -= (0xcc-0x5b)
import r2pipe

r2 = r2pipe.open('./tinyelf')
print r2

r2.cmd("doo FLAG{AAAAAAAAAAAAAAAAAAAAAAAAAA}")
r2.cmd("db 0x01002051")

flag = ''
for i in range(0, 32):
  r2.cmd("dc")
  eax = r2.cmd('dr? al')
  c = int(eax, 16)   
  if i == 17:
    c -= (0xcc-0x5b)
  r2.cmd("ds 2")
  r2.cmd("dr al = 0x0")
  flag += chr(c)

print('\n\n' + flag)