170913 逆向-问鼎杯题库(dice game)

1625-5 王子昂 总结《2017年9月12日》 【连续第345天总结】
A. 问鼎杯题库-逆向两题
B.

Beat our dice game and get the flag

C++写的,无壳
运行提示要求掷五次骰子,需要结果为3-1-3-3-7
很明显,是正常运行下不可能出现的结果

除了Enter外不接受输入,观察IDA
这里写图片描述
发现进行了大量处理,但没有字符串的显示,说明这里要不是混淆要不是加密
Shift+f12查找字符串才发现了真正的main,其实就在main的最后Return的地方调用的WinMain
最后对v89进行了多次处理,flag输出的是v88,那么向上要追踪v88或v89

总共进行了五次类似结构的判断,取其中一层分析
每段随机产生一个值,进行Case结构的判断,如果正确会对v89进行修改;同时通过前后时间比对是否超过两秒来反作弊

关键代码:

 v84 = time(0);//取运行前时间
  v86 = rand() % 6 + 1;
  if ( v86 == 1 )
    std::operator<<<char,std::char_traits<char>,std::allocator<char>>(&std::cout, &v82);
  if ( v86 == 2 )
  {
    v67 = 1;
    std::operator<<<char,std::char_traits<char>,std::allocator<char>>(&std::cout, &v81);
  }
  if ( v86 == 3 )
  {
    v67 = 1;
    std::operator<<<char,std::char_traits<char>,std::allocator<char>>(&std::cout, &v80);
  }
  if ( v86 == 4 )
  {
    v67 = 1;
    std::operator<<<char,std::char_traits<char>,std::allocator<char>>(&std::cout, &v79);
  }
  if ( v86 == 5 )
  {
    v67 = 1;
    std::operator<<<char,std::char_traits<char>,std::allocator<char>>(&std::cout, &v78);
  }
  if ( v86 == 6 )
  {
    v67 = 1;
    std::operator<<<char,std::char_traits<char>,std::allocator<char>>(&std::cout, &v77);
  }
  if ( v86 == 3 )//所需数字
  {
    v67 = 1;
    v10 = std::operator<<<std::char_traits<char>>((int)&std::cout, "[*] You rolled a three! Good!");
    v11 = std::ostream::operator<<(v10, std::endl<char,std::char_traits<char>>);
    std::ostream::operator<<(v11, std::endl<char,std::char_traits<char>>);
    v89 *= 2;//正确时对目标值的操作
    std::string::operator=((std::string *)&v88, &byte_444240);
    v16 = time(0);//运行后时间
    v83 = v16;
    v85 = v16 - v84;
    if ( v16 - v84 > 2 )                        // 作弊检测,通过两次time()函数所取时间是否大于两秒判断
      v89 *= 2;//如果作弊则修改目标值
    v67 = 1;
    v17 = std::operator<<<std::char_traits<char>>(
            (int)&std::cout,
            "[*] Next you will need to throw a one, press enter to throw a dice!");
最简单的方法

直接通过OD/IDA进行爆破,把每次判断时的跳转给转掉,就能得到正确的flag
注意作弊检测的地方没有字符串标识,cmp [ebp-0x30], 2的地方也要爆破过,否则在最后异或的时候会出错

if ( std::string::find((std::string *)&v88, "ebCTF", 0) == -1 )// 作弊检测,如果未出现正确字符串说明被检测到作弊,报错
            {
              v67 = 1;
              v59 = std::ostream::operator<<(&std::cout, std::endl<char,std::char_traits<char>>);
              v60 = std::operator<<<std::char_traits<char>>(
                      v59,
                      "[!] It seems you did something wrong :( No flag for you.");

这里写图片描述
这里写图片描述

更优雅的方法

跟踪v89被处理的流程,进行还原生成flag
通过OD发现最后flag是用一个内存中的字符串被多次异或处理得到的
在IDA中可以看到这个字符串的指针是v88,在输出之前与v91字符串逐个异或
v88的赋值语句为std::string::operator=((std::string *)&v91, &byte_444240);
向上溯源发现一共有三次对v91字符串的操作,分别是
1.将字符串赋给v91
std::string::operator=((std::string *)&v91, &byte_444309);

2.将字符串与v90逐个异或
v90的赋值在开头处:

if ( isDebuggerPresent() )                    // 检查是否被反调试
    v90 = 66;
  else
    v90 = 16;

看来是反调试措施,不过不知道为什么OD没有被触发……
查了一下该函数只针对Ring3(用户级)的调试器,可能是OD有插件搞定它了?
毕竟它只是一个可怜的会被调试器K.O.掉的反调试WinApi……╮(╯_╰)╭

3.将v91字符串与v89逐个异或

            for ( i = 0; ; ++i )
            {
              v67 = 1;
              v50 = std::string::size((std::string *)&v91);//取长度
              if ( i >= v50 )
                break;
              v51 = (_BYTE *)std::string::operator[]((std::string *)&v91, i);//取v91的第i个字符
              *v51 ^= v89;//逐个异或
            }

最后将v88与v91逐个字符异或
得到的v88字符串如果包含ebCTF则说明流程正确,输出flag
还原脚本:
其中middle和flag由IDC脚本从内存中dump下来得到

#v91
middle=[2,55,15,53,15,60,21,7,60,48,42,48,85,18,55,21,30,53,1,81]

#v88
flag=[19,33,56,21,61,51,87,71,45,39,106,115,68,5,38,89,92,121,23,68,69,119,26,117,73,125,5,74,120,116,106,112,66,2,113,5,15,34,8]

#v90
debug = 16

v89 = 6
v89 *= 2
v89 += 4
v89 *= 3
v89 += 2
v89 *= 2
#混淆
# v89 *= 50
# v89 /= 50
# v89 += 65
# v89 -= 65
# v89 *= 42
# v89 /= 42
for i in range(len(middle)):
    middle[i] ^= debug
    middle[i] ^= v89
for i in range(len(flag)):
    flag[i] ^= middle[i%len(middle)]
    print(chr(flag[i]),end='')

ebCTF{64ec47ece868ba34a425d90044cd2dec}

从所给文件中找flag

下载下来是一个无后缀名的文件,用UltraEdit打开发现文件头是7z
添加zip后缀名,成功拖出一个ELF文件
拖入IDA:
这里写图片描述
一毛一样的掷骰子套路,连数字都不带变的
一样随机生成,依次检查数字,通过比较时间来反作弊
不过这次检查过程中没有幺蛾子,直接通过一个函数sub_4006B6生成的flag并输出
虽然在Linux中没有OD那么好用的动态调试器了,不过IDA的远程动态调试也勉勉强强可以用,直接修改rip至0x4010d5运行flag生成函数即可
逆向也是可以的,不过这次太麻烦了而且中间有一堆goto,全盘复制下来直接编辑运行就行了应该╮(╯_╰)╭

C. 明日计划
问鼎杯题库逆向

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值