C++异常处理机制

March 16, 2019 逆向 访问: 29 次

以一道ctf逆向题来谈C++异常处理机制

C ++异常处理机制

C++处理异常的机制是由检查、抛出和捕获3个部分组成,分别由3种语句来完成:

try---检查
throw---抛出
catch---捕获

主动抛出异常

主动抛出异常是用throw语句。

throw 表达式;

如果某段程序中出现了异常,就可以使用throw语句来抛出这个异常给调用者,该异常与之匹配的catch语句来捕获。throw语句中的“表达式”是表示抛出的异常类型,异常类型由表达式的类型来表示

被动抛出异常

也就是说程序代码本身有问题,运行的时候遇到了错误等导致无法继续运行程序,直接导致异常,使程序转到catch代码块中去执行

异常的检查和捕捉

使用try和catch来检查和捕捉:

try{
    被检查的语句;
}
catch(异常类型声明一)
{
    进行异常处理的复合语句一;
}
catch(异常类型声明二)
{
    进行异常处理的复合语句二;
}
catch(异常类型声明三)
{
    进行异常处理的复合语句三;
}
……
catch(异常类型声明n)
{
    进行异常处理的复合语句n;
}

2018Hackgame--confused_flxg

这道题是运用了C++异常机制
首先用die查一下发现没有加壳,是C++的64位程序
尝试着先在dos下运行这个程序,出现关键字符串,以及在用户输入之后会得到一串类似与被base64加密了的字符串

import base64
print base64.b64decode("==QfzMzMzIzXnFGbm9VZrFmZfF0XtF2XJt3Z4xmZ"[::-1])
out:
flxg{I_am_A_fake_flag_23333}

载入IDA中查找关键字符串,然后再跟踪找到关键代码:

__int64 __fastcall sub_13F041900(signed int a1)
{
  __int64 v1; // ST60_8
  const wchar_t *v3; // [rsp+68h] [rbp-410h]
  __int64 v4; // [rsp+80h] [rbp-3F8h]
  char v5; // [rsp+180h] [rbp-2F8h]
  char v6; // [rsp+3F0h] [rbp-88h]
  signed int v7; // [rsp+480h] [rbp+8h]
  v7 = a1;                                      // 命令行参数个数赋值给v7
  v4 = -2i64;
  memset(&v5, 0, 0xC8ui64);
  sub_13F0424B0(std::cout, "请输入你要验证的flxg,格式为flxg{xxxxx}:");
  sub_13F042890(std::cin, &v5);
  if ( v7 >= 60 )
  {
    if ( v7 == 60 )
    {
      v3 = L"enter in exception!";
      CxxThrowException(&v3, &PE.(default argument 0));
    }
    if ( v7 > 60 )
    {
      sub_13F0424B0(std::cout, &unk_13F0454C0);
      return 0i64;
    }
  }
  else
  {
    v1 = sub_13F0424B0(std::cout, "你解密到得到的flxg为:");
    sub_13F0424B0(v1, "==QfzMzMzIzXnFGbm9VZrFmZfF0XtF2XJt3Z4xmZ");
  }
  sub_13F042890(std::cin, &v6);
  return 0i64;
}

可以很明显的判断出4b0函数是输出函数,890函数是输入函数,有一个if判断,看不出来v7到底是什么,好像跟这个函数没有什么关系
但是发现一个没见过的函数“CxxThrowException”,经过查资料发现这是一个异常处理函数,相当于throw,用来抛出异常
查看交叉引用发现该函数就被调用了一次,跟踪调用者:

signed __int64 __usercall sub_13F043E84@<rax>(unsigned int a1@<ebx>)
{
  __int64 v1; // rcx
  char v2; // si
  __int64 v3; // rcx
  __int64 v5; // rcx
  __int64 *v6; // rax
  __int64 v7; // rcx
  __int64 *v8; // rbx
  __int64 v9; // rax
  _QWORD *v10; // rax
  __int64 v11; // rcx
  _QWORD *v12; // rbx
  __int64 v13; // rdi
  __int64 v14; // rcx
  signed int *v15; // rbx
  __int64 v16; // rcx
  __int64 v17; // rcx
  __int64 v18; // rcx
  if ( !sub_13F043890(1i64) )
  {
    sub_13F04434C(7i64);
    goto LABEL_20;
  }
  v2 = 0;
  LOBYTE(a1) = sub_13F043854(v1);
  v3 = dword_13F048130;
  if ( dword_13F048130 == 1 )
  {
LABEL_20:
    sub_13F04434C(7i64);
    goto LABEL_21;
  }
  if ( dword_13F048130 )
  {
    v2 = 1;
  }
  else
  {
    dword_13F048130 = 1;
    if ( initterm_e(&unk_13F0452F0, &unk_13F045308) )
      return 255i64;
    initterm(&unk_13F0452C8, &unk_13F0452E8);
    dword_13F048130 = 2;
  }
  LOBYTE(v3) = a1;
  sub_13F043A54(v3);
  v6 = sub_13F044640(v5);
  v8 = v6;
  if ( *v6 && sub_13F0439B8(v6) )
  {
    v9 = *v8;
    sub_13F044950(0i64, 2i64);
  }
  v10 = sub_13F044648(v7);
  v12 = v10;
  if ( *v10 && sub_13F0439B8(v10) )
    register_thread_local_exe_atexit_callback(*v12);
  v13 = *_p___argv(v11);
  v15 = _p___argc(v14);                         // 返回命令行参数
  get_initial_narrow_environment(v16);
  a1 = sub_13F041900(*v15);
  if ( !sub_13F04449C(v17) )
LABEL_21:
    exit(a1);
  if ( !v2 )
    cexit();
  LOBYTE(v18) = 1;
  sub_13F043A78(v18, 0i64);
  return a1;
}

可以看到传入900函数的是v15,那么向上找到v15,是函数_p__argc函数的返回值

__p__argc( )
__p__argv( )

这两个函数都没有参数,分别返回__argc、__argv 变量,那这两个变量是什么呢?

argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数,等于文件名+命令行参数个数
argv:指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数

那么现在我们知道了v15其实是argc,也就是命令行参数个数+1,那么现在我们给程序添加上59个命令行参数再次运行这个程序,发现程序的流程又不一样了,没有了杠杠的base64,而是出现了“你的flxg不正确!”

从这我们可以看出来,程序通过利用抛出异常来跳转到catch块中执行另外的关键代码,在IDA中通过字符串来定位另外的关键代码

void __usercall sub_13F04498F(__int64 a1@<rbp>)
{
  unsigned __int8 *v1; // rax
  unsigned __int8 v2; // dl
  int v3; // eax
  *(a1 + 112) = a1 + 384;
  *(a1 + 40) = -1i64;
  do
    ++*(a1 + 40);
  while ( *(*(a1 + 112) + *(a1 + 40)) );
  *(a1 + 64) = *(a1 + 40);                      // 求出我们输入字符串的长度,存到*(a1+64)
  qmemcpy((a1 + 0x320), &byte_13F0454D8, 57ui64);
  memset((a1 + 0x359), 0, 143ui64);
  base_encode(a1 + 0x90, (a1 + 0x180), *(a1 + 64));
  memset((a1 + 176), 0, 0xC8ui64);
  *(a1 + 80) = sub_13F041A40(a1 + 144);
  *(a1 + 0x30) = a1 + 0xB0;
  *(a1 + 0x88) = *(a1 + 0x30);
  do
  {
    *(a1 + 32) = **(a1 + 80);
    **(a1 + 48) = *(a1 + 32);
    ++*(a1 + 80);
    ++*(a1 + 48);
  }
  while ( *(a1 + 0x20) );
  strrev((a1 + 0xB0));                          // base64(input)翻转
  memset((a1 + 0x250), 0, 200ui64);
  for ( *(a1 + 0x24) = 0; ; ++*(a1 + 0x24) )
  {
    *(a1 + 0x78) = a1 + 0xB0;
    *(a1 + 0x38) = 0xFFFFFFFFFFFFFFFFi64;
    do
      ++*(a1 + 56);
    while ( *(*(a1 + 120) + *(a1 + 56)) );      // 求出base64后的长度,存在a1+56
    if ( *(a1 + 0x24) >= *(a1 + 56) )
      break;                                    // 以上四行是为了加for语句中间的一个条件,相当于加一个i<len(base64(input))
    *(a1 + *(a1 + 36) + 0x250) = *(a1 + 0x24) ^ *(a1 + *(a1 + 36) + 0xB0);// 相当于 i ^ base64(input)[i]
  }
  v1 = (a1 + 592);
  while ( 1 )
  {
    v2 = *v1;
    if ( *v1 != v1[208] )
      break;
    ++v1;
    if ( !v2 )
    {
      v3 = 0;
      goto LABEL_15;
    }
  }                                             // 跟正确的字符串进行比较
  v3 = -(v2 < v1[208]) | 1;
LABEL_15:
  if ( v3 )
  {
    sub_13F0424B0(std::cout, &unk_13F045538);   // 失败
    *(a1 + 68) = 0;
  }
  else
  {
    sub_13F0424B0(std::cout, &unk_13F045518);   // 成功
    *(a1 + 72) = 0;
  }
  sub_13F041AE0(a1 + 144);
  JUMPOUT(&loc_13F0419F4);
}

刚打开这个函数的时候发现有很多的数据以及分支结构,有点烦,静态分析有点难以理解程序的流程,这个适合IDA本地动态调试,慢慢调试会发现程序没那么复杂,主要还是耐心吧。
主要流程:
- 将我们输入的字符串base64加密后翻转
- 将每一位和在字符串中的索引进行异或
- 与程序中正确字符串进行比较
正确字符串是在byte_13F0454D8,可以很快的找到
解题代码:

import base64
date = [0x39,0x65,0x45,0x54,0x77,0x5F,0x34,0x5F,0x64,0x5F,0x66,0x68,0x3C,0x34,0x58,0x55,0x7F,0x43,0x21,0x4B,0x7F,0x20,0x43,0x76,0x5F,0x20,0x4C,0x4D,0x7A,0x53,0x70,0x7D,0x56,0x4D,0x65,0x47,0x4C,0x5D,0x71,0x43,0x18,0x6F,0x47,0x48,0x42,0x18,0x1C,0x4D,0x74,0x45,0x1,0x69,0x0,0x4D,0x5B,0x6D]
base64_flag = ''
for x in xrange(len(date)):
    base64_flag += chr(x^date[x])
print base64.b64decode(base64_flag[::-1])
out:flxg{Congratulations_U_FiNd_the_trUe_flXg}

添加新评论