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