吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8649|回复: 7
收起左侧

[CTF] RCTF - magic writeup

[复制链接]
无名侠 发表于 2018-5-21 22:47
本帖最后由 无名侠 于 2018-8-4 21:15 编辑

RCTF - magic

题目考点

  • 入口函数位置混淆
  • 数据爆破
  • 原始代码调用
  • vm还原
  • 简单方程求解
  • Linux 异常处理

概述

运行题目给定的二进制文件,有如下输出:

flag only appears at a specific time, range [2018-05-19 09:00, 2018-05-21 09:00)
Better luck next time :)

该题目有许多坑,比如IDA函数表中的main函数并不是真正的main函数、运行时间限制、vm逆向还原、类rc4算法的处理等等。

0x1 寻找入口函数

使用IDA分析该二进制文件,在函数表中可以找到main函数,在main函数的头部打下断点并调试运行,这时能够发现在该断点命中的时候屏幕上已经有信息输出,说明在main函数之前还执行了大量的代码。如何寻找真正的入口函数呢?方法有如下:main函数栈回溯、puts栈回溯。

使用上面两种方法,可以大概确定真实入口函数的位置。

通过调试发现,这程序有两步验证:1、时间验证,2、输入信息验证。

0x2 时间验证

1526911164965.png

time64函数返回当前时间戳,根据这段代码,返回的时间戳应该在(0x5AFFE78F,0x5B028A8F]这个范围内程序才会继续执行。

其后以时间戳做为随机数种子,取随机数对E1表异或运算,再通过4027ED这个函数对E1表进行一些变换,最终得到a3的值,并判断a3的值是否等于0x700.

在后面的代码中可知,如果time_data[0] ==0,那么程序将直接退出。因此,必须要寻找一个值使得a3满足a3==0x700。

我们观察满足题意的time返回值,0x5B028A8F - 0x5AFFE78F = 0x2A300,可以说是非常小的数字了,只需要遍历测试(0x5AFFE78F,0x5B028A8F]区间的所有值即可寻找到正确的time值。

由于a3的求值过程过于复杂,还原a3的算法是不可行的,可以采取调用原程序的方法,下面是暴力求解time的代码

typedef unsigned int(*test)();
static UINT time = 0x5AFFE78F + 1;
UINT myfun(int) { //通过这种形式遍历每一个time
        return time++;
}
char e1_copy[256] = {0};
int main()
{
        UINT64 * ptr1 = (UINT64 *)0x40A38C;//time64
        UINT64 * ptr2 = (UINT64 *)0x40A414; //srand
        UINT64 * ptr3 = (UINT64 *)0x40A3FC;//rand
        UINT64 * ptr4 = (UINT64 *)0x40A3DC;//memset
        HMODULE h = LoadLibraryA("D:\\magic.exe");
        memcpy(e1_copy, (void *)0x405020, 256); //备份E1表,重新运算的时候需要还原E1表
        test test1 = (test)0x402268;
        *ptr1 = (UINT64)myfun;
        *ptr2 = (UINT64)srand;
        *ptr3 = (UINT64)rand;
        *ptr4 = (UINT64)memset;
        UINT val;
        while (true)
        {
                memcpy((void *)0x405020, e1_copy, 256); // 重置E1表
                val =  test1();
                if (val != 0)
                {
                        printf("time:%x\nkey:%x", time -1 , val); //0x322ce7a4
                        //time:5b00e398
                        //key: 322ce7a4
                        break;
                }
        }
    return 0;
}

最终测得满足条件的time为:0x5b00e398

为方便调试第二阶段,在time64函数返回后的位置处下断点,每次停下来后手工修改RAX寄存器(此时保存的是time64函数的返回值)的值为0x5b00e398,之后即可顺利进入第二阶段的调试。

输入验证

1526912086459.png

rc44 是加解密同函数的算法,可直接跳过不看,其密钥为第一步中求解的time运算所得。

vm_start是进入vm虚拟机,Count是rc44对输入数据加密后的数据,也就是说输入数据加密后经过虚拟机运算要满足一定条件才行。

正向思维:vm_start(rc44(input)) == 1

逆向求解input :vm_start ===》 解出 参数count ===》rc44解密count

虚拟机典型特征是switch结构 + bytecode读取,vm_start完全符合。

通过单步调试分析出如下指令集:

high4:rd
low4:rs
0xAB :3
        AB 03 00
        Rb = p2
0xAA :3
        Rb = Rc
0xA9 :2
        Ra += Rb
0xA0 :2
        Rb = [Rb]
0xAC :2
        Ra &= Rb;
0xAE :2
        Ra ^= Rb;
0xAD :2
        Rb = ~LOBYTE(Rb);
0xAF : 3
        Ra = Ra == Rb;
0xA6 : 2
        jz rb

0xA7 : 2 jnz +Rb
        if r5:
                ip += Rb
0xCC

其中0xAF指令的代码并不在vm_start中,而是在异常处理函数中。

if ( v1 != 0xAF )
goto LABEL_43;
ra = byte_405340[v7] >> 4;
rb = byte_405340[v7] & 0xF;
if ( !setjmp(&unk_4099E0) )
byte_405340[v7] = ra / byte_405340[v7 + 1];  //这里有0除异常,byte_405340[v7 + 1]总是等于0
v7 += 2;

vm_start的开头有注册异常处理的代码

signal(8, sub_402930);

分析sub_402930处的代码

void __fastcall __noreturn sub_402930(int a1)
{
  if ( a1 == 8 )
  {
    signal(8, sub_402930);
    context[ra] = context[ra] == context[rb];
    sub_404716(&unk_4099E0, 0);
  }
  exit(1);
}

整理翻译

init:
r1 = xxx   常量数组
r2 = &input #vm_start参数

AB 03 00                mov r3,00 
AB 04 1A            mov r4,0x1A
AB 00 66                mov r0,0x66
s2:
AA 05 02                  mov r5,r2            
A9 53                         add r5,r3
A0 05                        mov r5,[r5]    
AB 06 CC                 mov r6,0xCC    
A9 56                         add r5,r6       
AB 06 FF                  mov r6,0xff
AC 56                         and r5,r6  
AE 50                         xor r5,r0                   
AD 00                         mov r0,~LOW8(r0)        #r0 = ~0x66
AA 06 05                 mov r6,r5                          #r6 = ((input[r3] + 0xCC) & 0xff) ^ 0x66
AA 05 01                 mov r5,r1                        
A9 53                         add r5,r3
A0 05                          mov r5,[r5]         #r5=xxx[r3]
AF 56 00                 cmp r5,r6                        
A7 01                         je s1                 if(((input[r3] + 0xCC) & 0xff) ^ 0x66 != xxx[r3]) break
CC              stop

s1:
A9 35           add r3,r5  #r5一定等于1     r3=r3+1
AA 05 03                mov r5,r3  #r5 = r3
AF 54 00                 cmp r5,r4  #size 
A6 D1           jne s2              
CC

根据这些代码写z3求解脚本:

from z3 import *
s = Solver()
input = [BitVec('a%d' % i,8) for i in range(0x1A)]
xxx = [0x89, 0xC1, 0xEC, 0x50, 0x97, 0x3A, 0x57, 0x59, 0xE4, 0xE6, 0xE4, 0x42, 0xCB, 0xD9, 0x08, 0x22, 0xAE, 0x9D, 0x7C, 0x07, 0x80, 0x8F, 0x1B, 0x45, 0x04, 0xE8, 0x00, 0x00, 0x00, 0x00] #常量表xxx
k = 0x66
for r3 in range(0x1A):
        s.add(((input[r3] + 0xCC) & 0xff) ^ k == xxx[r3])
        k = (~k) & 0xff
s.check()
res = s.model()
flag_rcxx = []
for i in range(0x1A):
        flag_rcxx.append(res[input[i]])
print flag_rcxx

# 解的flag_rcxx为如下数据
#[35, 140, 190, 253, 37, 215, 101, 244, 182, 179, 182, 15, 225, 116, 162, 239, 252, 56, 78, 210, 26, 74, 177, 16, 150, 165]

这时解出来的数据仅仅是vm_start输入的参数,还需再次调用rc44函数解密。

这里使用一个IDA的插件来替换内存数据

from idaapi import *
def PatchArr(dest, str):
    for i in range(len(str)):
        c = str[i]
        idc.PatchByte(dest+i, ord(c));
    print 'ok'

最后得到的数据实际是一部分flag:@ck_For_fun_02508iO2_2iOR}

为了得到完整的flag,将flag输入并回车就会发现有一个字符画输出,摘掉眼镜就能看出是rctf{

整理组合后就是....
1526913036413.png

magic.zip

128.68 KB, 下载次数: 35, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 12威望 +2 吾爱币 +23 热心值 +12 收起 理由
Dispa1r + 1 + 1 用心讨论,共获提升!
AnonymousEd + 1 + 1 值得研究,多谢写得如此详细
b1ngo + 1 + 1 我很赞同!
zzzlucas + 1 + 1 谢谢@Thanks!
KNulL0 + 1 + 1 谢谢@Thanks!
Anonymous、 + 2 + 1 已经处理,感谢您对吾爱破解论坛的支持!
Sound + 2 + 9 + 1 用心讨论,共获提升!
小权666 + 1 + 1 用心讨论,共获提升!
wmsuper + 2 + 1 我很赞同!
Pizza + 2 + 1 xmsl
hjm666 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
我要做大神 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Max_Wang 发表于 2018-7-10 20:16
想请教大大关于0x2时间验证部分,爆破得到时间戳的程序中,LoadLibrary真的可以加载exe文件嘛,为什么我运行这个爆破程序的时候会报错呢,我使用的编辑器是VS,win10_64位操作系统,排除路径不正确导致的LoadLibrary加载程序失败。
Setsuro 发表于 2018-5-21 23:15
wisoft 发表于 2018-5-22 09:08
yssun 发表于 2018-5-22 09:10

学习,感谢分享
小权666 发表于 2018-5-22 10:37
楼主你辛苦了
七八鼓捣A 发表于 2020-3-31 15:09
大佬请问您有资料吗,那些逆向课,比如“链家apk调试+so签名验证专题+调试后门”
请问在哪看,感谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 16:08

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表