REVERSE-PRACTICE-JarvisOJ-2
DD - Hello
macos文件,无壳,ida分析
start函数和sub_100000C90函数没什么作用
主要的逻辑在sub_100000CE0函数,反调试检测和byte_100001040数组的循环变换,最后打印flag
按sub_100000CE0函数的逻辑写脚本,即可得到flag
byte_100001040=[0x41, 0x10, 0x11, 0x11, 0x1B, 0x0A, 0x64, 0x67, 0x6A, 0x68,
0x62, 0x68, 0x6E, 0x67, 0x68, 0x6B, 0x62, 0x3D, 0x65, 0x6A,
0x6A, 0x3D, 0x68, 0x04, 0x05, 0x08, 0x03, 0x02, 0x02, 0x55,
0x08, 0x5D, 0x61, 0x55, 0x0A, 0x5F, 0x0D, 0x5D, 0x61, 0x32,
0x17, 0x1D, 0x19, 0x1F, 0x18, 0x20, 0x04, 0x02, 0x12, 0x16,
0x1E, 0x54, 0x20, 0x13, 0x14]
start=(0x0000000100000CB0)&0xff
sub_100000C90=(0x0000000100000C90)&0xff
v2 = ((start - sub_100000C90) >> 2) ^ byte_100001040[0]
v1=0
while v1<55:
byte_100001040[v1]-=2
byte_100001040[v1]^=v2
v1+=1
v2+=1
print(''.join(chr(byte_100001040[i]) for i in range(1,len(byte_100001040))))
#[email protected]
APK_500
apk文件,jadx-gui打开
在com.ctf.test.android_ctf_500_test.MainActivity中看到,静态加载了easy库,输入password后,调用easy库中的helloworld函数,验证输入
ApkToolBox反编译apk文件后,ida打开CTF_500\lib\armeabi-v7a\libeasy.so
没有直接在左侧函数窗找到helloworld函数,来到JNI_OnLoad函数
解出三段字符串,分别为函数名,函数的参数,以及函数所在的类
往下走,sub_1198函数为验证输入的password
对输入的password有以下几步变换:
输入的字符转成了十六进制数
input[i]^=i,输入和其下标异或
input[i]+=1,i∈[0,3],前4个字节加1
input的前7个字节按原来的顺序放到最后7个位置上,其余的字节依次向前移动
变换位置后的input与byte_4004异或
input再转成十六进制数,但是不会填充两位,例如 “0x0c”->“0xc”,有一个0被忽略了
最后input和v50比较
bool __fastcall sub_1198(JNIEnv *a1)
{
const char *input; // r4
__pid_t v2; // r0
int v3; // r3
int v4; // r3
char *v5; // r2
int *v6; // r3
int v7; // r0
int v8; // r1
_BYTE *v9; // lr
__pid_t v10; // r0
int v11; // r9
const char *input_; // r10
size_t v13; // r0
signed int input_len; // r9
size_t v15; // r0
const char *v16; // r12
int v17; // r3
int v18; // t1
int v19; // ST0C_4
const char *v20; // ST08_4
char *v21; // r2
signed int v22; // r3
char v23; // r1
int v24; // r0
signed int v25; // r3
const char *v26; // r2
int v27; // r3
char v28; // r0
char *v29; // r2
char v30; // r1
int v31; // r2
char *v32; // r3
char v33; // t1
signed int i; // r3
const char *v35; // r6
signed int v36; // r9
int v37; // t1
char *v38; // r6
int *v39; // r3
int v40; // r0
int v41; // r1
__pid_t v43; // [sp+0h] [bp-2F0h]
int v44; // [sp+0h] [bp-2F0h]
char v45; // [sp+18h] [bp-2D8h]
char v46; // [sp+1Ah] [bp-2D6h]
int v47; // [sp+1Ch] [bp-2D4h]
__int16 v48; // [sp+20h] [bp-2D0h]
char v49; // [sp+22h] [bp-2CEh]
char v50; // [sp+24h] [bp-2CCh]
char v51[128]; // [sp+44h] [bp-2ACh]
char v52[512]; // [sp+C4h] [bp-22Ch]
input = (const char *)((int (*)(void))(*a1)->GetStringUTFChars)();// 读input
v2 = getppid();
v3 = 0;
v43 = v2;
do
v52[v3++] = 0;
while ( v3 != 64 );
v4 = 0;
do
v51[v4++] = 0;
while ( v4 != 32 );
v5 = &v50;
v6 = &dword_2C6F;
do
{
v7 = *v6;
v6 += 2;
v8 = *(v6 - 1);
*(_DWORD *)v5 = v7;
*((_DWORD *)v5 + 1) = v8;
v9 = v5 + 8;
v5 += 8;
}
while ( v6 != (int *)&unk_2C7F );
*v9 = *(_BYTE *)v6;
sub_1064(&v50, 17, 65); // /proc/%d/cmdline
v47 = 0x9021D19;
v48 = 0xD13;
v49 = 0x69;
sub_1064(&v47, 7, 99); // zygote
v10 = getppid();
snprintf(v51, 32u, &v50, v10, v43);
v11 = open(v51, 0);
read(v11, v52, 64u);
sub_107E(v52); // sub_107E函数,大写转小写
close(v11);
if ( !strstr(v52, (const char *)&v47) )
exit(-1);
input_ = input;
v13 = strlen(input);
BYTE2(v47) = 0;
v52[0] = 0;
input_len = v13;
v15 = strlen(input);
v16 = (const char *)&unk_2CF7; // %x
v17 = v15;
while ( input_ - input < v17 )
{
v18 = *(unsigned __int8 *)input_++;
v19 = v17;
v20 = v16;
sprintf((char *)&v47, v16, v18); // %x,输入的字符转成十六进制,不会填充2位
strcat(v52, (const char *)&v47);
v17 = v19;
v16 = v20;
}
v21 = (char *)input;
v22 = 0;
while ( v22 < input_len ) // input[i]^=i
{
v23 = *v21 ^ v22++;
*v21++ = v23;
}
v24 = sub_10A4(); // tracerpid 反调试
if ( v24 )
{
v26 = input;
v27 = 0;
v47 = v24 - v44 + 0x1010101;
do // input[i]+=1,0=<i<=3
{
v28 = *((_BYTE *)&v47 + v27++);
*v26++ += v28;
}
while ( v27 != 4 );
}
if ( input_len > 7 ) // input的前7个字节按顺序放到最后的7个位置,其余的依次向前移动,结果放在v51
{
v25 = 7;
do
{
v29 = &v51[v25];
v30 = input[v25++];
*(v29 - 7) = v30;
}
while ( v25 != input_len );
v31 = (int)(input - 1);
v32 = &v51[input_len - 8];
do
{
v33 = *(_BYTE *)(v31++ + 1);
(v32++)[1] = v33;
}
while ( (const char *)v31 != input + 6 );
v51[input_len] = 0;
}
for ( i = 0; i < input_len; ++i ) // 变换位置后的input与byte_4004异或,再放回input
input[i] = v51[i] ^ byte_4004[i];
v52[0] = 0;
v46 = 0;
v35 = input;
v36 = strlen(input);
while ( v35 - input < v36 )
{
v37 = *(unsigned __int8 *)v35++;
sprintf(&v45, (const char *)&unk_2CF7, v37);// %x
strcat(v52, &v45); // input字符转成十六进制数放到v52
}
v38 = &v50;
v39 = &dword_2C87;
do
{
v40 = *v39;
v39 += 2;
v41 = *(v39 - 1);
*(_DWORD *)v38 = v40;
*((_DWORD *)v38 + 1) = v41;
v38 += 8;
}
while ( v39 != &dword_2CA7 );
sub_1064(&v50, 32, 57); // v50=ddedd4ea2e7bef168491a6cae2bc660\x00
return strcmp(&v50, v52) == 0; // v50和v52比较
}
需要注意的地方有两点:
1、在解最后要去比较的v50时,只能得到31个可见字符"ddedd4ea2e7bef168491a6cae2bc660",原因是使用"%x"做参数转成十六进制数时,某个字节的1个0被忽略,但是并不知道是哪个字节转十六进制时忽略了0,因此需要爆破0的位置
2、byte_4004数组,在ida中直接可见16个字节
其中dword_4007在程序运行时被赋了新值,交叉引用过去就能看到
写逆运算脚本,打印的字符串中有可读意义的即为flag
#coding:utf-8
from Crypto.Util.number import long_to_bytes
byte_4004=[0x85, 0x8B, 0xEC, 0x83, 0xEC, 0x14, 0x83, 0x8D, 0x0C, 0x01,
0x75, 0x5F, 0xC6, 0x45, 0xF3, 0x50]
byte_4004[3]=0x83
byte_4004[4]=0x6c
byte_4004[5]=0x9c
byte_4004[6]=0x83
v50="ddedd4ea2e7bef168491a6cae2bc660"#长度为31
flags=[]
for i in range(32): #爆破被忽略的1个0的位置
p=v50[:i]+'0'+v50[i:]
flags.append(long_to_bytes(int('0x'+p,16)))
for s in flags:
flag=[]
for i in range(16):
p=ord(s[i])^byte_4004[i]
flag.append(p)
flag=flag[-7:]+flag[:-7]
for i in range(4):
flag[i]-=1
for i in range(16):
flag[i]^=i
print(''.join(chr(i) for i in flag))
#Go0dj06_n1cew0rk
DebugMe
elf文件,无壳,ida分析
main函数,主要逻辑为
命令行输入password,sub_A30函数中有个j_fork,不太明白什么作用
往下走,输入和下标循环异或,进入sub_D90->sub_C14验证输入,第二个参数为0
继续向下,输入和数字1循环异或,进入sub_C14验证输入,第二个参数为7
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // r4
size_t i; // r4
int v5; // r5
int v6; // r0
int v7; // r0
int v8; // r7
size_t j; // r4
const char *v10; // r0
char *input_; // [sp+4h] [bp-A4h]
void (__noreturn *v13)(); // [sp+10h] [bp-98h]
int v14; // [sp+18h] [bp-90h]
char v15; // [sp+20h] [bp-88h]
char v16; // [sp+21h] [bp-87h]
char v17; // [sp+22h] [bp-86h]
char v18; // [sp+24h] [bp-84h]
char v19[20]; // [sp+38h] [bp-70h]
char v20[64]; // [sp+4Ch] [bp-5Ch]
if ( argc != 2 )
{
j_puts("Usage:\t program_name password");
j_exit(0);
}
input_ = (char *)argv[1];
if ( sub_A30(2u, (int)argv) ) // 不太明白作用
{
for ( i = 0; i < j_strlen(input_); ++i )
input[i] = input_[i] ^ i; // input[i]^i
}
v3 = 0;
v13 = sub_D90; // check input,主要是sub_C14(input, 0)的调用
v14 = 0;
j_sigaction(7, (int)&v13, 0);
j_sigaction(11, (int)&v13, 0);
do
v20[v3++] = 0;
while ( v3 != 64 );
v5 = 0;
do
v19[v5++] = 0;
while ( v5 != 32 );
j_memcpy(&v18, &unk_213D, 17u);
sub_AE4((int)&v18, 17, 65); // /proc/%d/cmdline
v15 = 36;
v16 = 61;
v17 = 83;
v6 = sub_AE4((int)&v15, 3, 87); // sh
v7 = j_getppid(v6);
j_snprintf(v19, 32, &v18, v7);
v8 = j_open(v19, 0);
j_read();
sub_A0C(v20); // 大写转小写
j_close(v8);
if ( !j_strstr(v20, &v15) )
{
for ( j = 0; j < j_strlen(input_); ++j ) // input[i]^1
input[j] = input_[j] ^ 1;
}
if ( !sub_B1C() ) // 反调试
__breakpoint(0);
if ( sub_C14(input, 7) ) // sub_C14(input, 7) 的调用,和上面的的调用比较,第二个参数不同
v10 = "you win!\nFlag is your password!";
else
v10 = "The password you input is wrong!";
j_puts(v10);
return 0;
}
进入sub_C14函数,重要的逻辑在switch case语句中按照v3的值,验证输入的内容,LABEL_26的代码,是变换v3的值,实际上v3 = 7 * (v3 + 1) - 11*sub_E14(7 * (v3 + 1), 11)
把sub_E14函数的代码抠出来,编写c程序
两次调用sub_C14函数时,第二个参数(也就是v3的初始值)分别为0和7,于是验证顺序分别为
v3初始为0:0 7 1 3 6 5 9 4 2
v3初始为7:7 1 3 6 5 9 4 2
当v3为2时,sub_C14函数返回1
可以看到,v3初始为7时,比v3初始为0时,前者少验证了1位,其他的验证顺序是一致的
int __fastcall sub_C14(_BYTE *a1, int a2)
{
_BYTE *input; // r4
int v3; // r7
int v4; // r3
int v5; // r5
int v6; // r0
int v7; // r0
int v8; // r6
int result; // r0
int v10; // r1
char v11; // [sp+1Ch] [bp-9Ch]
char v12; // [sp+28h] [bp-90h]
char v13; // [sp+38h] [bp-80h]
char v14[20]; // [sp+48h] [bp-70h]
char v15[64]; // [sp+5Ch] [bp-5Ch]
input = a1;
v3 = a2; // v3=a2,第二个参数在两次调用中不同,0和7
while ( 2 )
{
v4 = 0;
do
v15[v4++] = 0;
while ( v4 != 64 );
v5 = 0;
do
v14[v5++] = 0;
while ( v5 != 32 );
j_memcpy(&v12, &unk_2113, 0xFu);
sub_BF8((int)&v12, 15, 107); // /proc/%d/wchan
j_memcpy(&v13, &unk_2122, 0xFu);
sub_BF8((int)&v13, 15, 116); // sys_epoll_wait
j_memcpy(&v11, &unk_2131, 0xCu);
v6 = sub_BF8((int)&v11, 12, 114); // ptrace_stop
v7 = j_getppid(v6);
j_snprintf(v14, 32, &v12, v7);
v8 = j_open(v14, 0);
j_read();
sub_A0C(v15); // 大写转小写
j_close(v8);
if ( j_strstr(v15, &v13) ) // 反调试
return -1;
result = -((unsigned int)j_strstr(v15, &v11) >= 1);
if ( result == -1 ) // 反调试
return result;
switch ( v3 ) // 验证input内容
{
case 0:
if ( *input == 105 )
goto LABEL_26;
return 0;
case 1:
if ( *input != 101 )
return 0;
goto LABEL_26;
case 3:
if ( *input != 110 )
return 0;
goto LABEL_26;
case 4:
if ( *input != 100 )
return 0;
goto LABEL_26;
case 5:
if ( *input != 97 )
return 0;
goto LABEL_26;
case 6:
if ( *input != 103 )
return 0;
goto LABEL_26;
case 7:
if ( *input != 115 )
return 0;
goto LABEL_26;
case 9:
if ( *input == 114 )
{
LABEL_26:
sub_EAC(7 * (v3 + 1), 11); // 调用sub_EAC函数时第二个参数永不为0,相当于直接调用sub_E14函数
++input;
v3 = v10; // 实际上,v3=7 * (v3 + 1) - 11*sub_E14(7 * (v3 + 1), 11)
continue;
}
return 0;
default:
return 1;
}
}
}
由于v3初始为0时,sub_C14函数验证了8位,在调用sub_C14函数前,输入的变换是,input[i]^i
于是写逆运算脚本即可得到8位小写字母,即为flag
#coding:utf-8
#按case 0 7 1 3 6 5 9 4 2 顺序装值
a=[105,115,101,110,103,97,114,100]
print(''.join(chr(a[i]^i)for i in range(len(a))))
#irgmcdtc
FindPass
apk文件,jadx-gui打开
在com.example.findpass.MainActivity中,GetKey方法的逻辑为
获取输入的fkey,已知的ekey做变换,fkey和变换后的ekey比较,验证输入fkey
按照GetKey方法的逻辑写脚本即可得到flag
ekey="Tr43Fla92Ch4n93"
ekey_data=[ord(ekey[i]) for i in range(len(ekey))]
f=open("D:\\ctfdownloadfiles\\src.jpg","rb")
cha=f.read(1024)
f.close()
for i in range(len(ekey)):
if ord(cha[ord(ekey[i])])<128:
tmp2=ord(cha[ord(ekey[i])])%10
else:
tmp2 = (-(ord(cha[ord(ekey[i])])&0x7f)) % 10
if i%2==1:
ekey_data[i]+=tmp2
else:
ekey_data[i]-=tmp2
print(''.join(chr(i) for i in ekey_data))
#Qv49CmZB2Df4jB-