首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >调试器和cpu模拟器不会检测到自修改的代码。

调试器和cpu模拟器不会检测到自修改的代码。
EN

Stack Overflow用户
提问于 2017-06-29 15:10:51
回答 1查看 367关注 0票数 2

问题:

我做了一个精灵的可执行文件自我修改它的一个字节。当我正常运行可执行文件时,我可以看到更改是成功的,因为它的运行与预期完全相同(更详细的介绍)。调试时会出现问题:调试器(使用radare2)在查看修改后的字节时返回错误的值。

上下文:

我在最小精灵的启发下,做了一个逆向工程挑战。您可以在那里看到“源代码”(如果您甚至可以这样称呼它):https://pastebin.com/Yr1nFX8W

组装和执行:

代码语言:javascript
复制
nasm -f bin -o tinyelf tinyelf.asm
chmod +x tinyelf
./tinyelf [flag]

如果标志正确,则返回0。任何其他价值都意味着你的答案是错误的。

代码语言:javascript
复制
./tinyelf FLAG{wrong-flag}; echo $?

..。产出"255“。

!解决方案破坏者!

这是有可能扭转它静态。完成该操作后,您将通过执行以下计算来发现标志中的每个字符:

代码语言:javascript
复制
flag[i] = b[i] + b[i+32] + b[i+64] + b[i+96];

...where i是字符的索引,b是可执行文件本身的字节。下面是一个c++脚本,它可以在没有调试器的情况下解决问题:

代码语言:javascript
复制
#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");
}

您可以看到,我的求解器处理一个特殊情况:当我的== 10,c= 0。这是因为它是在执行过程中修改的字节的索引。运行求解器,用它调用自己,我得到:

代码语言:javascript
复制
FLAG{Wh3n0ptiMizaTioNGOesT00F4r}
./tinyelf FLAG{Wh3n0ptiMizaTioNGOesT00F4r} ; echo $?

产出: 0。成功!

很好,让我们现在使用python和radare2动态地解决这个问题:

代码语言:javascript
复制
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�tiMiza�ioNGOesT00F4r}

2不正确的值,其中一个在索引10处(修改后的字节)。奇怪,也许是radare2的窃听器?下面让我们试试unicorn ( cpu仿真器):

代码语言:javascript
复制
from unicorn import *
from unicorn.x86_const import *
from pwn import *

ADDRESS = 0x01002000

mu = Uc(UC_ARCH_X86, UC_MODE_32)
code = bytearray(open('./tinyelf').read())

mu.mem_map(ADDRESS, 20 * 1024 * 1024)

mu.mem_write(ADDRESS, str(code))

mu.reg_write(UC_X86_REG_ESP, ADDRESS + 0x2000)
mu.reg_write(UC_X86_REG_EBP, ADDRESS + 0x2000)

mu.mem_write(ADDRESS + 0x2000, p32(2)) # argc
mu.mem_write(ADDRESS + 0x2000 + 4, p32(ADDRESS + 0x5000)) # argv[0]
mu.mem_write(ADDRESS + 0x2000 + 8, p32(ADDRESS + 0x5000)) # argv[1]
mu.mem_write(ADDRESS + 0x5000, "x" * 32)

flag = ''

def hook_code(uc, address, size, user_data):
    global flag
    eip = uc.reg_read(UC_X86_REG_EIP)

    if eip == 0x01002051:
        c = uc.reg_read(UC_X86_REG_EAX) & 0x7f
        #print(str(c) + " " + chr(c))
        flag += chr(c)

mu.hook_add(UC_HOOK_CODE, hook_code)

try:
    mu.emu_start(0x01002004, ADDRESS + len(code))
except Exception:
    print flag

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

注意在索引10处:'o‘而不是'p’。在字节被修改的地方,这是一个1的错误。那不可能是巧合对吧?

有人知道为什么这两种脚本都不能工作吗?谢谢。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-06-30 23:19:16

radare2没有问题,但是您对程序的分析是不正确的,因此您编写的代码不正确地处理这个RE。

让我们从

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

这部分是对的。它在开始时被设置为零,但是在每一轮之后都有这样的代码:

代码语言:javascript
复制
xor al, byte [esi]                               
or byte [ebx + 0xa], al

所以让我们了解一下这里发生了什么。al是当前计算出的标志的字符,esi指向作为参数输入的标志,在[ebx + 0xa],我们现在有0(设置在开头),所以索引0xa的字符只有在计算的标志char等于参数中的字符时才会保持为零,并且由于您使用假标志运行r2,所以从第6个字符开始出现问题,但是在索引10的第一个�上会看到这个问题的结果。为了减轻我们需要稍微更新脚本的问题。

代码语言:javascript
复制
eax = r2.cmd('dr? al')
c = int(eax, 16)
r2.cmd("ds 2")
r2.cmd("dr al = 0x0")

我们在这里所做的是,在击中brekpoint之后,我们读取计算出的标志char,我们进一步移动两个指令(以到达0x01002054),然后将al设置为0x0,以模拟我们在esi上的字符实际上与计算的字符相同(因此在这种情况下xor将返回0 )。通过这样做,我们将0xa的值保持为零。

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

这是破坏你的第二个字符的代码

r2.cmd('db 0x01002051')

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

代码语言:javascript
复制
if i == 17:
  c -= (0xcc-0x5b)

在代码中添加断点是无意中添加的。

最后代码:

代码语言:javascript
复制
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)

它打印正确的标志:

标志{Wh3n0ptiMizaTioNGOesT00F4r}

至于Unicorn,您没有设置断点,所以问题2就消失了,第10个索引上的1的差是由于与r2相同的原因。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44828937

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档