第四届强网杯 侧防 Rerverse

20 篇文章 0 订阅
1 篇文章 0 订阅

Start

常规逆向题
ELF64文件,拖进IDA,日常搜索字符串在这里插入图片描述
直接跟进去

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int128 v4; // [rsp+0h] [rbp-D8h]
  __int128 v5; // [rsp+10h] [rbp-C8h]
  __int64 v6; // [rsp+20h] [rbp-B8h]
  char v7; // [rsp+28h] [rbp-B0h]
  __int16 v8; // [rsp+58h] [rbp-80h]
  __m128i v9; // [rsp+60h] [rbp-78h]
  __m128i v10; // [rsp+70h] [rbp-68h]
  __int64 v11; // [rsp+80h] [rbp-58h]
  __int64 v12; // [rsp+88h] [rbp-50h]
  __int128 v13; // [rsp+90h] [rbp-48h]
  __int128 v14; // [rsp+A0h] [rbp-38h]
  __int64 v15; // [rsp+B0h] [rbp-28h]
  __int16 v16; // [rsp+B8h] [rbp-20h]
  unsigned __int64 v17; // [rsp+C8h] [rbp-10h]

  __asm { endbr64 }
  v17 = __readfsqword(0x28u);
  v12 = 0LL;
  v9 = _mm_load_si128((const __m128i *)&xmmword_2050);
  v11 = 4927612398162959439LL;
  v10 = _mm_load_si128((const __m128i *)&xmmword_2060);
  v13 = 0LL;
  v14 = 0LL;
  v15 = 0LL;
  v16 = 0;
  sub_1080("Input the flag: ", a2);
  memset(&v4, 0, 0x58uLL);
  v8 = 0;
  sub_10B0(&v4);
  sub_12F0(&v4);
  if ( (v4 ^ *(_OWORD *)&v9) != 0 || (v5 ^ *(_OWORD *)&v10) != 0 || v11 != v6 || (_BYTE)v12 != v7 )
    sub_1080("oh no,you are wrong", a2);
  else
    sub_1080("right,you get the flag!!", a2);
  if ( __readfsqword(0x28u) != v17 )
  {
    sub_10A0();
    start();
  }
  return 0LL;
}

这里的sub_1080很容易看出应该就是printf

然后对v4进行内存的初始化,之后sub_10B0sub_12F0都传入了v4的指针

这里先跟进sub_10B0看看,在这里插入图片描述
继续跟在这里插入图片描述
在这里插入图片描述
这里是JUMPOUT,说明是调用了外部的一个函数,其实不难猜测出,v4应该就是我们输入的flag的首地址,这里只传入v4的指针,所以,可以得出sub_10B0应该就是gets()函数,这里我们验证一下
回到汇编窗口,找到最开始传入v4的参数的call的函数偏移地址为0x1163在这里插入图片描述
随后利用gdb动态调试看看这个函数做了啥

start发现OEP0x555555555200,猜测基址应该是0x555555554000
在这里插入图片描述
在gdb下尝试下断点b gets,成功下断点
在这里插入图片描述
run,此时函数就停在了gets这个位置,说明程序正常的运行的时候就会调用这个函数
在这里插入图片描述
往下单步到0x7ffff7e52b68的位置在这里插入图片描述
再往下单步时,程序就会停留在这个输入这个位置在这里插入图片描述
这里随便输入点东西,之后继续单步,会发现函数返回到0x5555555555168在这里插入图片描述
减去基址,得到0x1168,发现刚好就是调用完sub_10B0后的返回地址在这里插入图片描述
其实上面这一步大概猜测出是gets函数就行了,没必要大费周章的去分析,这里我只是为了熟悉熟悉gdb。
往下来到sub_12F0,这里才是真正对flag进行处理的位置

signed __int64 __fastcall sub_12F0(_BYTE *a1)
{
  signed __int64 result; // rax
  int v2; // esi
  __int64 v3; // r8
  __int64 v4; // rdx
  __int64 v5; // rax
  int v6; // ecx
  char v7; // r8
  char v8; // dl

  __asm { endbr64 }
  result = sub_1090();
  if ( (signed int)result > 0 )
  {
    v2 = result;
    v3 = (unsigned int)(result - 1);
    v4 = 0LL;
    do
    {
      a1[v4] = (a1[v4] ^ byte_4010[(unsigned int)v4 % 7]) + 65;
      v5 = v4++;
    }
    while ( v3 != v5 );
    v6 = 0;
    do
    {
      v7 = a1[3];
      result = (signed __int64)(a1 + 3);
      do
      {
        v8 = *(_BYTE *)(result-- - 1);
        *(_BYTE *)(result + 1) = v8;
      }
      while ( a1 != (_BYTE *)result );
      v6 += 4;
      *a1 = v7;
      a1 += 4;
    }
    while ( v2 > v6 );
  }
  return result;
}

用gdb跟可以发现sub_1090就是strlen在这里插入图片描述
之后看到第一个do-while循环,遍历flag以七个为一组与byte_4010进行异或再加上65

do
{
	a1[v4] = (a1[v4] ^ byte_4010[(unsigned int)v4 % 7]) + 65;
	v5 = v4++;
} while ( v3 != v5 );

byte_4010: 51h, 57h, 42h, 6Ch, 6Fh, 67h, 73h
这里测试时我输入的全都是a,测试数据越简单越容易看出规律,可以先异或一组看结果 qwdNOGS

来到第二个循环

do
{
	v7 = a1[3];
	v8 = a1 + 3;
	do
	{
        v9 = *(v8-- - 1);
        v8[1] = v9;
	}
	while ( a1 != v8 );
    v6 += 4;
    *a1 = v7;
    a1 += 4;
} while ( v2 > v6 );

可以发现这个循环中v7 = a1[3],在执行完中间的do-while循环后,令*a1 = v7,之后a1 += 4,不难发现第二个加密是以四个为一组进行加密,并且先将每一组中最后一个存到v7中,最后放到了每一组第一的位置,这里v6是跳出循环的条件,可以不管。

重点是中间的循环,这里比较可能比较难理解,首先第一句:

v9 = *(v8-- - 1);

我们注意到v8 = a1 + 3;

也就是说最开始v8应该指向每一组的最后一个字符,第一次进入循环,将当前组的第三个字符(从左往右数)存到v9,当该运算完成后,v8指针偏移,指向第三个字符。
第二句:

v8[1] = v9;

通过第一句可以发现,此时v8的指针指向的是第三个字符(从左往右),所以v8[1]是第四个字符

总结出来就是,将当前组的第三个字符,移动到第四个字符的位置。

最后看到循环的条件,a1 != v8,也就是当v8指向第一个字符的时候,循环结束。再通过之前v7的操作,不难发现这里其实就是对字符进行了移位

假设经过第一轮异或后的字符串为qwdN,那么经过此轮移位后,得到的就是Nqwd

这里通过gdb验证之后,推断正确
在这里插入图片描述
读完处理函数后,往下来到最终的判断

(v4 ^ *(_OWORD *)&v9) != 0 || (v5 ^ *(_OWORD *)&v10) != 0 || v11 != v6 || (_BYTE)v12 != v7

首先是v4v9进行异或之后看是否等于0,这里其实就是看v4是否等于v9,一个数异或0之后还是它本身,后面的也如此,这里需要注意的是_OWORD是16个字节

v9 = 4F4243684E76495C65775554647C784Ch
v10 = 78796974435A466D497D57664E44714Ch
v11 = 4927612398162959439LL

最后还有个判断v12是否等于0的,这个先不管

根据v9v10v11就可以写出exp解出flag了,这里需要注意的是,小端存储,所以要先把v9v10v11翻转之后再进行拼接,得到

['0x4c', '0x78', '0x7c', '0x64', '0x54', '0x55', '0x77', '0x65', '0x5c', '0x49', '0x76', '0x4e', '0x68', '0x43', '0x42', '0x4f', '0x4c', '0x71', '0x44', '0x4e', '0x66', '0x57', '0x7d', '0x49', '0x6d', '0x46', '0x5a', '0x43', '0x74', '0x69', '0x79', '0x78', '0x4f', '0x5c', '0x50', '0x57', '0x5e', '0x65', '0x62', '0x44']

之后每四个为一组,将每组第一个字符提取出来,第2-4个统一提取出来,进行移位操作

tmp = []

index = 0

while index < 40:
    tmp1 = encrypt[index]
    tmp2 = encrypt[index+1:index+4]
    print(tmp1, tmp2)
    for i in tmp2:
        tmp.append(i)
    tmp.append(tmp1)
    index += 4

print(tmp)

最后将tmp-65byte_4010进行异或,便可得到最终的flag

exp.py:
v10 = [0x4F, 0x42, 0x43, 0x68, 0x4E, 0x76, 0x49, 0x5C, 0x65, 0x77, 0x55, 0x54, 0x64, 0x7C, 0x78, 0x4C]
v11 = [0x78,0x79,0x69,0x74,0x43,0x5A,0x46,0x6D,0x49,0x7D,0x57,0x66,0x4E,0x44,0x71,0x4C]
byte_4010 = [0x51, 0x57, 0x42, 0x6C, 0x6F, 0x67, 0x73]
v12 = [0x44, 0x62, 0x65, 0x5E, 0x57, 0x50, 0x5C, 0x4F]

v10.reverse()
v11.reverse()
v12.reverse()

# v10 = 'OBChNvI\ewUTd|xL'
# v11 = 'xyitCZFmI}WfNDqL'
# v12 = 'Dbe^WP\\O'
# v13 = 0

encrypt = []
for i in v10:
    encrypt.append(hex(i))
for i in v11:
    encrypt.append(hex(i))
for i in v12:
    encrypt.append(hex(i))

print(encrypt,len(encrypt))

tmp = []

index = 0

while index < 40:
    tmp1 = encrypt[index]
    tmp2 = encrypt[index+1:index+4]
    print(tmp1, tmp2)
    for i in tmp2:
        tmp.append(i)
    tmp.append(tmp1)
    index += 4

print(tmp)

flag = ''

for i in range(len(tmp)):
    flag += chr((tmp[i] - 65) ^ byte_4010[i % 7])

print(flag)

得到flag:flag{QWB_water_problem_give_you_the_scor

发现少了点什么东西,这里不难发现是少了e},添上即是完整的flag,而e的位置就是前面提到的v12 != v7

最终flag:flag{QWB_water_problem_give_you_the_score}

总结:这道题其实难度还是偏向简单的,比赛的时候解出来的也有几百支队伍,我感觉难的地方就在于初始化v4后又有个外部函数以v4作为参数,最开始看到这个就劝退了,等到比赛第二天才把这道题解了。这次的强网杯水题是挺多的,但是难的题也不少,刚刚接触逆向,还得慢慢学习。学习,是无止境的。

End

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值