rev_babyrev

根据描述,这是ISG2023的一道题,名字里有baby,难度是中,下载附件,发现是linux程序,拖入IDA,发现有UPX壳,

尝试upx -d,不出意外的发生了意外,解不开。010editer打开看一下,

果然,UPX的特征码[UPX!]被改了,而且文件类型的地方是03,动态库,这里导致IDA调试报错,将03修改为02,ISG!替换为UPX!,保存,在upx -d,不出意外又失败了,这次是bad seek 2,看了下源码,是写文件出错了,完全没思路继续解决,哪位大神如果知道原因,还请指点。

没办法,IDA远程调试,配置过程就不细说了,教程一大把。

跟进entry start函数,下断点

F8步过跟进,出现第一次寄存器跳转

继续F8,后面一大段带有循环的逻辑,建议找到ret,F2下断点,然后F9直接执行到断点,再F2取消断点(这里取消是因为不取消的话,后面还会再进来,要多按几次),之后再F8进入后续的逻辑,直到出现下一次寄存器跳转地址

继续F8,执行了syscall后再次出现寄存器跳转

到这里,upx的解压缩工作已经完成了,后面就要进入libc_start_call_main了,尝试了dump内存,但没成功,还请大神指点一下,该如何dump完成脱壳。

下断点,F7进入,出现libc_start_call_main

下断点,F7进入,再次出现寄存器跳转

同样的,下断点后,F7进入

在call处下断点,F7进入

switch处下断点,F9执行后,F8继续,找到寄存器跳转

下断点并F7进入

下断点并F7进入

这个rdi便是真正的主函数地址,F7进入,按p生成函数,按F5生成伪代码
[C] 纯文本查看 复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
void __noreturn sub_7F4573309980()
{
unsigned __int16 *v0; // rbx
unsigned __int64 v1; // rbx
__int64 *v2; // r14
__int64 v3; // r15
__m128i v4; // xmm0
int v5; // ebp
unsigned int v6; // eax
__m128i v7; // xmm0
__m128i v8; // xmm1
__m128i v9; // xmm3
__m128i si128; // xmm2
__m128i v11; // xmm4
unsigned int v12; // ecx
__m128i v13; // xmm3
unsigned __int8 v14; // al
__m128i v15; // xmm0
__m128i v16; // xmm1
__int64 v17[3]; // [rsp+8h] [rbp-690h] BYREF
__int128 v18[2]; // [rsp+20h] [rbp-678h] BYREF
__m128i v19; // [rsp+40h] [rbp-658h] BYREF
__m256 v20; // [rsp+50h] [rbp-648h]
__int64 v21; // [rsp+70h] [rbp-628h]
__m128i v22; // [rsp+360h] [rbp-338h] BYREF
__m128i v23; // [rsp+370h] [rbp-328h] BYREF
__m128i v24; // [rsp+380h] [rbp-318h] BYREF
__int64 v25; // [rsp+390h] [rbp-308h]
int v26; // [rsp+64Ch] [rbp-4Ch]

sub_7EFF194BB200((__int128 *)v19.m128i_i8);
sub_7EFF194BB470(&v22, (__int64)&v19);
if ( v22.m128i_i64[0] )
{
if ( v22.m128i_i64[1] )
sub_7EFF19498E50(v22.m128i_i64[0], v22.m128i_i64[1], 1LL);
sub_7EFF194BB470(v17, (__int64)&v19);
}
else
{
v17[0] = 0LL;
}
if ( *(_QWORD *)&v20.m256_f32[2] != *(_QWORD )v20.m256_f32 )
{
v1 = (
(_QWORD *)&v20.m256_f32[2] - *(_QWORD *)v20.m256_f32) / 0x18uLL;
v2 = (__int64 )((_QWORD *)v20.m256_f32 + 8LL);
do
{
if ( v2 )
sub_7EFF19498E50(
(v2 - 1), *v2, 1LL);
v2 += 3;
--v1;
}
while ( v1 );
}
if ( v19.m128i_i64[1] )
sub_7EFF19498E50(v19.m128i_i64[0], 24 * v19.m128i_i64[1], 8LL);
v0 = (unsigned __int16 *)v17[0];
if ( !v17[0] ) // 检查是否有参数,且不为空字符串
{
v19.m128i_i64[0] = (__int64)&off_7EFF194F35A0;
v19.m128i_i64[1] = 1LL;
*(_QWORD *)v20.m256_f32 = "src/main.rsWrong!\nCorrect!\n";
*(_OWORD *)&v20.m256_f32[2] = 0LL;
sub_7EFF194BCBD0((__int128 *)v19.m128i_i8);
((void (__fastcall *)(_QWORD))unk_7EFF194BF530)(0LL);
BUG();
}
v3 = v17[2]; // v17应该是std::string,v3就是v17.length();
sub_7EFF19499660((__int64)&v22);
if ( v22.m128i_i32[0] == 6 )
{
v4 = _mm_loadu_si128((const __m128i *)&v22.m128i_i8[8]);
v18[1] = (__int128)_mm_loadu_si128((const __m128i *)&v23.m128i_i8[8]);
v18[0] = (__int128)v4;
((void (__fastcall *)(__m128i *, __int128 *))sub_7EFF194996A0)(&v19, v18);
((void (__fastcall *)(__m128i *, __m128i *))sub_7EFF194988E0)(&v22, &v19);
v5 = v26;
sub_7EFF19498700(&v22);
sub_7EFF194986B0((__int64)v18);
if ( v5 )
goto LABEL_27;
if ( v3 != 0x20 )
goto LABEL_27;
v6 = *v0;
v7 = _mm_cvtsi32_si128(v6); // int转int128,4字节扩展到16字节
v8 = _mm_srli_epi16(v7, 8u); // 128位(16字节)数据,没2字节内右移8位,相当于
// unsigned int a[8];
// a[0] << 8;
// a[1] << 8;
// ……
// a[7] << 8;
v9 = _mm_loadu_si128((const __m128i *)(v0 + 1));// 读取flag16字节
si128 = _mm_load_si128((const __m128i *)&xmmword_7EFF194E0020);
if ( _mm_movemask_epi8(
_mm_cmpeq_epi8(
_mm_xor_si128(_mm_or_si128(_mm_slli_si128(v9, 1), _mm_andnot_si128(si128, v8)), v9),// memcmp(v9 ^ ((v9 << 8) | (~si128 & v8)), xmmword_7EFF194E0030) == 0
// xmmword_7EFF194E0030 = 0x192A30261B6261204D49152F22133C14
(__m128i)xmmword_7EFF194E0030)) != 0xFFFF )
goto LABEL_27;
v11 = _mm_loadl_epi64((const __m128i *)(v0 + 9));// 从第19字节读到26字节
if ( _mm_xor_si128(_mm_or_si128(_mm_slli_epi64(v11, 8u), _mm_srli_si128(v9, 15)), v11).m128i_u64[0] == 0x7140901160D0507LL
&& (v12 = *(_DWORD *)(v0 + 13),
v13 = _mm_cvtsi32_si128(v12),
_mm_cvtsi128_si32(
_mm_xor_si128(
_mm_or_si128(_mm_slli_epi32(v13, 8u), _mm_andnot_si128(si128, _mm_srli_epi64(v11, 0x38u))),
v13)) == 0x84C1D02)
&& (unsigned __int8)_mm_cvtsi128_si32(_mm_xor_si128(v8, v7)) == 0x1A
&& (_BYTE)v6 == 0x49
&& (v14 = *((_BYTE )v0 + 30), (v14 ^ HIBYTE(v12)) == 0x78)
&& (
((_BYTE *)v0 + 31) ^ v14) == 0x35 )
{
v19.m128i_i64[0] = (__int64)&off_7EFF194F35F0;
v19.m128i_i64[1] = 1LL;
*(_QWORD *)v20.m256_f32 = "src/main.rsWrong!\nCorrect!\n";
*(_OWORD *)&v20.m256_f32[2] = 0LL;
sub_7EFF194BCBD0((__int128 *)v19.m128i_i8);
}
else
{
LABEL_27:
v19.m128i_i64[0] = (__int64)&off_7EFF194F35E0;
v19.m128i_i64[1] = 1LL;
*(_QWORD *)v20.m256_f32 = "src/main.rsWrong!\nCorrect!\n";
*(_OWORD *)&v20.m256_f32[2] = 0LL;
sub_7EFF194BCBD0((__int128 *)v19.m128i_i8);
}
((void (__fastcall *)(_QWORD))unk_7EFF194BF530)(0LL);
}
else
{
v21 = v25;
v15 = _mm_loadu_si128(&v22);
v16 = _mm_loadu_si128(&v23);
*(__m128i *)&v20.m256_f32[4] = _mm_loadu_si128(&v24);
*(__m128i *)v20.m256_f32 = v16;
v19 = v15;
((void (__fastcall *)(const char *, __int64, __m128i *, void **, char **))unk_7EFF194980E0)(
"called Result::unwrap() on an Err valueUsage : BabyRev \n",
43LL,
&v19,
&off_7EFF194F3580,
&off_7EFF194F35B0);
}
BUG();
}

主函数里面有一个反debug,比较简单,就是v5的地方,判断了traceid是否为0,调试的时候,直接改ZF值,让跳转不生效即可。

这个函数的主逻辑比较简单,大致如下:

1、检查是否有参数,且不为空字符串。

2、检查是否有traceid

3、检查入参长度是否为32

4、flag[0]=0x49,后面每一字节与前一字节进行异或,比对结果是否为目标值。逻辑可以简化为如下代码
[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
unsigned char checkflag[] = {
0x1A, 0x14, 0x3C, 0x13, 0x22, 0x2F, 0x15, 0x49,
0x4D, 0x20, 0x61, 0x62, 0x1B, 0x26, 0x30, 0x2A,
0x19, 0x07, 0x05, 0x0D, 0x16, 0x01, 0x09, 0x14,
0x07, 0x02, 0x1D, 0x4C, 0x08, 0x78, 0x35
};
char flag[33] = { 0 };
flag[0] = 'I';
unsigned char Targetflag[31] = { 0 };
for (int i = 1; i < 32; i++)
{
Targetflag[i - 1] = flag[i] ^ flag[i - 1];
}
if(memcmp(flag, checkflag, 31))
{
printf("Wrong!");
}
else
{
printf("Conrect!");
}

由此、可以得到逆向计算逻辑如下
[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
char flag[33] = { 0 };
flag[0] = 'I';
unsigned char checkflag[] = {
0x1A, 0x14, 0x3C, 0x13, 0x22, 0x2F, 0x15, 0x49,
0x4D, 0x20, 0x61, 0x62, 0x1B, 0x26, 0x30, 0x2A,
0x19, 0x07, 0x05, 0x0D, 0x16, 0x01, 0x09, 0x14,
0x07, 0x02, 0x1D, 0x4C, 0x08, 0x78, 0x35
};

for (int i = 1; i < 32; i++)
{
flag[i] = checkflag[i - 1] ^ flag[i - 1];
}
printf("%s\n", flag);

得到flag
程序校验

总结一下,这个程序的逆向逻辑其实很简单,难点在于脱upx壳,不过在IDA下调试也不难。另一个难点是64位程序编译时用了大量的xmm寄存器及指令,不常见,但仔细看一下,并不难理解。

posted @ 2025-06-11 21:40  爷很困扰  阅读(40)  评论(0)    收藏  举报