MyU*_*358 2 python debugging assembly reverse-engineering self-modifying
问题:
我制作了一个自我修改其字节之一的elf可执行文件.它只是将1更改为1.当我正常运行可执行文件时,我可以看到更改成功,因为它完全按预期运行(更多关于更进一步下来).调试时出现问题:调试器(使用radare2)在查看修改后的字节时返回错误的值.
语境:
我做了一个逆向工程挑战,受到了最小精灵的启发.您可以在那里看到"源代码"(如果您甚至可以称之为):https://pastebin.com/Yr1nFX8W.
汇编和执行:
nasm -f bin -o tinyelf tinyelf.asm
chmod +x tinyelf
./tinyelf [flag]
Run Code Online (Sandbox Code Playgroud)
如果标志是正确的,则返回0.任何其他值表示您的答案是错误的.
./tinyelf FLAG{wrong-flag}; echo $?
Run Code Online (Sandbox Code Playgroud)
...输出"255".
!解决方案SPOILERS!
可以静态反转它.完成后,您会发现通过执行此计算可以找到标志中的每个字符:
flag[i] = b[i] + b[i+32] + b[i+64] + b[i+96];
Run Code Online (Sandbox Code Playgroud)
...其中i是字符的索引,b是可执行文件本身的字节.这是一个ac脚本,可以在没有调试器的情况下解决问题:
#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");
}
Run Code Online (Sandbox Code Playgroud)
您可以看到我的求解器处理特殊情况:当i == 10时,c = 0.这是因为它是执行期间修改的字节的索引.运行求解器并用它调用tinyelf我得到:
FLAG{Wh3n0ptiMizaTioNGOesT00F4r}
./tinyelf FLAG{Wh3n0ptiMizaTioNGOesT00F4r} ; echo $?
Run Code Online (Sandbox Code Playgroud)
输出: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)
Run Code Online (Sandbox Code Playgroud)
它在命令上放置一个断点,用于将输入字符与预期字符进行比较,然后得到与(al)相比较的内容.这应该工作.然而,这是输出:
FLAG {} Wh3n0tiMizaioNGOesT00F4r
2个不正确的值,其中一个位于索引10(修改后的字节).很奇怪,也许是radare2的错误?让我们下一步尝试独角兽(一个cpu模拟器):
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
Run Code Online (Sandbox Code Playgroud)
这次求解器输出:FLAG {Wh3n0otiMizaTioNGOesT00F4r}
请注意索引10:'o'而不是'p'.这是一个错误的错误,字节被修改.这不是巧合,对吧?
任何人都知道为什么这两个脚本都不起作用?谢谢.
radare2没有问题,但您对程序的分析不正确,因此您编写的代码错误地处理了此RE.
让我们开始吧
当i == 10时,c = 0.这是因为它是执行期间修改的字节的索引.
这是部分正确的.它在开始时设置为零,但在每轮之后都有以下代码:
xor al, byte [esi]
or byte [ebx + 0xa], al
Run Code Online (Sandbox Code Playgroud)
所以,让我们了解这里发生了什么.al是当前计算的标志字符,并esi指向作为参数输入的FLAG,并且[ebx + 0xa]我们当前有0(在开头设置),因此0xa仅当计算的标志字符char等于时,索引处的字符将保持为零一个在参数中,因为你正在运行带有假标志的r2,这从第6个字符开始出现问题,但是你在第一个see在索引10看到的结果.为了减轻我们需要更新你的脚本一点点.
eax = r2.cmd('dr? al')
c = int(eax, 16)
r2.cmd("ds 2")
r2.cmd("dr al = 0x0")
Run Code Online (Sandbox Code Playgroud)
我们在这里做的是,brekpoint被击中,我们读到计算的标志字符后,我们提出两项指令还(到达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值的偏移量.
这是打破你的第二个字符的代码
Run Code Online (Sandbox Code Playgroud)r2.cmd('db 0x01002051')
是的 - 你的断点.你设置为在该地址处断开,并且软断点正在将0xcc内存地址放入存储器地址中,因此当读取第18个字符的第3个字节的操作码击中它所获得的那个点时0x5b(原始值)0xcc.所以要解决这个问题,我们需要纠正这个计算.这可能是以更智能/更优雅的方式完成的,但我选择了一个简单的解决方案,所以我只是这样做:
if i == 17:
c -= (0xcc-0x5b)
Run Code Online (Sandbox Code Playgroud)
只是通过在代码中放置一个断点来无意中添加了减去.
最终代码:
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)
Run Code Online (Sandbox Code Playgroud)
打印正确的标志:
FLAG {} Wh3n0ptiMizaTioNGOesT00F4r
至于Unicorn你没有设置断点,所以问题2消失了,而第10个索引的1分是由于与r2相同的原因.