WP
红包时间过了,我就发下设计思路吧,供其他同学学习:
jadx打开,onClick判断eq方法是否为666,是的话用输入RC4解密内置字符串得到红包码:
public class MainActivity extends Activity {
public native int eq(String str);
/* access modifiers changed from: protected */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.loadLibrary("native-lib");
setContentView(R.layout.activity_main);
final Button btn = (Button) findViewById(R.id.check);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
TextView tv = (TextView) MainActivity.this.findViewById(R.id.input);
String input = tv.getText().toString();
if (MainActivity.this.eq(input) != 666) {
Toast.makeText(MainActivity.this, "不对哦,再试试叭~", 0).show();
return;
}
tv.setText("厉害啦~你成功了✧*。(ˊᗜˋ*)✧*");
tv.setTextColor(MainActivity.this.getResources().getColor(R.color.colorAccent));
tv.setEnabled(false);
btn.setEnabled(false);
((TextView) MainActivity.this.findViewById(R.id.code)).setText("支付宝红包口令:" + MainActivity.this.getCode(input));
}
});
}
public String getCode(String input) {
try {
return RC4Util.decryRC4("79adb30330b50997ba81e07e4636e13d1544fed7763315224c70", input, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
}
IDA打开libnative-lib.so,在JNI_OnLoad的RegisterNatives中找到对应的函数:
int __fastcall sub_84C4(_JNIEnv *a1)
{
int x; // r5
const char *v2; // r4
int v3; // r1
int result; // r0
size_t v5; // r0
_BYTE *ret; // r5
unsigned int i; // r4
unsigned int v8; // r1
int v9; // r4
unsigned __int8 input; // [sp+4h] [bp-34h]
unsigned int v11; // [sp+8h] [bp-30h]
int isdbg[4]; // [sp+10h] [bp-28h]
x = 0;
v2 = (const char *)((int (*)(void))a1->functions->GetStringUTFChars)();
isdbg[0] = dbg1();
isdbg[1] = dbg2();
isdbg[2] = dbg3();
isdbg[3] = dbg4();
while ( x != 4 )
{
v3 = isdbg[x++];
if ( v3 )
return anti();
}
v5 = strlen(v2);
ret = malloc(v5 + 1);
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string<decltype(nullptr)>(
&input,
v2);
for ( i = 0; ; ++i )
{
v8 = v11;
if ( !(input << 31) )
v8 = (unsigned int)input >> 1;
if ( i >= v8 )
break;
ret[i] = *(_BYTE *)std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::at(
&input,
i) ^ 0xA;
}
v9 = memcmp(ret, &unk_16C75, 0x1Bu);
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&input);
result = 0;
if ( !v9 )
result = 666;
return result;
}
v2是输入,乍一看前面的几行没什么用,从v5 = strlen(v2);
开始同v2有关。算法是将输入异或0xA后,同unk_16C75做比较,解密算法:
for i in [0x5E, 0x62, 0x63, 0x79, 0x55, 0x43, 0x79, 0x55, 0x43, 0x64, 0x69, 0x65, 0x78, 0x78, 0x6F, 0x69, 0x7E, 0x55,
0x5E, 0x78, 0x73, 0x55, 0x4B, 0x6D, 0x6B, 0x63, 0x64]:
print(chr(i ^ 0xA), end="")
得到flag:This_Is_Incorrect_Try_Again,结束,输入拿红包。
不对?什么姿势错了?
回头看下前面的几行,执行四个函数后将结果赋值给isdbg数组,循环内遍历数组结果,结果非0则返回anti函数:
int anti()
{
return 0;
}
看下这四个函数是什么:
signed int dbg1()
{
int v0; // r11
FILE *v1; // r4
int v3; // [sp+0h] [bp-1018h]
int v4; // [sp+1008h] [bp-10h]
v4 = v0;
_aeabi_memclr8(&v3, 4096);
v1 = popen("cat /proc/net/tcp |grep :5D8A", "r");
if ( fgets((char *)&v3, 4096, v1) )
return 1;
pclose(v1);
return 0;
}
signed int dbg2()
{
char *v0; // r6
char *v1; // r5
char *v2; // r1
signed int result; // r0
FILE *stream; // [sp+4h] [bp-102Ch]
char v5; // [sp+8h] [bp-1028h]
_aeabi_memclr8(&v5, 4096);
stream = popen("ps", "r");
while ( fgets(&v5, 4096, stream) )
{
v0 = strstr(&v5, "android_server");
v1 = strstr(&v5, "frida");
v2 = strstr(&v5, "gdb");
result = 1;
if ( v0 || v1 || v2 )
return result;
}
pclose(stream);
return 0;
}
signed int dbg3()
{
FILE *v0; // r4
int v1; // r5
char s; // [sp+4h] [bp-11Ch]
__int16 v4; // [sp+Eh] [bp-112h]
char v5; // [sp+84h] [bp-9Ch]
syscall(20);
sub_884C(&v5);
v0 = fopen(&v5, "r");
while ( fgets(&s, 128, v0) )
{
if ( !memcmp(&s, "TracerPid", 9u) )
{
v1 = atoi((const char *)&v4);
fclose(v0);
if ( v1 )
return 1;
}
}
return 0;
}
signed int dbg4()
{
int v0; // r11
DIR *v1; // r0
DIR *v2; // r4
unsigned int v3; // r6
struct dirent *v4; // r0
signed int result; // r0
char v6; // [sp+8h] [bp-118h]
int v7; // [sp+110h] [bp-10h]
v7 = v0;
_aeabi_memclr8(&v6, 256);
getpid();
sub_8890(&v6);
v1 = opendir(&v6);
if ( !v1 )
return 0;
v2 = v1;
v3 = 0;
while ( 1 )
{
v4 = readdir(v2);
if ( !v4 )
break;
if ( (unsigned __int8)(v4->d_name[8] - 48) < 0xAu )
++v3;
}
result = 0;
if ( v3 > 0xA )
result = 1;
return result;
}
平凡无奇的反调试函数,检测被调试就不向下执行,直接返回0,那就不用看了。
那就没思路了。是这样的嘛?
注册的函数源码如下:
static jint func(JNIEnv *env, jobject, jstring n) {
const char *input = env->GetStringUTFChars(n, 0);
int dbg[4];
dbg[0] = CheckPort23946ByTcp();
dbg[1] = SearchObjProcess();
dbg[2] = readStatus();
dbg[3] = CheckTaskCount();
for (int i = 0; i < 4; i++) {
__asm __volatile (
"mov r7, %0\n"
"mov r2, %1\n"
::"r" (i), "r" (input)
:"r7", "r2");
if (dbg[i]) return anti();
}
unsigned char *a = static_cast<unsigned char *>(malloc(strlen(input) + 1));
std::string str(input);
for (int i = 0; i < str.length(); i++) a[i] = str.at(i) ^ 0xA;
unsigned char data[] = {94, 98, 99, 121, 85, 67, 121, 85, 67, 100, 105, 101, 120, 120, 111, 105, 126, 85, 94, 120, 115, 85, 75, 109, 107, 99, 100};
return !memcmp(a, data, sizeof(data)) ? 666 : 0;
}
为了防frida或自写so来hook API,所以将输入的指针直接传递给了anti函数。
调试起来就会发现,anti函数会被patch成一条未知指令。
在.init_array可以找到对anti的处理,鉴于存在结构体和花指令,这里放源码:
struct sigmux_registration *reg;
reg = sigmux_init();
uintptr_t addr = (uintptr_t) reg->handle - (uintptr_t) _; //anti
sysc(52, 73, ~(PAGE_SIZE - 1) & ((uintptr_t) _ & 0xFFFFFFFE), PAGE_SIZE * 2, 7, 0, 0, 0); //mprotect
if (addr & 1) { //thumb mode
unsigned char break_insns[] = {0x01, 0xde};
memcpy(reinterpret_cast<void *>((addr & ~1)), break_insns, 2);
} else {
unsigned char break_insns[] = {0xf0, 0x01, 0xf0, 0xe7};
memcpy(reinterpret_cast<void *>(addr), break_insns, 4);
}
sysc(0x78001, 0x88003, addr, addr, 0, 0, 0, 0); //__clear_cache
处理了SIGTRAP信号,也就是断点指令,本意是让IDA运行到anti后死循环,结果发现IDA能识别非自己设置的断点并传递给程序,实为本人疏忽。处理部分若isdbg的前三个结果为1,则将anti函数修复,pc设为anti继续执行,否则从r2得到输入传给r1进行校验,然后将r0赋值为r2,pc设置为一个空函数,从而改变返回值:
if (sc->arm_pc == addr - (addr & 1)) {
if (sc->arm_r7 != 3) {
unsigned char origin_insns[] = {0x00, 0x20, 0x70, 0x47};
memcpy((void *) (addr & 1 ? ((uintptr_t) addr & ~1) : addr), origin_insns, 4);
sysc(0x3C000, 0xCC002, addr, addr, 0, 0, 0, 0); //__clear_cache
sc->arm_pc = addr;
} else {
unsigned long ret;
__asm __volatile (
"mov r1, %0\n"
::"r" (sc->arm_r2)
:"r1");
check();
__asm __volatile (
"mov %0, r2\n"
:"=r" (ret):
:"r2");
sc->arm_r0 = ret > 65535 ? 0 : ret;
sc->arm_pc = (unsigned long) callback;
}
}
check函数从r1得到输入,接着将每个字符减去20,拆分为各17字节长的三部分str1、str2、str3,并要求str2[0] <= str3[0]以限制多解,接着进行大数运算,将str1、str2、str3三次方,记为a、b、c,记d = b + c,将a和d右对齐,记e = d - a,最后判断e的前49位为'0',以及第50位为'4'、51位为'2',结果正确将r2赋值为666:
static void check() {
char *input;
int ret = 0;
__asm __volatile(
"mov %0, r1\n"
:"=r" (input): :"r1");
char str[M], a[M], b[M], c[M], d[M], e[M], f[M] = {0};
int i, j, k;
for (i = 0; i < _strlen(input); i++) str[i] = input[i] - 20;
char *str1 = substring(str, 0, 17);
char *str2 = substring(str, 17, 17);
char *str3 = substring(str, 34, 17);
if (str2[0] > str3[0]) {
goto end;
}
pow3(str1, a);
pow3(str2, b);
pow3(str3, c);
add(b, c, d);
_strcpy(e, a);
for (i = 0; d[i]; i++);
for (j = 0; e[j]; j++);
for (k = M - 2; i > 0; k--, i--) {
d[k] = d[i - 1];
d[i - 1] = 0;
}
for (k = M - 2; j > 0; k--, j--) {
e[k] = e[j - 1];
e[j - 1] = 0;
}
sub(d, e, f);
for (i = 0; !f[i]; i++);
_strcpy(e, &f[i]);
for (i = 0; i < _strlen(e); i++) {
if (i <= 48) {
if (e[i] != 48) {
goto end;
}
} else {
if (e[i] != (51 - i) * 2 + 48) {
goto end;
}
}
}
ret = 666;
end:
__asm __volatile (
"mov r2, %0\n"::"r" (ret):"r2");
}
综上,这是一个丢番图方程,即求b^3+c^3-a^3==42,百度一下即可得解,随便贴个链接:http://www.360doc.com/content/19/0910/09/7442640_860164846.shtml,倒推回去得到flag:LDIGLKGLLEFDKIMKHEFJDFEFGFMKGGIJGELDHGIKILEHILEKIEI
sysc函数是自写的syscall,在其上面有一个函数会导致IDA指令识别错误:
修复方法Alt+G切换为thumb指令即可:
从去年12月开始学安卓,由于本人懒得要死,也是想到什么就学什么,所以一直没有长进,大一快结束了,希望大二有所收获吧。