好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 shuaiyue 于 2019-4-24 00:20 编辑
下载:https://www.52pojie.cn/forum.php ... peid%26typeid%3D262
双进程守护,无法反调试,肉疼,换了N多版本和真机无法运行。谁知道为啥的回复下,最后用夜神模拟器试试DDMS无法显示进程,各种肉疼,无奈索性静态分析吧。
乱糟糟的,凑合看吧。
流程:jadx加载dex - > 分析入口函数
private native boolean check(String str); //so
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView((int) R.layout.activity_main);
this.mEdit = (EditText) findViewById(R.id.mKeyEdit);
this.mButton = (Button) findViewById(R.id.mCheckBtn);
this.mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String flag = MainActivity.this.mEdit.getText().toString();
if (MainActivity.this.check(flag)) { //JAVA层爆破
Toast.makeText(MainActivity.this, "great:flag{" + flag + "}", 0).show();
} else {
Toast.makeText(MainActivity.this, "Error, try again", 0).show();
}
}
});
在继续跟踪so在哪里加载的
//注意这里JAVA层运行先执行APP再到-》MainActivity
public class App extends Application { //主要是做初始化用的
private native int checkDebugger(); //动态函数
static {
System.loadLibrary("check");
}
protected void attachBaseContext(Context arg1) {
super.attachBaseContext(arg1);
}
public void onCreate() {
super.onCreate();
if (checkDebugger() != 0) { //第一次执行
}
}
接着到IDA 分析so 先看看checkDebugger()函数 代码很长简化下
int checkDebugger()
{
signed int mark; // r7
int v1; // r9
__pid_t v2; // r0
_BOOL4 v3; // r3
int cid; // r0
int v5; // r1
int v6; // r2
int v7; // r4
int v8; // r0
__pid_t fpid; // [sp+Ch] [bp-44h]
char v11; // [sp+10h] [bp-40h]
int v12; // [sp+14h] [bp-3Ch]
char v13; // [sp+18h] [bp-38h]
char v14; // [sp+19h] [bp-37h]
char v15; // [sp+1Ah] [bp-36h]
char v16; // [sp+1Bh] [bp-35h]
char v17; // [sp+1Ch] [bp-34h]
char v18; // [sp+1Dh] [bp-33h]
char v19; // [sp+1Eh] [bp-32h]
char v20; // [sp+1Fh] [bp-31h]
char v21; // [sp+20h] [bp-30h]
char v22; // [sp+21h] [bp-2Fh]
char v23; // [sp+22h] [bp-2Eh]
char v24; // [sp+23h] [bp-2Dh]
char v25; // [sp+24h] [bp-2Ch]
char v26; // [sp+25h] [bp-2Bh]
char v27; // [sp+26h] [bp-2Ah]
char v28; // [sp+27h] [bp-29h]
mark = 6;
LABEL_2:
if ( --mark && !pipe(&gpipe) ) // 对于整数 ,当n为0时,转换为布尔值就是假,此时 !n 就是 真 进入if后面的语句块
// fd[0]指向管道的读端,fd[1]指向管道的写端
{
fpid = fork(); // 系统先给新的进程分配资源_简单理解克隆自己创建一个新的进程
prctl(4, 1); // 获取子进程接收器
v1 = fpid;
if ( !fpid ) // 为真子进程干活了。
{
cid = getppid(); // 子进程ID
safe_attach(cid, v5, v6); // 安全附加
close(gpipe); // 关闭管道
v7 = dword_3034;
memset(&v13, v1, 0x11u); // 初始化数组
v13 = 74;
v14 = 117;
v15 = 115;
v16 = 116;
v17 = 72;
v18 = 97;
v19 = 118;
v20 = 101;
v21 = 65;
v22 = 84;
v23 = 114;
v24 = 121;
v25 = 33;
v26 = 33;
v27 = 33;
v28 = 33;
write(dword_3034, &v13, 0x10u); // 子进程向管道里写数据
// arrys[] = {74,117,115,116,72,97,118,101,65,84,114,121,33,33,33,33}
v8 = close(v7); // 成功执行后会返回0,否则返回-1
handle_events(v8); // 处理接收到的连接
exit(1);
}
close(dword_3034);
pthread_create(&v11, 0, parent_read_thread, &fpid);// 父进程--线程管道读取
do
{
v2 = waitpid(fpid, &v12, 1);
if ( v2 > 0 )
v3 = (v12 - 1) <= 0; // 正常
else
v3 = v2 == 0; // 检测到子进程在玩火
if ( !v3 )
{
kill(fpid, 9); // 强制杀死子进程
goto LABEL_2; // 在去制造个儿子
}
}
while ( !sflag[6] ); // 0循环,非0退出 ,一直在循环
pthread_create(&v11, 0, child_attach_thread, &fpid);// 监控子线程
}
return 0;
}
safe_attach 重点注释
if ( ptrace(PTRACE_ATTACH, cid) < 0 ) // ptrace系统调用提供了一种方法,这个方法可以让一个进程监视
// 控制另一个进程的执行,并且可以查看和更改被追踪进程的内存和寄存器。
// 通常用来下断点和调试。
// 子进程调用PTRACE_TRACEME,表明这个进程由它的父进程来跟踪
// 任何发给这个进程的信号signal(除了SIGKILL)将导致该进程停止运行
// 而它的父进程会通过wait()获得通知。另外,该进程之后所有对exec()的调用都将使操作系统产生一个SIGTRAP信号发送给它,这让父进程有机会在新程序开始执行之前获得对子进程的控制权。
// 成功返回0。错误返回-1。errno被设置
goto Fail_Code; // GOTO执行失败
v8 = 0; // 返回子进程结束状态值。 子进程的结束状态值会由参数 status 返回
while ( 1 ) // 循环作用主要是子进程退出时要干的事情
{ // #define __WALL 0x40000000 等待所有类型的子进程,包括"clone"和"non-clone"
result = waitpid(v3, &v8, 0x40000000); // 如果在调用waitpid()函数时,当指定等待的子进程已经停止运行或结束了,则waitpid()会立即返回;但是如果子进程还没有停止运行或结束,则调用waitpid()函数的父进程则会被阻塞,暂停运行。
// 这调试!
// 如果不关心子进程为什么推出的话,也可以传入空指针
if ( result != -1 ) // 失败
break;
if ( *_errno(-1) != 4 )
goto Fail_Code;
}
handle_events 函数的分析
// //事件处理函数
__pid_t __fastcall handle_events(int a1)
{
__pid_t result; // r0
__pid_t v2; // r5
const char *v3; // r0
int v4; // r4
signed int v5; // r3
int v6; // [sp+0h] [bp-18h]
int v7; // [sp+4h] [bp-14h]
v6 = a1; // 成功执行时,返回状态改变的子进程标识
v7 = 0;
do
{ // 监控自己子进程
while ( 1 )
{
result = waitpid(-1, &v7, 0x40000000); // 参数-1 = 等待任一个子进程
v2 = result;
if ( result != -1 )
break;
if ( *_errno(-1) != 4 )
goto LABEL_4;
}
if ( result < 0 )
{
LABEL_4:
v3 = "waitpid";
LABEL_13:
perror(v3);
exit(1);
}
if ( (v7 & 0x7F) == 127 && ((v7 + 1) & 0x7F) <= 1 )// 先来对wait status做个整体总结,一般我们通过wait status可以判定子进程发生了以下事件:
//
// (1)子进程通过传递一个整形参数给exit(或者_exit)而正常退出
//
// (2)子进程被一个信号终止
//
// (3)子进程被一个信号暂停(调用waitpid时需指定WUNTRACED标志)
//
// (4)暂停的子进程被信号SIGCONT恢复(调用waitpid时需指定WCONTINUED标志)
//
{
v4 = v7 >> 8;
v5 = may_cause_group_stop(v4) ? 0 : v4;
result = ptrace(PTRACE_CONT, v2, 0, v5, v6);// 简单理解只要子程序被调试中断将被退出
if ( result < 0 )
{
v3 = "PTRACE_CONT";
goto LABEL_13;
}
}
}
while ( v7 << 25 && ((v7 + 1) & 0x7F) <= 1 ); // 成功执行时,返回状态改变的子进程标识
return result;
}
分析完看到了SFLAG 数组,用于后面后面 check(JNIEnv *a1, int a2, char *getText) 做亦或运算用。
接着来看看check 函数
bool __fastcall check(JNIEnv *a1, int a2, char *getText)
{
JNIEnv *v3; // r4
char *strpass; // r7
const void *v5; // r8
int v6; // r2
_BOOL4 v7; // r5
char v9; // [sp+0h] [bp-38h]
char v10; // [sp+1h] [bp-37h]
char v11; // [sp+2h] [bp-36h] //所以第四位是0
char v12; // [sp+4h] [bp-34h] //注意这里和上面少了一位
char v13; // [sp+5h] [bp-33h]
char v14; // [sp+6h] [bp-32h]
char v15; // [sp+7h] [bp-31h]
char v16; // [sp+8h] [bp-30h]
char v17; // [sp+9h] [bp-2Fh]
char v18; // [sp+Ah] [bp-2Eh]
char v19; // [sp+Bh] [bp-2Dh]
char v20; // [sp+Ch] [bp-2Ch]
char v21; // [sp+Dh] [bp-2Bh]
char v22; // [sp+Eh] [bp-2Ah]
char v23; // [sp+Fh] [bp-29h] 最后一位是0
v3 = a1;
strpass = getText;
if ( ((*a1)->GetStringLength)() != 16 )
return 0;
v5 = ((*v3)->GetStringUTFChars)(v3, strpass, 0);
memset(&v9, 0, 0x11u);
v12 = 1;
v6 = 0;
v19 = 0x11;
v9 = 30;
v10 = 29;
v11 = 18;
v13 = 18;
v14 = 51;
v15 = 11;
v16 = 37;
v17 = 120;
v18 = 38;
v20 = 64;
v21 = 79;
v22 = 74;
v23 = 82;
do
{
*(&v9 + v6) ^= sflag[v6]; // FLAG是上面checkdebugger 函数动态生成的。
++v6;
}
while ( v6 != 16 );
v7 = memcmp(v5, &v9, 16u) == 0; // 爆破关键位置
((*v3)->ReleaseStringUTFChars)(v3, strpass, v5);// 释放
return v7;
}
这里注意,就是V9 字符串的问题,上面我注释了。第四位和最后一位。
开VS2017
int main()
{
char sflag[] = { "F8LEFT" };
char str[] = { 30,29,18,0,1,18,51,11,37,120,38,17,64,79,74,82,0 };
char flag[] = { 74,117,115,116,72,97,118,101,65,84,114,121,33,33,33,33 };
char str2[17] = { 0 };
for (size_t i = 0; i < 16; i++)
{
str2= str ^ flag;
}
cout << str2 << endl;
}
最终密码=ThatIsEnd,Thanks
|
免费评分
-
查看全部评分
|