吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2338|回复: 3
收起左侧

[CTF] [2019红帽杯]childRE 分析

  [复制链接]
Tokameine 发表于 2021-11-20 13:18
​
        对我来说十分特殊也有趣的一题,特此记录。流程并非难以理解,但有些需要注意的点。
        无壳,可以直接用IDA分析,但由于存在一些动态变量,一旦开始动调,代码将会变得更难理解,因此目前只用静态调试来审计
[C] 纯文本查看 复制代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rax
  const CHAR *v5; // r11
  __int64 v6; // r10
  int v7; // er9
  const CHAR *v8; // r10
  __int64 v9; // rcx
  __int64 v10; // rax
  int result; // eax
  unsigned int v12; // ecx
  __int64 v13; // r9
  __int128 v14[2]; // [rsp+20h] [rbp-38h] BYREF

  v14[0] = 0i64;
  v14[1] = 0i64;
  sub_140001080("%s");
  v3 = -1i64;
  do
    ++v3;
  while ( *((_BYTE *)v14 + v3) );
  if ( v3 != 31 )
  {
    while ( 1 )
      Sleep(0x3E8u);
  }
  v4 = sub_140001280(v14);
  v5 = name;
  if ( v4 )
  {
    sub_1400015C0(*(_QWORD *)(v4 + 8));
    sub_1400015C0(*(_QWORD *)(v6 + 16));
    v7 = dword_1400057E0;
    v5[dword_1400057E0] = *v8;
    dword_1400057E0 = v7 + 1;
  }
  UnDecorateSymbolName(v5, outputString, 0x100u, 0);
  v9 = -1i64;
  do
    ++v9;
  while ( outputString[v9] );
  if ( v9 == 62 )
  {
    v12 = 0;
    v13 = 0i64;
    do
    {
      if ( a1234567890Qwer[outputString[v13] % 23] != *(_BYTE *)(v13 + 0x140003478i64) )
        _exit(v12);
      if ( a1234567890Qwer[outputString[v13] / 23] != *(_BYTE *)(v13 + 0x140003438i64) )
        _exit(v12 * v12);
      ++v12;
      ++v13;
    }
    while ( v12 < 0x3E );
    sub_140001020("flag{MD5(your input)}\n");
    result = 0;
  }
  else
  {
    v10 = sub_1400018A0(std::cout);
    std::ostream::operator<<(v10, sub_140001A60);
    result = -1;
  }
  return result;
}

         第57行是明显的显示验证结果,则能够判明第56行的while为判断条件的遍历;IDA将 ‘!=’ 后面的内容分析成地址而不是数组,但不妨碍提取数据
[C] 纯文本查看 复制代码
	char fp[] = {"1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:\"ZXCVBNM<> ? zxcvbnm, . /"};//a1234567890Qwer
	char tp[] = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&";//0000000140003478
	char kp[] = "55565653255552225565565555243466334653663544426565555525555222";//0000000140003438


        而outputString则是我们目前需要求取的数据,它只起到了索引的作用,逆算法不难写出:
[C++] 纯文本查看 复制代码
int main()
{
	char fp[] = {"1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:\"ZXCVBNM<> ? zxcvbnm, . /"};
	char tp[] = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&";//0000000140003478
	char kp[] = "55565653255552225565565555243466334653663544426565555525555222";//0000000140003438
	char output[64];
	for (int i = 0; i < 63; i++)
	{
		output[i]=find(tp[i],kp[i],fp);
	}
	cout << output<<endl;
}
char find(char p1,char p2,char *p3)
{
	int index = 0;
	for (int i = 0; i < 95; i++)
	{
		if (p3[i] == p1)
		{
			index = i;
			break;
		}
	}
	while (p3[index/23]!=p2)
	{
		index += 23;
	}
	return index;
}
//private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)t


        结果是一个函数声明的字符串,试着将它md5后提交,发现错误,那么就需要继续往上读
        那么跟踪outputString是从哪里获得的,能够来到第38行UnDecorateSymbolName函数
UnDecorateSymbolName:https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-undecoratesymbolname


        只靠阅读官方文档似乎不太足够,但第38行的大致意思是:完全取消对C++符号的修饰,也就是说,某个C++函数符号被取消修饰后,得到了
“private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)t”


        这样一个函数声明符号
        查阅一些文档之后才知道,C++中的符号在编译之后都会被修饰为另外一种样子
https://www.cnblogs.com/yxysuanfa/p/6984895.html
https://blog.csdn.net/Scl_Diligent/article/details/83990429

[C] 纯文本查看 复制代码
int Max(int a, int b);	//?Max@@YAHHH@Z
double Max(int a, int b);	//?Max@@YANHH@Z
double Max1(int a, int b);	//?Max1@@YANHH@Z
double Max1(int a, double b);	//?Max1@@YANHN@Z


         我们通过上述代码定义的函数,在编译后都会形成如注释所示的那样的名称
&#8203;
        实际操作也验证了我们的想法,那么我们的工作就应该是找到这个经过修饰的名称字符串
         根据上面给出的两位大佬总结的编译器名称修饰规则,以及我们已经得出的未修饰名称,可以写出确定的字符串:
[C] 纯文本查看 复制代码
?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z

        md5后提交发现还是不对,那就只能再往上读
        第28行的函数有些复杂,可以暂时不看;第30~37中涉及了v5,这个v5应是我们输入的内容或是中间内容,也正是v5经过UnDecorateSymbolName变换得到了outputString
        函数sub_1400015C0实际上是一个二叉树下序遍历
        (我不确定是不是叫后序,总之就是自下往上的遍历方式)
        如果不是因为最近正好遇到过类似的题目,可能我也没办法马上认出来,不过两层的递归查找其实也还算明显的;以及,就算不确定是否真是如此,也可以通过动态调试来确定是否为二叉树;并且,如果将其当作二叉树,sub_140001280函数便能够比较自然的想象为二叉树的生成
&#8203;
         上图是我根据下序遍历的规则手绘出的二叉树,然后再用上序遍历把字符串拼出来得到了flag
        (可恶,好久没写过字了,本就难看的字写的更加难看了......)
        直接把这个flag输入进去,程序提示正确,我们的猜想也就被验证了
&#8203;
         当然,实际操作中我们根本需要这样繁琐的去验证是否为二叉树
        大可以通过动调将输入值改为
[C] 纯文本查看 复制代码
ABCDEFGHIJKLMNOPQ......

        等比较好确定的有序的值,然后通过修改PC(程序计数器)跳过第23行的 if 判断,这样就能用较短的数据量确定出实际结构了
        但实际上,这为大佬也给出了另外一个比较简单的方法来算出置换后的结果:


https://www.freesion.com/article/6515734088/

         个人觉得这要比我手绘二叉树来得简单得多,供参考吧 &#8203;

免费评分

参与人数 2威望 +1 吾爱币 +22 热心值 +2 收起 理由
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
小朋友呢 + 2 + 1 我很赞同!

查看全部评分

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

Wavin1688 发表于 2021-11-21 10:19
感谢分享
snake88 发表于 2021-11-21 10:34
咔c君 发表于 2021-11-21 20:42
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-12-25 14:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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