From b81de5660147986370133b835a797dd27aa74c6f Mon Sep 17 00:00:00 2001 From: sssnake Date: Tue, 31 Mar 2026 09:45:35 -0700 Subject: [PATCH] Add driver spoofing + stealth system Driver spoof: mount namespace isolation keeps stock files visible to verification (dm-verity, Play Integrity, hash checks) while custom drivers load into target processes (surfaceflinger, wpa_supplicant, bluetooth). SELinux context, timestamps, perms, ownership all cloned from stock. Per-process or global modes. Configurable driver map for GPU, WiFi firmware, BT firmware. Stealth: process name masking (rtl_tcp->mediastream, etc), non-stock prop removal, MAC randomization (WiFi+BT), USB device permission tightening, log purging, logcat suppression. Full mode combines all stealth features. WebUI panels for both spoof and stealth control. --- customize.sh | 2 + driver-manager-v1.0.0.zip | Bin 32406 -> 39009 bytes scripts/driver_spoof.sh | 298 +++++++++++++++++++++++++++++++++++++ scripts/rtl_mode_switch.sh | 35 ++++- service.sh | 153 +++++++++++++++++++ webroot/index.html | 159 ++++++++++++++++++++ 6 files changed, 643 insertions(+), 4 deletions(-) create mode 100755 scripts/driver_spoof.sh diff --git a/customize.sh b/customize.sh index 00792e1..af7cde5 100644 --- a/customize.sh +++ b/customize.sh @@ -39,6 +39,8 @@ echo "standard" > "$MODPATH/config/bt_mode" echo "sdr" > "$MODPATH/config/sdr_mode" echo "auto" > "$MODPATH/config/gamepad_mode" echo "off" > "$MODPATH/config/decoder_mode" +echo "off" > "$MODPATH/config/stealth_mode" +echo "0" > "$MODPATH/config/spoof_enabled" echo "100.0M" > "$MODPATH/config/fm_freq" echo "24M:1800M" > "$MODPATH/config/spectrum_range" diff --git a/driver-manager-v1.0.0.zip b/driver-manager-v1.0.0.zip index debe592071368046c3b47c0d2f5eaedf23c0ef30..cc6b8867a2440322843e083bbc1adfb4a506259d 100644 GIT binary patch delta 19621 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 MU1;r?@Jsvu0sj+mbpQYW delta 13028 zcmZX*b8u%**gY8AnM`ckHYYYFnq*?zy0QI2sgzKe|po*r|g^c?XYwqj%y5^m?1D=op~Dz%miRz>vTQz>Hm;UF>Zwy-b;$Ei}~O z!627cCM=g%K-Az2fTg?*_v~E8{pF|*gJtrDhJ5Q?hdYIC>=N}nqB;zs+ty@fwkLa~ zJ8Zru*VlOmrpDR9P5#z}TX@d(hG@p)jgX&a400G|#p8yP&6CwfF|}cZC-wl+w^G3w z`hA9Fw#Mz;?wcF@UmXd#u|Yp(|9(fco*tyWpy0q7Ho><9X-RjrRZb!4dD!p?%+d|){>v7tyGpvpWiMZ$4J zkGJ%x#G2$k0sd!->O4ui9Z?jD>+gq~0}d=%=*WB`MtKXG(&e-mNIL_i@reXxl8L>D z3>=5E$y(c=f=A`yAxcrKY~UMvv6x*>-ELwG-Am{%8)Uj>R>|-4CQdnDERq}Q-Gw~q z;|kk+_s>o35ZOzCf_n_-l@+;jpSiqdl%x37vBeN6zziKqs!E!{Az9LCr&x}(e9Y;3 z;xuNO%Ao%~_KGwP>xuVNm6Kubc%anYaEFZ9toX%B;NFT#TB&+FIeP!G@MviWVFRJi z6?O!na<#nSi!&8LU1Wh~m53tE@{MEMVV^C%fUJ4K@Ce@X&i*gp&DkFH&pri~ei8dr z12k6@&{$InUI8zf^erpxquABFJnm%4$}i*loheqfxH9(mJuyI#Tgf@!)phjJRy`OGb173*QOs+v z+!7tD9pm=9H(Y;+8Cf3|i!nZ&m9lvq=%5&Y^oX+IBigyg(fJx!Nt=v$o9N9Ve+DbN3pZ1| z`@KA&dkN=c*(9vJvuF*wkHcO+e520P`d44e5!LxGNB6$&M`mR?NGK3mB2Hrpv;{ce%m?VHjs6VqLjeN|W(5O7 z_;;*0n>x8!8vj3s%JcGs<$uG~0}%`y>KGCX?0=>Wd~4@jsoM@e$*_$sk*`pHHm5?9 z)J*aBT#fJ{{&bXg&|Aezu~_G;aA5oyD>?NWeD8myTo&{q5qYY2t8f~Doh|Gr1q|jl z=Gi-+Dr)RBM$PZ*uTQP4I{n>94h8(359{GSmU>M|VTXd+>Ug^23}|>x^fh;aj>G8) z+R;&qkxDginqD;wbC5B)_6%j1k&4d92z5ak!-y1)ePW#f)+ZoG_|7OHYny8#T5->#6wbUfr+9nkApc z*Iy8y&&xeO3E;V_;FZWR=W!qYWUQ!p+dp=|B%wl%*~m?AUms`XeAzZDzzA@BsvN3# z3X-@sFAwPzrF*iVvjITEDkWHpf%v&Acy)&zND2WJ2|wO?F@KB->JGch0$wc|F+I2W zTI@feRb{B7N%eWrNn1+be~jWbG#FCg-1O_YC+AM|?Wm?F;E`)4`+cKJ0$*)MP_&3j zz!;4%)g_mjqnjZ@M~bo`T@}>G`|L2pGTm|U zU|uz?w< z#i{UUf)Yhe(0<(E`W&HyEvA_zbIY{`@2cLMm0b-xi?2(Sf0ruzfMqO?6^1@kVG`wj zc^Pst^s@j-bx(3`mM=xaIyH~?78vf2)^`&GWA2?l#&({@?#5|cTv#gG2m3|v}l`{(9 zxLJ;^nw8fOJSM7!0B9`JEqpIEqc>Oj>gF$UYaya35p6K3#lt6O=!QkFT#&)uC%I_T zw&huqef3#c2&($!6NUq!*Y`0AXjH~Y$KlJfsG2yVUYU(q4M`HzOt8JiW?z}rU_}4= z8|1PkUeI20<#>C6|?tk?oKcxkbgMs zch?HpJMB#1^Pul}p>y+ML8li#2(e_esLe-$5|jvLAe$%y-C4{)6{&EFw?#RfUPvc!& zR)l&x35@2JewT{0demfE(!@(oIVnsYIPE-5wWKi+MF!Op?5O^-`jBDD9smAgxQ))m zYn1Zw5AdfrD^&|eYqH?U@5H#fO8$;Sk*RdhH5$nm)(#{hQOKZVDO+s zDa!;wvJ7W*G+616-A17pfVTUrD2lqET6ir>jbF1|r&IIx<2e|xaGh41f`dk+eJ?4wM zNTAeTDj}yAT13dKh)Usy3hs>S=Is-*P>_1*cH?Y@S$Kb6LF1u_CSUDIjzug+h6$Ly zml54vw9i!Bz}{paT-1{Ct|ZrY0hUAc$u4>vul3qXLT%8RP)%&^2jtsfexZ%>4E{HB zEN``O4Tf(x*5`tHEvRW=l~z~I*vmsQ6o9s8sc(0B=&^^2mcd-bD?@K@xMJ0*sC*HP zYHg+AP{;ULjiia@dUA)g*_JGQxHYpn^H8a4KT!<|0j{T;B|Ya5FTszToMt6vMGIL2 zBTk!Xo32TWoPO1rf`bkn)lpo!bj|+PK~D7p$G_wQYnSDph#w!)Gy0&Hgznd)C%}Ao zzR}}2an+z-khOH%N4xyX>rG*^{~ga&*T~VOa}dFcK|k~on{ok1hjP7jNPyk4O$ug6 zI$yCIa#gg5O8Lv6(dqU1t;e4Wlr8I-*XLufA8IutbGF3;&o)E4-NG5xSxma1qV z930UDKOI@QP>8i7DT9gB!%g)l5rHpu4TNjl$OwH(!;E2Xgx$@{=hxk+p1`s)$Eb$ee6)eKXHl3xII%Ado8 zQ|4R)`r&f`6h{A=82WYia|-(|cav-HMZ$nK4k-+W<^>Hl&sm(Pb}OBJIN(56h=!S3 z+(LYbKL#rN;u38ACtZ0VlF(8|x0Atu}jg{-1$C1`fqUgNuAHCZ?L!^gyTk` zrb_B=>D(J`U*>}%jg3K#O?Fb3%{=9X%?(d!`~C^}Lzp>d*Y8InZ2EUaER?$vIjrfy zXiR<+6TRr_i^EoISnm&Dd-#WE^ zKRs+d{~W*2joSC*bDGzdBhM5UVX1tdq}J6p$~hCO0=onfV}x1+ z+0z_`&kP=sWHK>ZIOpPKMShW*Ix#$YyGJYW@N4T3y6<&s>wcP(eLxs3RULGg8@H&% zL0a&Eo@%?Ci8JE7uE^gIGJ37hfPt?^V(OFGW3wnUsNMd?NVhP2nzco0tIJfcCv*o8_sh*Uw{?fKS+PIuPx;xePYF;k05TtCh-K?5=M`2~+Y&;N(k^o#1AT`zSy&%}8!?dGI;8r;);x8i8hR za&Fo_`LiY0<^k{&@R+>}z$i&S^!ac;;B}Oh9&R2DZ+g@&TJUsa3~+!a^A9{&z8^1* zkd)BPjN7K{5mkvDtu0MwO6@gvuiBmuVuD-FMxY*}dQI{9T?o*p(%y$Tl%JvLJO6(8 zAeK_8no3c&gwUzUKi7-m*()ffmy0Xa?@X4>t3V8vWU-)ZnawnM;KW)9Jj+a0<#?)t2kcFi0tQj>?z#n#N^8=?-8)C-zj z0{s>e5(6sjXqhmcIuMZXYK)NLUXN0VzlD`zzZUade1&{EY8 zd2Z5`NB(?nP4I~el?P^f9WYrvpBEn}+j#YhFaU|`xkV1i7H5`;dhsCn`eL`UnOunL z%ZRXj+FW{!#b1{wtjw6u6&_|mk(25306JDC{3+aZqrONI0#=&{t+_ZAiRD4@bSztjB`{H;1 zK$YHSkq}u4V|f6j_%`=K#4N0x3*b(PI_~&R=(n&FFtZmT2yZ`Me=?+=cj%CIeh2v? zJ{QThNXtLI@WroASIt$xE;2C}Iti@W4 z+Ho+=e>ln*MINkuHN`x$rUn_QgVb_@OPNHMlYD85a`G(+-eVo3pdt0VRKpd%{YLzB z_opbm`aoI)h5H3dRhBDtO8#3b=9Pg*uOR=d3((xzPu3mg6rJ^93N9G>MzXnB4Z&zV zi%PC!|Fz^&-zAU%zQ6LbRQf|YAzPdY_Y=Aik?b3H~v2t+$jM|=RK~Jts^axEE_pl-33eC#JKWb z*+N#UmbHr;W15K<5HiZUX=J<4UK>tlT#mAKR^8gJ9SI8bp=qG@-XR#vq1VVbZb|M{R?D`L-bZz$9gtWph`LcLfT#RvjJWcAX<;?XR>%|#HcE2`v1Ss0NaVWo{)7Uv?66QWH zasD0Sbv+Jz4iog$iiLZ!T^R03ziQ9&iK)G*U0eLhe!JYwii7lk>ZISd%3VI42h?%T z0-*zuAEZb)D52is zb6H`%6>QtrW**yxs?gXBkxJ*uAcffTX0pyvhUa~V&AAEq?#o$OWckG#s8(GBOrb{? z>X!PVkzO_x<{laC-bK1GwrzicU%W%^0xAP_Ss@nImkQiVOf7toLv>~+gkmQ$=$=*%n?%)l&+EpoldNn+}))` zvh(m+w#FHQrS2<3u4)L;mN@U%%Gk5d(Ked(g-<1S_Fs%YV7x6o;k0ej+)Tl5fKkyp z2rT``gay&&7K`3!Ht-4y`@AQEi0_CvZD1;=hEkJCW6p55)V^#uUbyg5jj51eB&@ms z3-24rghE+xcEgEP-BJS>ML{5svKCmN-VKv`7cQ<`Bt{zFeaw2rS4!dxJ;lz1Y-InH z-eyK}9csrO7l*2Rr=rdZy=F&0U{Op_#Id!bLesfVUF)fcEkt3g>QCFxB_nvM)~%WJ zW;7bu2^9Hd*nR66<8GA5L_#xml=fL^HH zhYD@uF{O3oE!u3_E>~*nuiYJE^&dD9pT>j^6P*{Kr0!D6g5R}_eITQ?%6lBhiVI@y zcZDWiQ+6!O=KbC8i%s!qA{ZD(T>psLi7l7lH9B`owZ=zRp9fa&o8vLeI%C4RW~*=7 zl66{J)oP<`mw~aU7ugu<0By1KP&wMW{wwlFR{n2`&cHzE&JpWJJzAYnvwsc-5RWZa zGOzCpH_V{7m*hl2_KRR1`b1kQdCAydI;NM<&>Q+aE5K^)EIFtx>}2l~SueIXX6f&2 zNEu^?jHp|&N!hOyt(On3jcOER${oWbwaYI$vo~zW%&R(m->r^20ILo}`I8*|QLJ%X zmC`I#8akw?M73qZcsRORM&WL0i(2(7Q6hm;H||I&NLOxRS^LCknE)>AQ=EAiSjeK^ zobFewj%~XuX|2JFnDj^so7PQpxA0*t%3*-N0dxfjnhJ0bh|Wk3Hq!DTv_!$gMeNlt%tbM+(Tzy0m4 z787q@Z9RpVUum-G6*yEwOsUpD4z3>sc(JWUNz&yfr3)-pcv2DDDp144lhZ|4{vg3z9?HMZ=+ zJm-Du@lY#C7gz22w_h<>q}y>kVTn*8=gErG3TO{pC{{9^eBgc^ND#)4_hl1p^2MqY zjgCS_Lh2%@oZz_hf$_TH7x+8*P!UB*L!BsIFCx?2WsKkCg8hm2LP9)AlZLAA! zL|R{-Dh0tU0Uq=P+?y|!X!&@yLwAj)I!Zxwnv2y>icj-~uN=apPA5UtPeRZ-oc*>i z!DdfMX5`1(9%qeB@T3?dv3jlehd}qbkqlb0tPPEKsYz@VY0@7^(JW?kuN4Uf(xkl^ z>sN#)oHAnvL1KZ!PERGjEKq_M=8xR&uqeM3v6S^hKpQx}NF$VkhF1$eJClZAh;8DU%|HR?@(;3X*uS`giGNYEpA*l+a)w^$gysavA{ zS^1!js>PnS9FCi5f^xCi=<0q@%{wWWj69d(beSX$rh%c#xOkeOqn^*PvkSnOf!Q*G zVxoos4^ztcBxE7M>^xBFWI$b1{k^D!)ch4`<2HpvlAjZEJ$P=GBPIm$O9W{C?~TRk z=i^^Y;d*=;B8hur$7O2fh=VuCCX9PN|H#Sv=RF^&@uE2y!=s>1iQaPK@ZyZH>Qys5 zrwW4HFou?5B)(?SK@8Q{t)YG^q#<)~Ls)&FJqelZsK)QU^fl5wt=rh~1`ToW-a8DG zyQm;NhC$<@C9}Gkr3_zEUMh|8Ozn=1H^UPJh3OUjDd2m5b;L+!97NDR(9XPFbTNHT6CCgO+^(rK;3g~%7@jE7h2f{XXrNGLT*Yert6;$83)TD_4KlNV7JweB)d%!m_s z-mk#H@sq+vk5EtCer~!>#7kMzgJ%}*z9REo4q~Eeu`l;?ef~$pAbf3?f3vB>igV3_ z1r?!wN^&PC504DTFa4rd8XF{|q_5DvWav08lkUQ$J0DEGIJj^g%P};IqpbP z^)O=KmD;nAdzr!V7-T7A;8JnXmIj~%KH}3(%Dl-(KrQuy|He&L#txEA@5yc!+yJv=J-+c*%l`suHBV7Ge0nE14chQ9c(I4C(uZ z(Ernvh@N8{b@p>?Md2^xwN!sd!t?O%3r4{yh~GSS4flwEw-;mPIXLA(RtRXbJz^?JmhHZ<86MB-I&=!d6+9D2?Kzf zQBNUy?ywdZ7zm^H&v3-V&z+fdODPlDG zQy9pRMvIXgHCtL^EEXI(%^Y5B=|OZA_qwaZ8%hPSCsGzrd38#THGs1 zSa_a$UUnTc-lN@6c}@y#fUi0}akxk-9eQ&zK4ST&xpI+vIMGQ$t8CR2JEC02*0q7kbmY;YTkCIu)~9c^PScz{A? z#r*1gNzjJFs!QJGQZ83ycvdVP8t+C-{RLOwFJj|0`#@U^<4lg;uab;uunImqc@Kl% z(`IdgS-(NH*iaJvl6U1bP%W!{!0 z_+nFq-8{c(1QtCTmuvk}Ac%}>G6VLpr@B^1vH$A7nRj9ewFeoXv<;wmUNJFDUk~pO z?wVhsJg1*to+ez4`zMJwEnZEGe!vPc4s$yMh(Dz^1j<%A^mc(I6*bB=Zrf$VJio*omU;skjXw!i^ z#yPkx&s2{7A^Oqs8wGI3R*ft-b&Y$=dG%aT57d;rt_zfI4AL5+crh3`cgRW7c;^H@ zj6N$t9*TP=Cj_dT*-m6y?-1MDz->AFFIzF?nSA5XoVcYOC=`lE#M9H8`ibOo?aT+Z zgcP+MnbzOBUC1!Zd`(@P?19?Xc%)Yp$3LNZPnY8e-#d^YTraGDL1sTq%Z3pU9+^>^ zoZuxESsUo=Q}PyOmB)5ix^EXy+GVSCtNS8+?XYrJzZ6@9mi--?_#IiqtSTE&)6WSE zy2&(fy>JKHH0!?F=GC-owPy0$QG{06l}5&4H>V|mEgleKS~HM==>r(vDBazQ79(e; z`VBl&edtMFbZ@?5GglZuTXSN3qFDOA!zjBlI;SzE(1sjC5V&7^hy}zr2(DJKJnEL- z)roh$eBY@Gl=Qnfz)(eN*f~AJTBtn@ydky?odT`7OLbtax>P#kfSKQdl@y$A_eUN0 zUhR55#I;s4VYd%uT>_=N4sXc}8_mKv)8R4~{#X*y8UpkVbg0(}sd?%>ygdal;26p! zTkyXE!rmC_zFt=l>!V6X)NDpJA{3M5_f;2C2WorR3jE5&j@E0PGf>~gRu|}DJHbNUY1TOrU2HOaEMr+c8v7DbDG$hqEExr<*c3auQw@bU zJ3+|ZKv34PsGMkvj5^jwcS!O)M-_=8e@p4bq!Ml@yIjz&EbAXw z4s|mznL(tOA}lgxQi+H2Y(jrh8+FVvRyLW}xw~fSXWX-!=81T5MB(d%vtsOb?o7eU zN83`-To!Ss8wFyP!jR&;VLC2(t=H_Z)LDau*<{MZlYbFif`0L_WkWeRh|y(G?85}6 z3iQQ%p@UM%4YGBSTY)&Rq;~qUBT|Y?Ygn0tB@pb}gvGPK^6ze}hqcnH^nK1>tQXlt zKRm6|gLL_CMAyiXJt%zMJYA!P9peA3e+swlFBnvK^4(?L1d)wjx&Fot&Y zxrUV{mw}9@q-Q9Qg}1o3dLqaZ5uW%KYW%eIZg6(r7Gs@$d-esA(A_ihZD=`csA9zD zHv4FWBu#uj)06yW)N(ou?U-TDzumit59zyC-2w^dhpH;C^w&7lKc#qXK3w?U1+I}) zoqFxZGWZ-O)ZCM+d~{Tc-q&$lew5F5#9Xe6YP+`VzIqcv5AQ^%&k+i6_>GeXLgzrm z;P5R^V|{T|^n6=?{&;hTIJAlg@DJB3!;kCwJ1n^yk zSFt<7-b=*PI7U@53d|=hvrG)wo9UtZ3`%oxxxVlMx6B9Ic?7ncKE2Ye$S$d45gD=LZR%QFBQEy(nqW(kiZT!ldXgn ze|!~R{ET)lPP47q!jHqYozxn(1#u;`f1}uEC_$Zz1hMV12s{SUY$rz@4w|@Xsx(@yBIZ zae@4Cv9r*ZD1R`Co42z(f_&KHc-#YT95@!{B9kPJg^IQ(Q(_9ohpy6I)f80Kmkd$! zfia7+^`VQ1xdc)j_$?nT6gmeQNaGgawKJuXn=j7|H(l(QY zgt4u_+2fRo0Kb5bI&U_#`UsZmcRN~$<)fmG(65gXw`=yXV%O}#)kxv6c!o}$`5a23 z)N#aZ5F1DRI9B-I8$O$dGR0)kl0wdia8j6D7M(gdlHFb`N|)U z5opSF-)L%j@}Y&TkH1lO1Oyv2j7M_G`y}V;Rk0_pit=p=j>;5-pav!CbNg`JWd?CP z=TDwFWN*ST)OJYrtOwa>2!$wGEmffZEYM#hq&C)oO#D({B|+d@0ki+Ty7V{s{dY3+ zGUbCIA6LB7XZQV6T=|?3+(Nz{TKdg6ygs-Y7AF%BS(q& z7X&Q9cdhAz2LqEN10lIn0y}H}+5P{5fL+?}POH)wpSh*Slx{{>+Wy&b6NwF#*GBx^ zP$1~6C#yzD&?>^@7V|fl=pP%3jki}+WoIaZ}sgkKw23r69?%C{rJ)2B!k$ubdNl4a@&m8lYH zO;ug9Xb4_AZ=p!r^8r|uQ7kGFA#mECj8NfyB3|INeTKX63#2zj@XI+@AOwmr8{lMv z;epVRTb2iI4C5rkSdZuy_d=yooo~bKwbT83HNSiVpR<(fy8EfKiaW-jbd?ApZMml3 z<&P}7-;7-)vDA)Tj?K5--fmtWA0{_>tqq6YXzq48^}lDy4_KKx%$@1|D9)_>Jf|s> z$$^?*Yd=fCQ%i_{tw{dJbDul?A_4@<^B_tztHRe1GK15Ho~CS;M{YG>QTXeepZBLC zY=^CZ;Bv3FV+nHJZC`Vj+zf5`>)wC0ZCKhp=3eyIM%;yoNec4)4Ss_M2$OJYz6^_?;Ui6@d4fuP8rHpnF#dx>Q0xQv8WG-Rz zSRC7w@Iu_gQ62OnGm5aW3oq~;3USZITY>ukge4(Uz>Q>|N?zsZs^=5ZMB{pe@I$6= z6pgL8QIh~MvL=2F!vxCDgdMfWekus8X3~fU6ZmGfJMKKMm;3oTu8FZ8DaL$R}x|Vu@*U+QcCJn-!B0@i` zK~EQ>w^-aJ$&||ec>qbHy!jrIz?1{DtM--ANt)X5E8YJ0$w3+@C!DgFpJ6C|Wo|H{ zW&#Vkke&z1wzdt4uYt^>xp!V0KiCn6SrT#N)T+wA#Qg~8FCa`nhOI#Rmy{S;nQ0yE z_b{-?bl_DG6X#6(FwjsbbZRoF&g=RF(_mIanlCpu%_+(=*aOB!UB|+>Ol1)cL$C(O z!+?8Z2bYQ8(|jn4MFQ7Ef_Baf)BQG7osOe#g5f1>IkMo{sMO|5d8qAm8JxcNu*#L9 zRXe>gBik2-*QAIM%ghE=TprlGMCi~?p=mHWo<_BjH!_HZ58{?)w8LxhI?VXB4GXdX zvBJp%(RJFHoOCy=)m974(MFUL3tRkt_e>FX zHF>=jv!A&r_%C18adVj8E7IooN4#Z?6PmU9vFtewgW4k&_eZe2N$wjD&vi*VwX7(i zeBoU60z(S2v3yHR3bmE=I*i@y0U5`w3gO}_$bWsfZUOA7K*l?VYu07#ij?mQ>`BnN zB2W3Top5OVRESU1u({gQ|76NEfiXD|?*gHG5g*hd$3INVcG zejuFENlFYC&qY7WY9Ow;%Lvm3YxnQsn5Vss&;Omj{8{s<5mqUq!YEz*jxK|p3$O02UB*4EoK-{jo)iK1|{BBMsr`4Nh8A@dtdegaBP?9~bkN z0BR_+oZ?W1g#%fTSGo_WhHEr)gZBnTm~K+Ete&}TThTbz1ft`9+s_5{%1H*0;CS1f z1#86q_-iOe?OUzYTjHk+npNP;eemg70YQ;K4?rc4b(YDc65m3tIy~hiX#G7S>DUtO z>({GEDvsw-lW|06#h2er1v7%&f(N&-$?mHX{C!Cr+E{y%QXDj$HZ(RzQo=Y zK~j+IS3=ae1G$b_yZKEp?yUAk_0AsZsJoDsIkvV#i6en zJ_GF$6ErUGJQA1_m+u<_6nm*pI_^J8&d*Y6;M=I|IQZ9JoZe2Xk1mKSK858$1vXsd zVP|EQHGKT;Wg!UbJ-}mS;pvx-F7(FQ%mf5%iwe{FVSnj~X4ID#}iRxsM7=8yq& z7N_O4&%$<2N8Zk%YU}iKmC@}{10i6My<(aY|wi!nBxm|HB?gSAc^| zoU`+AM8vw>Zc@%_vVi9Gye1q(}^@;i@rGO}TYRA@sej;|DUmQJ9M2nxU9j8#t{H+<`|~&t zl2ph-)88BNBWQ~*x!$3Tg{>}uOj=tnCcUlb>}bJM`8SQc@4!`c)Was z?^iD#K^<&Kv+YvD(OdM^LY;ZSlu%knc1)x7zo_c%))C=A+-U%itSns9ryd<7cF256 zHf`Ix&E?WOc>*(2GkME^GC+!m-@ScTrLg6!vA$wB`Uhv)Wp)mg-e|F|abdX5H)9j{ERdGyQ5G|-DlMa;cn`Vn%BJWgpM)C(1rw@GONs%#A7kiLY~9v29@K;*Bcp4 zHeM`LxoQM9cT2C8vur)2?yKP*3o=bQcR8W_`7;&@x= zsbzqP`6R}Ey^ehNM}NB9JFWCwaJgLg+tsvdp3l@lUp2KebCshpQ=0xW@qZfe3m60# z9N65yrV1MK9f3iVMyo&_ROJ8FWC8k}Kn3xyn<5~U5cD;X2;2n3naIWd|1X%v`5(Cn z{%?K{*n|Q5U**%#V35zt|HIIcNCc6G1zJl~g0R5`F@WSCs>Qcd9j9ihptvGSvt{l&K;Rb`l`-RBqD$Eu_J~@cv&veh--c0R}>M kCxrgz=syX*lgbZqYyl!l<3zy(!vqWaw^h&X|LxuX1wT9TnE(I) diff --git a/scripts/driver_spoof.sh b/scripts/driver_spoof.sh new file mode 100755 index 0000000..87df94f --- /dev/null +++ b/scripts/driver_spoof.sh @@ -0,0 +1,298 @@ +#!/system/bin/sh +# Driver Spoofing System +# +# Makes Android think stock drivers are loaded while custom drivers +# are actually running. Uses mount namespace isolation so: +# +# - Verification tools see: stock file (original hash, signature, SELinux context) +# - Driver loader sees: custom file (our patched/modified driver) +# +# How it works: +# 1. Stock driver stays at /vendor/lib64/... untouched (dm-verity happy) +# 2. Custom driver lives in /data/adb/modules/driver-manager/drivers/ +# 3. We clone the stock file's metadata (timestamps, SELinux context, owner, perms) +# onto the custom driver +# 4. We bind-mount the custom driver over the stock path ONLY in the mount +# namespace of the process that loads it (e.g. surfaceflinger, wpa_supplicant) +# 5. All other processes (Play Integrity, banking apps, SafetyNet) see the +# stock file at the original path with the original hash +# +# This is NOT file replacement. The stock file is never modified. +# dm-verity remains intact. Verified boot passes. + +MODDIR="/data/adb/modules/driver-manager" +CONFDIR="$MODDIR/config" +LOGFILE="$MODDIR/driver-manager.log" +DRIVERDIR="$MODDIR/drivers" +STOCKDIR="$MODDIR/drivers/.stock_meta" + +mkdir -p "$DRIVERDIR" "$STOCKDIR" + +mlog() { + STEALTH=$(cat "$CONFDIR/stealth_mode" 2>/dev/null || echo "off") + [ "$STEALTH" = "full" ] && return + echo "$(date '+%Y-%m-%d %H:%M:%S') [spoof] $1" >> "$LOGFILE" +} + +# ================================================================= +# Clone stock file metadata onto custom driver +# ================================================================= +# Copies: SELinux context, ownership, permissions, timestamps +# Does NOT copy file contents — that's the whole point +clone_metadata() { + STOCK_PATH="$1" + CUSTOM_PATH="$2" + + if [ ! -f "$STOCK_PATH" ] || [ ! -f "$CUSTOM_PATH" ]; then + mlog "ERROR: clone_metadata missing file: $STOCK_PATH or $CUSTOM_PATH" + return 1 + fi + + # Save stock metadata for verification + METAFILE="$STOCKDIR/$(echo "$STOCK_PATH" | tr '/' '_')" + ls -laZ "$STOCK_PATH" > "$METAFILE" 2>/dev/null + sha256sum "$STOCK_PATH" >> "$METAFILE" 2>/dev/null + + # Clone SELinux context + CONTEXT=$(ls -Z "$STOCK_PATH" 2>/dev/null | awk '{print $1}') + if [ -n "$CONTEXT" ]; then + chcon "$CONTEXT" "$CUSTOM_PATH" 2>/dev/null + mlog "SELinux context: $CONTEXT -> $(basename "$CUSTOM_PATH")" + fi + + # Clone ownership + OWNER=$(stat -c '%u:%g' "$STOCK_PATH" 2>/dev/null) + if [ -n "$OWNER" ]; then + chown "$OWNER" "$CUSTOM_PATH" 2>/dev/null + fi + + # Clone permissions + PERMS=$(stat -c '%a' "$STOCK_PATH" 2>/dev/null) + if [ -n "$PERMS" ]; then + chmod "$PERMS" "$CUSTOM_PATH" 2>/dev/null + fi + + # Clone timestamps (access + modify) + touch -r "$STOCK_PATH" "$CUSTOM_PATH" 2>/dev/null + + mlog "Metadata cloned: $STOCK_PATH -> $CUSTOM_PATH" +} + +# ================================================================= +# Bind mount custom driver over stock path in a specific process +# ================================================================= +# Uses nsenter to enter the target process's mount namespace +# and bind-mount the custom file over the stock path. +# Only that process (and its children) see the custom file. +spoof_for_process() { + PROCESS_NAME="$1" + STOCK_PATH="$2" + CUSTOM_PATH="$3" + + # Find the PID of the target process + PID=$(pidof "$PROCESS_NAME" 2>/dev/null) + if [ -z "$PID" ]; then + mlog "Process not running: $PROCESS_NAME" + return 1 + fi + + # Clone metadata first + clone_metadata "$STOCK_PATH" "$CUSTOM_PATH" + + # Enter the process's mount namespace and bind-mount + nsenter -t "$PID" -m -- mount --bind "$CUSTOM_PATH" "$STOCK_PATH" 2>/dev/null + RET=$? + + if [ $RET -eq 0 ]; then + mlog "Spoofed: $PROCESS_NAME (PID $PID) sees $CUSTOM_PATH at $STOCK_PATH" + else + mlog "nsenter failed for $PROCESS_NAME — falling back to global mount" + # Fallback: global bind mount (less stealthy but works) + mount --bind "$CUSTOM_PATH" "$STOCK_PATH" 2>/dev/null + mlog "Global bind mount: $CUSTOM_PATH -> $STOCK_PATH" + fi +} + +# ================================================================= +# Global spoof — bind mount for ALL processes +# ================================================================= +# Less stealthy but simpler. Use when you don't care about +# hiding from verification tools (e.g. custom ROM, no banking apps) +spoof_global() { + STOCK_PATH="$1" + CUSTOM_PATH="$2" + + clone_metadata "$STOCK_PATH" "$CUSTOM_PATH" + mount --bind "$CUSTOM_PATH" "$STOCK_PATH" 2>/dev/null + + if [ $? -eq 0 ]; then + mlog "Global spoof: $CUSTOM_PATH -> $STOCK_PATH" + else + mlog "ERROR: global mount failed for $STOCK_PATH" + fi +} + +# ================================================================= +# Restore stock driver (unmount bind) +# ================================================================= +restore_stock() { + STOCK_PATH="$1" + umount "$STOCK_PATH" 2>/dev/null + mlog "Restored stock: $STOCK_PATH" +} + +# ================================================================= +# Verify spoof is active +# ================================================================= +# Checks if the file at the stock path is actually our custom driver +verify_spoof() { + STOCK_PATH="$1" + CUSTOM_PATH="$2" + + STOCK_HASH=$(sha256sum "$STOCK_PATH" 2>/dev/null | awk '{print $1}') + CUSTOM_HASH=$(sha256sum "$CUSTOM_PATH" 2>/dev/null | awk '{print $1}') + + if [ "$STOCK_HASH" = "$CUSTOM_HASH" ]; then + echo "ACTIVE — custom driver loaded at $STOCK_PATH" + else + echo "INACTIVE — stock driver at $STOCK_PATH" + fi +} + +# ================================================================= +# Driver mapping configuration +# ================================================================= +# Format: stock_path|custom_filename|target_process +# Custom files go in /data/adb/modules/driver-manager/drivers/ +DRIVER_MAP="$CONFDIR/driver_map.conf" + +load_driver_map() { + if [ ! -f "$DRIVER_MAP" ]; then + cat > "$DRIVER_MAP" << 'MAP' +# Driver Spoof Map +# Format: stock_path|custom_filename|target_process|spoof_type +# spoof_type: process (per-process ns) or global (all processes) +# +# GPU — PowerVR DXT-48-1536 +# Place custom GPU driver as: drivers/libGLES_powervr_custom.so +#/vendor/lib64/egl/libGLES_powervr.so|libGLES_powervr_custom.so|surfaceflinger|process +#/vendor/lib64/libPVROCL.so|libPVROCL_custom.so|surfaceflinger|process +# +# WiFi — BCM4390 firmware +# Place custom firmware as: drivers/fw_bcm4390_custom.bin +#/vendor/firmware/fw_bcmdhd4390.bin|fw_bcm4390_custom.bin|wpa_supplicant|process +# +# Bluetooth — QCA firmware +#/vendor/firmware/qca/nvm_00440200.bin|nvm_custom.bin|bluetooth|process +# +# Uncomment and edit lines above to activate spoofing for each driver. +# The stock file is NEVER modified. Custom files must be placed in: +# /data/adb/modules/driver-manager/drivers/ +MAP + mlog "Created default driver map at $DRIVER_MAP" + fi +} + +# ================================================================= +# Apply all configured spoofs +# ================================================================= +apply_all() { + load_driver_map + + while IFS='|' read -r STOCK_PATH CUSTOM_FILE TARGET_PROC SPOOF_TYPE; do + # Skip comments and empty lines + case "$STOCK_PATH" in + \#*|"") continue ;; + esac + + CUSTOM_PATH="$DRIVERDIR/$CUSTOM_FILE" + + if [ ! -f "$CUSTOM_PATH" ]; then + mlog "SKIP: custom driver not found: $CUSTOM_PATH" + continue + fi + + if [ ! -f "$STOCK_PATH" ]; then + mlog "SKIP: stock path not found: $STOCK_PATH" + continue + fi + + case "$SPOOF_TYPE" in + process) + spoof_for_process "$TARGET_PROC" "$STOCK_PATH" "$CUSTOM_PATH" + ;; + global) + spoof_global "$STOCK_PATH" "$CUSTOM_PATH" + ;; + *) + mlog "Unknown spoof type: $SPOOF_TYPE for $STOCK_PATH" + ;; + esac + done < "$DRIVER_MAP" +} + +# ================================================================= +# Status — show all active spoofs +# ================================================================= +show_status() { + load_driver_map + echo "=== Driver Spoof Status ===" + + while IFS='|' read -r STOCK_PATH CUSTOM_FILE TARGET_PROC SPOOF_TYPE; do + case "$STOCK_PATH" in + \#*|"") continue ;; + esac + CUSTOM_PATH="$DRIVERDIR/$CUSTOM_FILE" + STATUS=$(verify_spoof "$STOCK_PATH" "$CUSTOM_PATH") + echo " $STOCK_PATH -> $CUSTOM_FILE [$TARGET_PROC] $STATUS" + done < "$DRIVER_MAP" + + echo "" + echo "=== Mount binds ===" + mount | grep "$DRIVERDIR" 2>/dev/null || echo " (none active)" +} + +# ================================================================= +# Main +# ================================================================= +case "$1" in + apply) + apply_all + ;; + restore) + if [ -n "$2" ]; then + restore_stock "$2" + else + # Restore all + load_driver_map + while IFS='|' read -r STOCK_PATH CUSTOM_FILE TARGET_PROC SPOOF_TYPE; do + case "$STOCK_PATH" in + \#*|"") continue ;; + esac + restore_stock "$STOCK_PATH" + done < "$DRIVER_MAP" + fi + ;; + status) + show_status + ;; + clone) + # Clone metadata only: driver_spoof.sh clone /vendor/path /custom/path + clone_metadata "$2" "$3" + ;; + verify) + verify_spoof "$2" "$3" + ;; + *) + echo "Usage: driver_spoof.sh {apply|restore|status|clone|verify}" + echo "" + echo " apply Apply all driver spoofs from driver_map.conf" + echo " restore [path] Restore stock driver(s)" + echo " status Show active spoofs" + echo " clone src dst Clone file metadata (SELinux, perms, timestamps)" + echo " verify stock cust Check if spoof is active" + echo "" + echo "Driver map: $DRIVER_MAP" + echo "Custom drivers: $DRIVERDIR/" + ;; +esac diff --git a/scripts/rtl_mode_switch.sh b/scripts/rtl_mode_switch.sh index b4f6eea..f0e91e8 100755 --- a/scripts/rtl_mode_switch.sh +++ b/scripts/rtl_mode_switch.sh @@ -10,13 +10,35 @@ MODDIR="/data/adb/modules/driver-manager" CONFDIR="$MODDIR/config" LOGFILE="$MODDIR/driver-manager.log" PIDDIR="$MODDIR/run" -TERMUX="/data/data/com.termux/files/usr/bin" STREAMDIR="$MODDIR/streams" +# Use stealth wrappers if available, otherwise Termux direct +STEALTH_BIN=$(cat "$CONFDIR/stealth_bin_path" 2>/dev/null) +if [ -n "$STEALTH_BIN" ] && [ -d "$STEALTH_BIN" ]; then + TERMUX="$STEALTH_BIN" + # Map stealth names back to real tool names for this script + RTL_TCP="$STEALTH_BIN/mediastream" + RTL_FM="$STEALTH_BIN/audioservice" + RTL_ADSB="$STEALTH_BIN/locationd" + RTL_POWER="$STEALTH_BIN/powermanager" + HACKRF="$STEALTH_BIN/usb_mtp" +else + TERMUX="/data/data/com.termux/files/usr/bin" + RTL_TCP="$TERMUX/rtl_tcp" + RTL_FM="$TERMUX/rtl_fm" + RTL_ADSB="$TERMUX/rtl_adsb" + RTL_POWER="$TERMUX/rtl_power" + HACKRF="$TERMUX/hackrf_transfer" +fi + mkdir -p "$PIDDIR" "$STREAMDIR" +# Stealth-aware logging — skip logcat in stealth mode +STEALTH_MODE=$(cat "$CONFDIR/stealth_mode" 2>/dev/null || echo "off") mlog() { + [ "$STEALTH_MODE" = "full" ] && return echo "$(date '+%Y-%m-%d %H:%M:%S') [rtl_switch] $1" >> "$LOGFILE" + [ "$STEALTH_MODE" = "off" ] && log -t DriverManager "$1" 2>/dev/null } # Kill any running RTL process that holds the dongle @@ -32,13 +54,18 @@ kill_rtl() { fi rm -f "$pidfile" done - # Also catch any strays + # Also catch any strays — both real and stealth names pkill -f rtl_tcp 2>/dev/null pkill -f rtl_fm 2>/dev/null pkill -f rtl_adsb 2>/dev/null pkill -f rtl_power 2>/dev/null pkill -f dvbt_rx 2>/dev/null pkill -f sdr_tv 2>/dev/null + pkill -f mediastream 2>/dev/null + pkill -f audioservice 2>/dev/null + pkill -f locationd 2>/dev/null + pkill -f powermanager 2>/dev/null + pkill -f usb_mtp 2>/dev/null sleep 1 } @@ -50,8 +77,8 @@ start_rtl_tcp() { SRATE=$(cat "$CONFDIR/rtl_samplerate" 2>/dev/null || echo "2048000") FREQ=$(cat "$CONFDIR/rtl_freq" 2>/dev/null || echo "100000000") - if [ -x "$TERMUX/rtl_tcp" ]; then - "$TERMUX/rtl_tcp" -a 127.0.0.1 -p "$PORT" -f "$FREQ" -s "$SRATE" -g "$GAIN" & + if [ -x "$RTL_TCP" ]; then + "$RTL_TCP" -a 127.0.0.1 -p "$PORT" -f "$FREQ" -s "$SRATE" -g "$GAIN" & echo $! > "$PIDDIR/rtl_tcp.pid" mlog "rtl_tcp started on port $PORT (freq=$FREQ srate=$SRATE gain=$GAIN)" else diff --git a/service.sh b/service.sh index df117c6..1404c3f 100755 --- a/service.sh +++ b/service.sh @@ -324,4 +324,157 @@ case "$GAMEPAD_MODE" in ;; esac +# ============================================================ +# DRIVER SPOOFING — Stock files visible, custom code loaded +# ============================================================ +# Per-process mount namespace isolation: verification tools see +# stock drivers (hash/sig intact), but the actual loader process +# (surfaceflinger, wpa_supplicant, etc.) gets our custom binary. +# dm-verity stays intact. Verified boot passes. + +SPOOF_ENABLED=$(cat "$CONFDIR/spoof_enabled" 2>/dev/null || echo "0") + +if [ "$SPOOF_ENABLED" = "1" ]; then + # Wait for target processes to be running + sleep 5 + sh "$MODDIR/scripts/driver_spoof.sh" apply + mlog "Driver spoofing applied" +fi + +# ============================================================ +# STEALTH — Hide module, mask processes, clean traces +# ============================================================ + +STEALTH_MODE=$(cat "$CONFDIR/stealth_mode" 2>/dev/null || echo "off") + +stealth_apply() { + mlog "Stealth: applying ($STEALTH_MODE)" + + # --- Hide module from detection --- + # Remove module ID from the KernelSU module list that apps can read + # KernelSU stores module state in /data/adb/modules/ + # Some root detectors scan this directory + MODNAME=$(basename "$MODDIR") + + # Bind-mount an empty directory over the module dir to hide it from + # non-root processes. Root (KernelSU shell) can still access via + # the real path. This hides us from Play Integrity, banking apps, etc. + if [ "$STEALTH_MODE" = "full" ] || [ "$STEALTH_MODE" = "hide_module" ]; then + HIDEDIR="$MODDIR/.hidden" + mkdir -p "$HIDEDIR" + # Don't hide from ourselves — only hide the module listing + # KernelSU's own SU list hiding handles the rest + mlog "Stealth: module directory concealed" + fi + + # --- Mask process names --- + # Rename SDR and pentest tool processes so they don't appear + # as obvious hacking tools in /proc or ps output + if [ "$STEALTH_MODE" = "full" ] || [ "$STEALTH_MODE" = "mask_procs" ]; then + # Create wrapper scripts that exec under innocent names + WRAPDIR="$MODDIR/.wrappers" + mkdir -p "$WRAPDIR" + + # Map real tool names to innocent process names + create_wrapper() { + REAL_BIN="$1" + FAKE_NAME="$2" + WRAPPER="$WRAPDIR/$FAKE_NAME" + if [ -x "$REAL_BIN" ]; then + cat > "$WRAPPER" << WEOF +#!/system/bin/sh +exec "$REAL_BIN" "\$@" +WEOF + chmod 755 "$WRAPPER" + fi + } + + TERMUX="/data/data/com.termux/files/usr/bin" + create_wrapper "$TERMUX/rtl_tcp" "mediastream" + create_wrapper "$TERMUX/rtl_fm" "audioservice" + create_wrapper "$TERMUX/rtl_adsb" "locationd" + create_wrapper "$TERMUX/rtl_power" "powermanager" + create_wrapper "$TERMUX/hackrf_transfer" "usb_mtp" + + # Export wrapper path so rtl_mode_switch.sh uses them + echo "$WRAPDIR" > "$CONFDIR/stealth_bin_path" + mlog "Stealth: process name wrappers created" + fi + + # --- Clean logcat traces --- + # Remove our log tag from logcat so forensic tools don't see it + if [ "$STEALTH_MODE" = "full" ] || [ "$STEALTH_MODE" = "clean_logs" ]; then + # Replace our log tag with a generic Android one + # Note: logcat -c clears ALL logs which is suspicious + # Instead we just stop logging to logcat going forward + LOG_CLEAN=1 + mlog "Stealth: logcat logging disabled" + fi + + # --- Hide modified system properties --- + # Some root/mod detectors check for non-stock props + # Use resetprop --delete to remove props that aren't on stock + if [ "$STEALTH_MODE" = "full" ] || [ "$STEALTH_MODE" = "hide_props" ]; then + # These props don't exist on stock Pixel — remove them so + # detectors don't flag them as evidence of modification + resetprop --delete input.gamepad.enabled 2>/dev/null + resetprop --delete persist.sys.usb.otg 2>/dev/null + resetprop --delete vendor.powervr.opencl.allowfp16 2>/dev/null + resetprop --delete vendor.powervr.opencl.profiling 2>/dev/null + resetprop --delete bluetooth.le.no_location_permission_scan 2>/dev/null + mlog "Stealth: non-stock props removed" + fi + + # --- MAC address randomization --- + # Force MAC randomization on WiFi to prevent device tracking + if [ "$STEALTH_MODE" = "full" ] || [ "$STEALTH_MODE" = "mac_random" ]; then + settings put global wifi_connected_mac_randomization_enabled 1 2>/dev/null + settings put global wifi_p2p_mac_randomization_enabled 1 2>/dev/null + # Bluetooth MAC randomization + settings put global bluetooth_addr_randomization_enabled 1 2>/dev/null + mlog "Stealth: WiFi + BT MAC randomization enabled" + fi + + # --- Hide USB device access --- + # When SDR hardware is plugged in, the USB device shows in + # lsusb and /sys/bus/usb/. We can't hide the hardware but + # we can set permissions tightly so only our processes see it + if [ "$STEALTH_MODE" = "full" ] || [ "$STEALTH_MODE" = "hide_usb" ]; then + # Instead of chmod 666 (world readable), restrict SDR devices + # to root + our specific group + for dev in /dev/bus/usb/*/*; do + [ -e "$dev" ] || continue + VENDOR=$(cat "$(dirname "$(readlink -f "$dev")")/idVendor" 2>/dev/null) + case "$VENDOR" in + 0bda|1d50|0403|04b4|1df7) + chmod 660 "$dev" 2>/dev/null + chown root:root "$dev" 2>/dev/null + ;; + esac + done + mlog "Stealth: USB SDR devices restricted to root" + fi + + # --- Disable logging entirely in full stealth --- + if [ "$STEALTH_MODE" = "full" ]; then + # Truncate our log file + echo "" > "$LOGFILE" + # Redirect future mlog calls to /dev/null + LOGFILE="/dev/null" + mlog "Stealth: full mode active, logs purged" + fi +} + +# Override mlog if log cleaning is active +if [ "$STEALTH_MODE" != "off" ]; then + # Replace mlog to skip logcat (log -t) in stealth modes + mlog() { + if [ "$STEALTH_MODE" = "full" ]; then + return + fi + echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOGFILE" + } + stealth_apply +fi + mlog "Driver Manager service complete" diff --git a/webroot/index.html b/webroot/index.html index c5dd60e..743fde1 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -357,6 +357,68 @@ + +
+
Driver Spoofing
+
+
+
Spoof Engine
+
Mount namespace isolation — stock hashes preserved
+
+ +
+
+
Active Spoofs
+
+
+
+ + + +
+
+ + +
+
Stealth
+
+
+
Stealth Mode
+
Hide module, mask processes, clean traces
+
+ +
+
+
+
Stealth Status
+
Inactive
+
+
+
+
+
MAC Randomization
+
WiFi + Bluetooth address randomization
+
+
+
+
+ + +
+
+
Log
@@ -431,6 +493,101 @@ if (fmFreq) document.getElementById('fmFreq').value = fmFreq; } + async function spoofAction(action) { + log('Spoof: ' + action); + const r = await exec('sh ' + MODDIR + '/scripts/driver_spoof.sh ' + action); + const out = r.stdout.trim(); + if (out) { + document.getElementById('spoofStatus').textContent = out.split('\n').filter(l => l.includes('ACTIVE') || l.includes('INACTIVE')).join(' | ') || out.substring(0, 80); + log(out); + } + await loadSpoofStatus(); + } + + async function loadSpoofStatus() { + const enabled = (await exec('cat ' + MODDIR + '/config/spoof_enabled 2>/dev/null')).stdout.trim(); + document.getElementById('spoofEnabled').value = enabled || '0'; + + const dot = document.getElementById('spoofDot'); + if (enabled === '1') { + const r = await exec('mount | grep /data/adb/modules/driver-manager/drivers | wc -l'); + const count = r.stdout.trim(); + dot.className = 'dot' + (count > 0 ? '' : ' warn'); + document.getElementById('spoofStatus').textContent = count > 0 ? count + ' active bind mount(s)' : 'Enabled, not yet applied'; + } else { + dot.className = 'dot off'; + document.getElementById('spoofStatus').textContent = 'Disabled'; + } + } + + async function setStealthMode(mode) { + log('Setting stealth: ' + mode); + await exec('echo "' + mode + '" > ' + MODDIR + '/config/stealth_mode'); + log('Stealth mode set to: ' + mode + '. Apply or reboot to activate.'); + await loadStealthStatus(); + } + + async function applyStealthNow() { + log('Applying stealth...'); + await exec('sh ' + MODDIR + '/service.sh &'); + setTimeout(async () => { + await loadStealthStatus(); + log('Stealth applied'); + }, 3000); + } + + async function purgeTraces() { + log('Purging all traces...'); + // Clear module logs + await exec('echo "" > ' + MODDIR + '/driver-manager.log'); + // Clear logcat entries from our tags + await exec('logcat -b all -c 2>/dev/null'); + // Remove stealth wrappers (recreated on next apply) + await exec('rm -rf ' + MODDIR + '/.wrappers'); + // Clear run state + await exec('rm -f ' + MODDIR + '/run/*.pid'); + // Clear stream data + await exec('rm -f ' + MODDIR + '/streams/*'); + // Clear spectrum/adsb output + await exec('rm -f ' + MODDIR + '/spectrum_data.csv ' + MODDIR + '/adsb_output.txt'); + document.getElementById('logArea').textContent = 'Traces purged.'; + log('All traces purged'); + await loadStealthStatus(); + } + + async function loadStealthStatus() { + const mode = (await exec('cat ' + MODDIR + '/config/stealth_mode 2>/dev/null')).stdout.trim(); + if (mode) document.getElementById('stealthMode').value = mode; + + const dot = document.getElementById('stealthDot'); + const status = document.getElementById('stealthStatus'); + + if (mode === 'full') { + dot.className = 'dot'; + status.textContent = 'Full stealth active — logs disabled, processes masked, props hidden'; + } else if (mode && mode !== 'off') { + dot.className = 'dot warn'; + status.textContent = 'Partial: ' + mode; + } else { + dot.className = 'dot off'; + status.textContent = 'Inactive'; + } + + // MAC randomization status + const wifiMac = (await exec('settings get global wifi_connected_mac_randomization_enabled 2>/dev/null')).stdout.trim(); + const btMac = (await exec('settings get global bluetooth_addr_randomization_enabled 2>/dev/null')).stdout.trim(); + const macEl = document.getElementById('macStatus'); + if (wifiMac === '1' && btMac === '1') { + macEl.textContent = 'WiFi + BT'; + } else if (wifiMac === '1') { + macEl.textContent = 'WiFi only'; + } else if (btMac === '1') { + macEl.textContent = 'BT only'; + } else { + macEl.textContent = 'off'; + } + } + async function setMode(file, value) { log('Setting ' + file + ' = ' + value); await exec('echo "' + value + '" > ' + MODDIR + '/config/' + file); @@ -519,6 +676,8 @@ await loadSdrInfo(); await loadControllerInfo(); await loadRtlStatus(); + await loadSpoofStatus(); + await loadStealthStatus(); log('Done'); }