XCTF-攻防世界CTF平台-Reverse逆向类——65reverse-box

Posted 大灬白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了XCTF-攻防世界CTF平台-Reverse逆向类——65reverse-box相关的知识,希望对你有一定的参考价值。

挑战描述
$ ./reverse_box ${FLAG}
95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a
flag格式:TWCTF{}

1、查看程序信息

先查看程序信息

Ubuntu下gcc编译的32位ELF文件,没有加壳
运行程序reverse_box:

要求是输入一个字符串即flag作为输入
将文件reverse_box用IDA中打开,

2、main函数

程序的逻辑:输入的参数只有一个就会输出“usage:./reverse-box flag”,输入了多个参数就会调用sub_804858D(&v4)函数,然后根据输入的字符串的ASCII值,输出对应的v4的值的16进制。
输入的flag格式是TWCTF{},对应的ASCII码是0x54(84),57(87),43(67),54(84),46(70),7b(123),7d(125)
对应的输出16进制是95 ee af 95 ef 94 4a
所以就是最后生成的v4数组中v4[84]=0x95,v4[87]=0xee,v4[67]=0xaf,v4[84]=0x95,v4[70]=0xef,v4[123]=0x94,v4[125]=0x4a

3、sub_804858D()函数:

进入sub_804858D()函数:

完整的代码:

int __cdecl sub_804858D(_BYTE *a1)
{
  unsigned int v1; // eax
  int v2; // edx
  char v3; // al
  char v4; // ST1B_1
  char v5; // al
  int result; // eax
  unsigned __int8 v7; // [esp+1Ah] [ebp-Eh]
  char v8; // [esp+1Bh] [ebp-Dh]
  char v9; // [esp+1Bh] [ebp-Dh]
  int v10; // [esp+1Ch] [ebp-Ch]

  v1 = time(0);
  srand(v1);                                    // 以时间作为随机种子
  do
    v10 = (unsigned __int8)rand();              // 获得伪随机数
  while ( !v10 );                               // 当v10为0,继续循环
  *a1 = v10;                                    // 把v10的值赋值给参数a1
  v7 = 1;
  v8 = 1;
  do
  {
    v2 = v7 ^ 2 * v7;
    if ( (v7 & 0x80u) == 0 )
      v3 = 0;
    else
      v3 = 27;
    v7 = v2 ^ v3;
    v4 = 4 * (2 * v8 ^ v8) ^ 2 * v8 ^ v8;
    v9 = 16 * v4 ^ v4;
    if ( v9 >= 0 )
      v5 = 0;
    else
      v5 = 9;
    v8 = v9 ^ v5;
    result = (unsigned __int8)__ROR1__(v8, 4) ^ (unsigned __int8)__ROR1__(v8, 5) ^ (unsigned __int8)__ROR1__(v8, 6) ^ (unsigned __int8)__ROR1__(v8, 7) ^ (unsigned __int8)(v8 ^ *a1);
    a1[v7] = result;
  }
  while ( v7 != 1 );
  return result;
}

其中:

#define unsigned char    BYTE;
inline uint8  __ROR1__(uint8  value, int count) { return __ROL__((uint8)value, -count); }

ROl: 参数:(value, int count)
循环左移函数,参数有两个,第一个参数为左移的数,第二个参数为左移的位数。
如果第二个参数值为负数,则实际上为循环右移 -count位。

该函数的实现细节为:
先得到value的位数,然后count对位数取模。
如果count值大于0,则先右移-count取模的结果,然后在左移取模的结果,得到的两个数相或,即为循环左移的结果。
如果count值小于0,先左移再右移即可。
举例来说: value = 0110, count = 6
value为4位数, 6 % 4 = 2,
0110先右移4-2=2位,得到0001,然后在左移2位,得到1000,0001 | 1000结果为1001,即循环左移结果为1001。

注意的是,这里是__ROR1__循环右移函数,调用的循环左移函数__ROL__((uint8)value, -count)通过左移负count位来实现右移count位。

4、程序逻辑

根据题目的意思,关键就在于得到v4数组,之后的操作就是根据我们输入的flag的ASCII码值,输出v4数组中对应下标的16进制数而已。
v4数组是在sub_804858D函数中生成的,独立不受输入的参数影响,
生成v4数组的过程中有根据参数if判断赋不同的值、移位、循环等单向过程,所以我们无法只根据v4数组中v4[84]=0x95,v4[87]=0xee,v4[67]=0xaf,v4[84]=0x95,v4[70]=0xef,v4[123]=0x94,v4[125]=0x4a等部分结果逆向推导出其他数组内容。
所以我们只能通过爆破,算法中产生的随机值取低2位的取值范围就是0-255,也就是这256个数,会产生256个不同的v4数组,我们只需要找到其中v4[84]=0x95,v4[87]=0xee,v4[67]=0xaf,v4[84]=0x95,v4[70]=0xef,v4[123]=0x94,v4[125]=0x4a的那个数组就是正确的v4数组。

5、方法一:gdb脚本获得内存中的数组

直接用gdb调试,运行脚本

define test.sh

然后逐行输入脚本内容:

set $i=0
set $total=256
while($i<$total)
  b *0x80485b4
  b *0x8048707
  run TWCTF
  set $i=$i+1
  set *(char*)($ebp-0xc)=$i
  continue
  if ($eax==0x95)
    print $i
    x/256xb $esp+0x1c
    set $i=256
  end
  stop
end
end

然后运行脚本test.sh,一直按回车直到找到正确的解就会输出v4数组:

6、gdb脚本分析:

脚本在0x80485b4的位置下了第一个断点

此时rand()随机数生成函数刚生成一个随机数与0xFF后,从eax寄存器取出存放在[ebp+var_C]
查看栈中var_C的地址相对于栈顶是-0000000C

所以当时加载到栈中的地址就是ebp-0xc,其实这里我们把断点下在0x80485b1,然后把当前的随机值赋值给eax也是可以的,因为只要能达到我们的目的把当前的随机值赋为0-255即可。
之后运行程序再停在第二个断点0x8048707:

停在这,正是printf函数开始输入的位置,这时候我们判断它输入的第一个16进制值是不是0x95,如果是那么我们就认为找到了正确的解;然后输出找到解的随机值和v4数组的所有值,查看起始地址从esp+0x1c开始的256个16进制字节的内容,在上图的汇编中数组地址是esp+eax+1Ch,但是当输出数组的第一个值时i为0即eax为0,所以此时数组起始地址esp+eax+1Ch=esp+1Ch,之所以输出256字节就够了,实际上也许用不上256字节,因为数组的下标是我们输入的字符的ASCII码,又我们输入的字符只能是可打印的字符,所以范围就是0-255中哪些可打印的字符就行了,这里我们输出完整的256字节是没有具体去区分可打印字符了,因为也不会影响到结果。

或者我们也可以直接输入gdb命令:

source test.sh

引入脚本文件直接运行:

#设置从0到255的随机值爆破
set $i=0
set $total=256
while($i<$total)
  #在生成随机值之后的地址下第一个断点
  b *0x80485b1
  #在main函数中printf输出的位置下第二个断点
  b *0x8048707
  #运行程序,并输入参数"TWCTF"
  run TWCTF
  #$i变量的值递加1
  set $i=$i+1
  #程序在第一个断点停下来的时候,把当前的随机值赋值给eax寄存器
  set $eax=$i
  #继续运行程序
  continue
  #程序在第二个断点停下来的时候,判断此时的输出的第一个16进制值是否是0x95
  if ($eax==0x95)
	#如果是,则认为找到了正确的解,输出此时的随机值
    print $i
	#查看起始地址从esp+0x1c开始的256个16进制字节的内容,这里就是存放数组的地方
    x/256xb $esp+0x1c
	#令变量的值为256,退出循环
    set $i=256
  end
  stop
end
end

脚本下断点之后会暂停,直接回车一直运行即可,直到找到结果:

7、将输出转换会输入的flag

之后我们只要根据程序输出的16进制字符:95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a
找到它们在数组中的下标,再把下标转换成ASCII码对应的字符就是我们输入的flag了
使用Python代码的re库便于分割字符串:
flag.py

import re
result = "95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a"
output = '''
0xffffcfcc:     0xd6    0xc9    0xc2    0xce    0x47    0xde    0xda    0x70
0xffffcfd4:     0x85    0xb4    0xd2    0x9e    0x4b    0x62    0x1e    0xc3
0xffffcfdc:     0x7f    0x37    0x7c    0xc8    0x4f    0xec    0xf2    0x45
0xffffcfe4:     0x18    0x61    0x17    0x1a    0x29    0x11    0xc7    0x75
0xffffcfec:     0x02    0x48    0x26    0x93    0x83    0x8a    0x42    0x79
0xffffcff4:     0x81    0x10    0x50    0x44    0xc4    0x6d    0x84    0xa0
0xffffcffc:     0xb1    0x72    0x96    0x76    0xad    0x23    0xb0    0x2f
0xffffd004:     0xb2    0xa7    0x35    0x57    0x5e    0x92    0x07    0xc0
0xffffd00c:     0xbc    0x36    0x99    0xaf    0xae    0xdb    0xef    0x15
0xffffd014:     0xe7    0x8e    0x63    0x06    0x9c    0x56    0x9a    0x31
0xffffd01c:     0xe6    0x64    0xb5    0x58    0x95    0x49    0x04    0xee
0xffffd024:     0xdf    0x7e    0x0b    0x8c    0xff    0xf9    0xed    0x7a
0xffffd02c:     0x65    0x5a    0x1f    0x4e    0xf6    0xf8    0x86    0x30
0xffffd034:     0xf0    0x4c    0xb7    0xca    0xe5    0x89    0x2a    0x1d
0xffffd03c:     0xe4    0x16    0xf5    0x3a    0x27    0x28    0x8d    0x40
--Type <RET> for more, q to quit, c to continue without paging--
0xffffd044:     0x09    0x03    0x6f    0x94    0xa5    0x4a    0x46    0x67
0xffffd04c:     0x78    0xb9    0xa6    0x59    0xea    0x22    0xf1    0xa2
0xffffd054:     0x71    0x12    0xcb    0x88    0xd1    0xe8    0xac    0xc6
0xffffd05c:     0xd5    0x34    0xfa    0x69    0x97    0x9f    0x25    0x3d
0xffffd064:     0xf3    0x5b    0x0d    0xa1    0x6b    0xeb    0xbe    0x6e
0xffffd06c:     0x55    0x87    0x8f    0xbf    0xfc    0xb3    0x91    0xe9
0xffffd074:     0x77    0x66    0x19    0xd7    0x24    0x20    0x51    0xcc
0xffffd07c:     0x52    0x7d    0x82    0xd8    0x38    0x60    0xfb    0x1c
0xffffd084:     0xd9    0xe3    0x41    0x5f    0xd0    0xcf    0x1b    0xbd
0xffffd08c:     0x0f    0xcd    0x90    0x9b    0xa9    0x13    0x01    0x73
0xffffd094:     0x5d    0x68    0xc1    0xaa    0xfe    0x08    0x3e    0x3f
0xffffd09c:     0xc5    0x8b    0x00    0xd3    0xfd    0xb6    0x43    0xbb
0xffffd0a4:     0xd4    0x80    0xe2    0x0c    0x33    0x74    0xa8    0x2b
0xffffd0ac:     0x54    0x4d    0x2d    0xa4    0xdc    0x6c    0x3b    0x21
0xffffd0b4:     0x2e    0xab    0x32    0x5c    0x7b    0xe0    0x9d    0x6a
0xffffd0bc:     0x39    0x14    0x3c    0xb8    0x0a    0x53    0xf7    0xdd
0xffffd0c4:     0xf4    0x2c    0x98    0xba    0x05    0xe1    0x0e    0xa3
'''

#把output数组中符合正则表达式的都用空字符替换
output = re.sub(r'(0xffff[a-z0-9][a-z0-9][a-z0-9][a-z0-9])',"",output)
#编译16进制的正则表达式,生成一个 Pattern 对象
hex = re.compile(r'(0x[a-z0-9][a-z0-9])')
#找到output中和16进制正则匹配的所有子串,并返回一个列表
tmp = hex.findall(output)
print(tmp)
#将列表中的所有16进制字符,按16进制转换成10进制数字
tmp = [int(i,16) for i in tmp]
print(tmp)

flag = ""
#将16进制字符2个一组,整除2得到48
for i in range(len(result)//2):
   #每次取出两个16进制字符,按16进制转换成10进制数字
   num = int(result[i*2:i*2+2],16)
   #将数字到列表中查找,将找到的下标转换成ASCII字符
   flag += chr(tmp.index(num))
print(flag)

运行结果:

得到flag:TWCTF{5UBS717U710N_C1PH3R_W17H_R4ND0M123D_5-B0X}

8、C语言复现程序得到数组

reverse-box.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/*
//#include<_mingw.h>
//#include "defs.h"

//_rotl函数是在32位下的移位操作,所以我们需要字节写一个8位下的循环座椅函数
//#define __ROL__(x, y) __rotl__(x, y)
#define __ROL__(x, y) _rotl(x, y)
typedef unsigned char   uint8;
//__ROL__在#include<stdlib.h>头文件中的定义函数是_rotl
inline uint8  __ROR1__(uint8  value, int count) { return __ROL__((uint8)value, -count); }
   // Rotate left
*/

/*
int value :移位的数
int shift:右移的位数
返回值:循环右移后的结果
*/
unsigned __int8 __ROR1__(int value , int shift){
    int result = (unsigned __int8)(value<<(8-shift))|(value>>shift);
    return result;
}

int main(){
    int v2; // edx
    char v3; // al
    char v4; // ST1B_1
    char v5; // al
    int result; // eax
    unsigned __int8 v7; // [esp+1Ah] [ebp-Eh]
    unsigned __int8 v8; // [esp+1Bh] [ebp-Dh]
    unsigned __int8 v9; // [esp+1Bh] [ebp-Dh]

    unsigned int a1[256];
    //全部初始化为0
    memset(a1,0,sizeof(a1));

    int temp=0;
    for(; temp <256 ;temp++){
        //全部初始化为0
        memset(a1,0,sizeof(a1));
        a1[0] = 214;
        v7 = 1;
        v8 = 1;
        do
        {
            v2 = v7 ^ 2 * v7;
            //if ( (v7 & 0x80u) == 0 )
            if ( v7 < 128 )
                v3 = 0;
            else
                v3 = 27;
            v7 = v2 ^ v3;

            v4 = 4 * (2 * v8 ^ v8) ^ 2 * v8 ^ v8;
            //char字符类型的取值范围是-128到127,这里异或之后的结果是255会大于127变成-1
            v9 = (16 * v4 ^ v4) & 0xff;
            //if ( v9 >= 0 )
            if ( v9 < 128 )
                v5 = 0;
            else
                v5 = 9;
            v8 = v9 ^ v5;
            result = (unsigned __int8)__ROR1__(v8, 4) ^ (unsigned __int8)__ROR1__(v8, 5) ^ (unsigned __int8)__ROR1__(v8, 6) ^ (unsigned __int8)__ROR1__(v8, 7) ^ (unsigned __int8)(v8 ^ temp/* *a1 */);
            a1[v7] = result &0xff;
            //printf("result:%d\\n", result);
        }
        while ( v7 != 1 );

        if(a1[84]==0x95 && a1[87]==0xee && a1[67]==0xaf && a1[84]==0x95 && a1[70]==0xef && a1[123]==0x94 && a1[125]==0x4a){
            printf("找到了正确的数组,此时的随机值temp为:%d\\n",temp);
            break;
        }
    }

    for(temp =0 ; temp <256 ;temp++){
        if(temp%16==0){
            printf("\\n");
        }
        printf("0x%02x ",a1[temp]);
    }
    return 0;
}

运行程序:

也成功得到了关键的数组。
之后再同样将输出的16进制字符每两个一组,到数组中找到对应的数的下标,最后将下标转换成ASCII字符即可得到输入的flag。

以上是关于XCTF-攻防世界CTF平台-Reverse逆向类——65reverse-box的主要内容,如果未能解决你的问题,请参考以下文章

XCTF-攻防世界CTF平台-Reverse逆向类——53easyCpp

XCTF-攻防世界CTF平台-Reverse逆向类——65reverse-box

XCTF-攻防世界CTF平台-Reverse逆向类——59mfc逆向-200

XCTF-攻防世界CTF平台-Reverse逆向类——52handcrafted-pyc

XCTF-攻防世界CTF平台-Reverse逆向类——56tar-tar-binks

XCTF-攻防世界CTF平台-Reverse逆向类——54echo-server