From 75bce0e4b4bfa5352dccbbca781a33aeee680432 Mon Sep 17 00:00:00 2001 From: ors1331 Date: Wed, 4 Dec 2024 09:56:10 +0000 Subject: [PATCH 1/8] Enhanced GUI with new features: - Added CodeShare browser - Added favorites system - Improved process management - Added themes and styling - Added Oliver Stankiewicz attribution --- .DS_Store | Bin 0 -> 6148 bytes README.md | 25 - image.png | Bin 38096 -> 0 bytes main.py | 406 --------- requirements.txt | 11 +- .../android_helper.cpython-311.pyc | Bin 0 -> 7719 bytes .../history_manager.cpython-311.pyc | Bin 0 -> 3422 bytes .../process_monitor.cpython-311.pyc | Bin 0 -> 3373 bytes .../script_history.cpython-311.pyc | Bin 0 -> 6382 bytes .../script_templates.cpython-311.pyc | Bin 0 -> 3634 bytes src/core/android_helper.py | 175 ++++ src/core/history_manager.py | 38 + src/core/process_monitor.py | 53 ++ src/core/script_history.py | 76 ++ src/core/script_manager.py | 74 ++ src/core/script_templates.py | 94 +++ src/gui/__init__.py | 1 + src/gui/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 162 bytes .../__pycache__/main_window.cpython-311.pyc | Bin 0 -> 45587 bytes src/gui/main_window.py | 785 ++++++++++++++++++ src/gui/widgets/__init__.py | 1 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 170 bytes .../__pycache__/app_launcher.cpython-311.pyc | Bin 0 -> 21710 bytes .../codeshare_browser.cpython-311.pyc | Bin 0 -> 33294 bytes .../__pycache__/device_panel.cpython-311.pyc | Bin 0 -> 3488 bytes .../device_selector.cpython-311.pyc | Bin 0 -> 16942 bytes .../__pycache__/history_page.cpython-311.pyc | Bin 0 -> 9257 bytes .../injection_panel.cpython-311.pyc | Bin 0 -> 14770 bytes .../__pycache__/output_panel.cpython-311.pyc | Bin 0 -> 1908 bytes .../process_monitor.cpython-311.pyc | Bin 0 -> 21326 bytes .../__pycache__/process_panel.cpython-311.pyc | Bin 0 -> 3806 bytes .../__pycache__/script_editor.cpython-311.pyc | Bin 0 -> 1997 bytes src/gui/widgets/app_launcher.py | 306 +++++++ src/gui/widgets/codeshare_browser.py | 539 ++++++++++++ src/gui/widgets/data_visualizer.py | 44 + src/gui/widgets/device_panel.py | 46 + src/gui/widgets/device_selector.py | 307 +++++++ src/gui/widgets/history_page.py | 148 ++++ src/gui/widgets/injection_panel.py | 220 +++++ src/gui/widgets/output_panel.py | 21 + src/gui/widgets/process_manager.py | 307 +++++++ src/gui/widgets/process_monitor.py | 335 ++++++++ src/gui/widgets/process_panel.py | 59 ++ src/gui/widgets/script_editor.py | 25 + src/main.py | 70 ++ src/utils/__pycache__/themes.cpython-311.pyc | Bin 0 -> 6478 bytes src/utils/themes.py | 172 ++++ 47 files changed, 3903 insertions(+), 435 deletions(-) create mode 100644 .DS_Store delete mode 100644 README.md delete mode 100644 image.png delete mode 100644 main.py create mode 100644 src/core/__pycache__/android_helper.cpython-311.pyc create mode 100644 src/core/__pycache__/history_manager.cpython-311.pyc create mode 100644 src/core/__pycache__/process_monitor.cpython-311.pyc create mode 100644 src/core/__pycache__/script_history.cpython-311.pyc create mode 100644 src/core/__pycache__/script_templates.cpython-311.pyc create mode 100644 src/core/android_helper.py create mode 100644 src/core/history_manager.py create mode 100644 src/core/process_monitor.py create mode 100644 src/core/script_history.py create mode 100644 src/core/script_manager.py create mode 100644 src/core/script_templates.py create mode 100644 src/gui/__init__.py create mode 100644 src/gui/__pycache__/__init__.cpython-311.pyc create mode 100644 src/gui/__pycache__/main_window.cpython-311.pyc create mode 100644 src/gui/main_window.py create mode 100644 src/gui/widgets/__init__.py create mode 100644 src/gui/widgets/__pycache__/__init__.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/app_launcher.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/codeshare_browser.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/device_panel.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/device_selector.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/history_page.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/injection_panel.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/output_panel.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/process_monitor.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/process_panel.cpython-311.pyc create mode 100644 src/gui/widgets/__pycache__/script_editor.cpython-311.pyc create mode 100644 src/gui/widgets/app_launcher.py create mode 100644 src/gui/widgets/codeshare_browser.py create mode 100644 src/gui/widgets/data_visualizer.py create mode 100644 src/gui/widgets/device_panel.py create mode 100644 src/gui/widgets/device_selector.py create mode 100644 src/gui/widgets/history_page.py create mode 100644 src/gui/widgets/injection_panel.py create mode 100644 src/gui/widgets/output_panel.py create mode 100644 src/gui/widgets/process_manager.py create mode 100644 src/gui/widgets/process_monitor.py create mode 100644 src/gui/widgets/process_panel.py create mode 100644 src/gui/widgets/script_editor.py create mode 100644 src/main.py create mode 100644 src/utils/__pycache__/themes.cpython-311.pyc create mode 100644 src/utils/themes.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5172429f264de2441865cb4700216d4256da9242 GIT binary patch literal 6148 zcmeH~J!%6%427R!7lt%jx}3%b$PET#pTHLgIFQEJ;E>dF^gR7ES*H$5cmnB-G%I%Z zD|S`@Z2$T80!#olbXV*=%*>dt@PRwdU#I)^a=X5>;#J@&VrHyNnC;iLL0pQvfVyTmjO&;ssLc!1UOG})p;=82 zR;?Ceh}WZ?+UmMqI#RP8R>OzYoz15hnq@nzF`-!xQ4j$Um=RcIKKc27r2jVm&svm< zfC&6E0=7P!4tu^-ovjbA=k?dB`g+i*aXG_}p8zI)6mRKa+;6_1_R^8c3Qa!(fk8n8 H{*=HsM+*^= literal 0 HcmV?d00001 diff --git a/README.md b/README.md deleted file mode 100644 index 5729f98..0000000 --- a/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Frida GUI Injector - -A graphical user interface for Frida script injection and app launching on Android devices, with special support for Nox emulator. - -![image](https://github.com/gru122121/FridaGUI/blob/main/image.png?raw=true) - -## Features - -- Device scanning and selection -- Application listing with package names -- Automatic Frida server setup and management -- Script injection during app launch -- Support for Nox emulator -- Real-time output logging - -## Prerequisites - -- Python 3.7+ -- ADB (Android Debug Bridge) -- Rooted Android device or emulator -- USB debugging enabled on target device - -## Installation - -1. Clone the repository: diff --git a/image.png b/image.png deleted file mode 100644 index 29321e822af49acea6709cfea5309e0604941586..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38096 zcmZr&bzGC*_eVuRDWy|FLb@9PMY>~*aHN0|f^>%nC@CO~AV}y2qdSJAv~+iOH-q0Z z6+YiTe!kdi+uey zc)Q`CA^Qvs-bJ~BhDL)X|NNy7mqWFA9{+mcJ2kK_sa8S4hpttem6f8lxE9Y0^A zIU_^=b$GO-zmcn%#jSeDcal27g%3=gR2c-M#1t9iv4`~5j`XZ2y*!1BrL%oh@vT$j zo?noob(4Osm8=+9nW2}QDTnt|N#me@uU~UuVkuc9ySrv-L6fxO2i$x0N4EGt_L>Lm z%&}YinEE`y@Wcb*es&l=b#Yj>6-9$~15*<1=6zo@^jMnhHrA<=RWRakrA1Ehh5 z^7QBBw(7Zu&#LNqs|3HtOaMvT_mI2j*Q4st{QCO(Qjyd?n-ZtLea@LK<`EJU!>SMd z>mrHe7lB-F$@AXs{tDBJH^#bNFc332CFP<*;xg_xNXq)LKibuxFm!R`vBhstSO-_o zb)yt|?dd>X8Z59*?auWSho!gFm@B%>yC%9 zlDV*p5ybgMc4`gp@zz?m*Bt7Fg*Yqe_TfH1%T7HbnL6omIV8rW`d>8FLfGD(;n_Ljq@?>Yi5qe-?T<$eYoYcT$JjaF>+Uu$8Y{CstFLm|5;XRlgTptx&UacYipYoJxff7-j@n3UeAj6| zjEiYJ;Ll^FVkyM~_<9Fs{e9adJmPXf;;@C~HNQ@|Ct8Wx@#>KGWm8{KgxFy~DzMe3 zYd)8TV!ITV>lPO?m#5RF!3RVnJzPj?Gt zslEEahO|Csha0u4iFvR@J+l3&`5UHx^{~zWKfcr4=QzkmXt*TA5b<40t3;S|{=8lf zJmI~|%9)aaT(|H!LYxfv92#EiT3n1yoniQFP@i*lKXEx-JSv2r2B{)1Y%h|gs*E7~ z77VYw@i2~XS06eK(6HX4{j2T-_`@s^r=v|spGK^1c2NoZbmBYgVwdxxITfjoI8VLk z5)vmvsh5b_{0umLfj#on=|LK!wCz+admI)Z=QnF0=gd4r zJiR#xyf8X$EkH2fggiX}ICT_AdIFW_z)VaiexuxN&g&!Jv&J&Q@E|xW&ZM`^TA)a1 z0Y7`#=ddU>7ye%1C{wL+`*I_Cw_@Vs060L$l~=u-1HXv$+^Gw-w+=2E+}`bD_1O{-~-+wFv|O-Ad(3l72_ zO^C=o7?}e$aRXRjb_0|FCe4;t^IZ2MoQK=xNfw|=?vXDqSRo8J6SZFYdIc1~`U+a!rye*?8N zS`XY`8_GKyVAM~<_sD~FbiCWCY&jn@2+?#NVTt?o2ywiBNll3#_~XfsStOWTop_1m zCh;w@e+uMHFf0Hui@Rhi!o8e1fFjP<1TWew9wIp}TD!fD9em~x7bKSr4&7f;e6~u! z1}DzdxW_eBE>o+umq>o67{t+dMquummR`nc+HhVLEaPsTN5{!-W!wA2{*4g&EyQX_ zj`smfhxFw?FX*9U1akysWwdXO>qJUK+&c~-t&TWz12&Ag_ipxb$rw?*O z;@CfWZ>xrJ=tei80)k+CHp(d)6uIJ@_CCszQ#>{+yj2Z7cbcAf zcGZ(0_G6AFtB(9PJIiJfRkq0KP5lXd3D>1sP^+}I}!INr*U6M7+vO?xn zX|&`3>XfWTa^-^AX}1UUZIvI&=fVtpkO#J2Yx>!W8K}2B#Kr0S4dqQc76=30+jucC z%4dIL9ZkLBBk|TAOr|jHM(?)*j=?*gEZ9|F|M1IMI4D_Z(M(k z4+NGV4|nyu4x7s?;nzWsh6@Lvk&klc=5^C=5aq2a`Xr6W0VZDy&EEdU!VS!?FrhpD z)Eroy4_Lj=t`X~cIZ5zaY)K<-zoMcdQJ2l+Pc(K$=$qpe4$jVlVHtaSdn5*~GxPKF zy7=h00BYf^arE{U-?jPGv<&TdM~U8y33OfBD4X(5Mfbb)X!~%?cB;a`V%y`bx3{;w z{q8C(ul8ut14AZY^C6ud@M)`L$UszBSod-6Z;-zQ_B~p)`$EFP!i@xXy#njbwo;Rm z2@UT|xUOWz0eaws`E>d#X7ZOWHQV)ArwpVQ&%rnjX@oaZ&?;BIs(f)ABp~erb&H>^ z0}id4!3}f$iJ!;OW|-F_>o!R+**%{7qzu0B(X&1VD#}M(0wH2&>qVxZ$3b-u30x0H zEM&8T%WpyWQ}H}#?*mdgJZ6(0S|pGJcP=$dNn8BgeYt?7riTEp+Z+@*rFV!||xtniR=~c1>-q(qiOoOGriL{mbL|eGx*zr~TZH!VidP97%y?}<7W=x~< z#F6(a5!(z0NiKDtN7(^7Vp>N?riZ1|ivwm;w1CRx3XPt$R~?qMRV1?S$ZF!cZd;|e zMC%9Cu~e>(cJ%k>TKU&oxP9y^+h3DzGEotl`%XbnG^OGxQ-x!8T|tzQMhOTm%$^te!fLa>Fo(qf^lOvF&t=N% zXd%vUQ}%MVofC1kS*0Bg=OJWInEaTYX>YTJ$W=FG-^^98om)Vk64p;h{=t}o1EtdTcitA|6%Cy>N$pMCND z;gGcLnIP&Y&Il5(7{+4X)^!R$mr1v!NZE&Orz_cBEh*loE5PUZAc^xdqI4Lxv|@8@ zW^EL^dJw12g?O7GWXI!Fr{)6{Yz-`QQrNjp=N-^s?xxM%zD)vF_?ZqBQk!mIG`z1P z4Q3cIfilW5Ox!2?+^Ci(W=Y8(#+%>Dm=v*w|F+jWB`TLye z?6|w`_5Nt94RUX5_aO*Q`IxPKDVUM6v3pX&!3~ZoP_|(unNR-m*}?Z`HP7!NOLh30 zlX26VjJbED2hQfYZ!ZXADN+#~mh4Rl{aEQndwo}za}C@)kad1A2MgMh>z8tGbDf=W zjg1Tj9ry(kFKBp)*nQ)71jT>S+SdXPJRjG>&kl*3zr&6k*B&5uAcBfH%(a#1L!v;& zijB#(>AQ;*ba;L%nApouJIjwtT%r~MMIf;fU3n4YXCIG256~>(!rQr0M2b#a_3^M& z4OX}cp_{F^?QS=T-u!6xEeYF)dPmZFPj-Jj8c>L_Gpe!Nm`f8=|4_A^sfg9Q;-T-w z;AFvdRApVnqNE;3&_J-oq{}}&0>#_E=fsbeX;>T#zh@$&p}}sv(AHL;Qm|WI4APGU zmB9IoQrM&QN)PiO(E}`2d^u_rjDak72smr+rCLiG-M*;{Zq-$+wX`=_OPz3MxFFO^ zrE4fnHf)e*XaC8R*2l^$))M_y;v|?QDr%kyP}( zOv%^DDlPA~zpKCaNay`vk6+*jQfm9Ewyb9Z2Zt!hSubDdf#RzZW8K`s;0soc?7L3} z-f&k)`R!qozFBV0*_q*-Z@N_YaTZWVj37cH% z^U|N$l`CYV2;T5EQ7CjY-E(*hqERFGRmqY|o%^f~lyeY)(`Oma&M^=G&qJAA{f`uF`0}%vVux zvWyoU=~qX0(-wg|{37ad2O-5-ykfisX=#axi7Bt_bQ5!6hC@ugnzS|dfTh_DSrH(x1?J{Cjcx?0XRWLPa)!^gARPE z88Yw)I~rEv$(%*MInmnMYW(HdGCTU`I^g3p^E3b`+>r-E1Hr18l;|k50{|Q2Z3^Rn z7Z`kmFKM_G0NuEstt2A++(-%h1V9SaZ{09S&CZmjCu=-Rr!bQweuDzvVWZ0cVgB$$ zNq%Y(pEOB2#p`gS;$Zl$ACi81y{J1}h3G9trKLFa;WL{1lHiS{yHN)0PXR!PPOpUf zq0amdE_hL~gH6DJjvH9lwFV`j_E=79F|dAekgE-|H0zMJa$6`JrQFj_V4w5^0rhMKOLI443q&zCZHbJAh(P9^1uN`dd5rtuSElAE7XR=0v) z6}LVSWlsLPYOD4Pzz}QN7oH~zaR97f-`v(!=Fuc#96OJmJ|AU3F;|>4;r-$ zUIs_C7psFFE+X`Yv}_Hl`17lZDqDs0AQf*PX@zVoDi8-Ajoijtu+#&o*~oZk2J3T4 z6V&74Z&$9S1wHgWPIJV}l3>}RPV_I@CBR#YqZ_uE(-BQpif#x>l{;HSfEM0UT>2<^ zy#1 zY~#Di0e25Bw!7h%yr>|q!JTFKPOBG3U0R;vpzYgx^*$jtViqXJ-J8p8VHWAQy?SYH zTZ3jl^>y=+9g#boHSFq@jC{Q1IDl)JU~{kHHz$%oqnZ+*m;5}XT0CaOP-x#BCqI~dD`u^PsLsznE~as+5VH8scG8&pniYKMl2@2vPc5jaENd3|PQ(g?sd`Q>>*uM2|VZ8v3NKfTk~Vb10H z_|RUocw9Bezp`!h!|09x$a`>a1dlQI(+@6~pcmgfZ9bjfGw@0U=S9KsZ_~1d#zhU0 z$V^@pr^Edud*Y%f?`9Rf!glO)=HYw(f~mdKRVxNgyzTC?D(!kr^Wl7!?14zLi0Y>j zQQlIf4AKPTffr#Z4Y7k>Td{WslE;j~Vn|=+8F+RS2A7eKz~fOsyEL~EXNq-N#tIr1cf_MRF}UBGCtF+ zBkA+8eftqFh<${QcM1+S%8W^BlUZmdSkO?iw`~SB3wTL2w>m;Yj=FF#ly-r` zq^(6-q8tj1hz5!d=xFVz18S5rSNm1{%wLl^JYuLF-$g2c6a_jv8p_m5S!zP%U;wzz z(EEWe(_Df>V&&IuvH@^YGW?TB-!8RE%}1O7H_qZfY;NrWJ{RTZAWFtWApY` zc%T#LvN!6B({su9oyj4h4f4u^KE1f2RXFC>B{CotgI@)RSJ8-dVQFLBc1`CA_XNS8yFw$16)eF-S~Tk} z8|{ym-rz1whQEG=w+8apYQ{qTG;Qe(iFlHn;*ego0ehL^7(qnXGNUncqpyO-?K^o5 zq={>L0OsG3_Ng6Ry9cycn96~30<*Q3{S2w^Gi%>}^vr^JS7|V^UtLSs?NL{M034r; zUIjD-5}z=)*3!cSrO#OsK%9QrzbdRHscZ{(6a5&VnUSTp`N3M=!#};PS>14?)sd@6 z4ZM8fnJvLzWCd_9rbHLkP$0t zi=vUjw2LqV_xPhT2S}pY$INUcqEF%(USwt)rh80lAe&!MeLz|*4Lp`Ckmakrq za%!OT9^q3tyB<`*g?^qxP#IWfz#%b4!aE@2v@EQ^l)XPpax(zl##BGs-rOAiz^?2> zZ{FCXkcgl4$vSrBYJN-VDA>OW12qLrhl)|GxA$?3*5nKx78M&v=!Kt!`dL<06 zv}EF=K*X3W;>kWc&>4u^Kvkj^Xqt}O3Lr5M5)~a=>gp53+{dgw9JS)C+3%eetiNXw z`v|ChhCTp(_5hGq3jNdRI!v*HeoYGt3;(GQ08>RrN27oR2sSyd`12utxy#lh0OtTq zadL8knxW?pDnLOXzWYKF5|^h#h>J{4uN8s2H_ue2+kyHv;O}eHw45M8a7&?&M)$6v z&?0~kI{g|rt|~_~za`Ni187*9*A2kYNu?z&rSsE&6)!ZuJAhd3=)_-@z|j1J0hu~X z=VM)$d-#A^`RnQluNyjOec_3>f*{w}@2g+kcen^}FoAc`wu0e(kyI2hIYWJo>V#h2 zl-Qx*Os(E+eip!=15+_@o4X}(*_nE>0ai8eS{k#xcyM_Q`Rab+3%MP9iJ0=3(ERCT(QA^H3F5u>aVvw*8va`s9blY5a=je{6es;c9&PV4d7rm2)wbciLiMq} zal?_Q^4`t+<&rm(eoKaUdC$#(&3TK5h?Rs%vP)Ok&^bN=MWmY+pB2eXRbJj7rn?!! zuXYBuPwO_aLo(%^uS7^pY$oTi12p&%-&|(tDgugUxAB)>f6cq7Pff`rbczuC7|YSxU?sm`d_MCcvOP z51bk1`%0$0p4ZMU?k7b5lmkZ52CaLckqfRFslwHv7(S{j{;cRH9#{~?9+~$3#tqDM z@*9pZUwlxN+a#vxyi;l9K3NSdvPHrZ@=(f*Rq01#J59T&g#DDeyLSxPuZr@~Vwksk zw?}z}f_61D3L+ZA`Kibe1K8@vfN^xH(v$GVChO3m`DJ4Eo4|Ag^xJM3TB1zq5g?ak z7EDRN!oR{~7H{z}0Mv^65jhQ@jDRVxw#a+W_1}RS(EaS`^!)l4)UQ4Z`xf43))J3@ zV_lmjFfd~3VpH|JxqV`sk7C?ox9tQq4K)G@hSBEz}$PhhC*22Jp*BC zLKSVH99Sh>)NSFM$$MdB#OEw##PIUT#eY`r&#>)xxoJgxo_Xnz_I>G{)JfHLbs}Y9)X$diJ{mpB$X9JS65^M(RD>8iROaSpTh35aqVLRX|*ro1L z{Kr&0;BUAevHdrO2as92T0M}b{u{=|=nL2D!2BOUVEY+E4tl04_xw`b?GI--4==r@ zvs2DjT2l3!`30`VNdkpjpbnFIx?kia42tpGFWaOfWEA=DB$*hW^f$)q6 zG&6caZN;yW&|!+3HkGRlh>L3GT=JhN$#fmZkYy9LzW;o%u?ZD~$!*qUkGy0aRs zdcA31(HB>sR)~x9ImBfU;5!O9p~TB9`oTO8<_MMaeia@t^Tx*K1P6XkWP1}by(1vb z$*S)aG&m1P*43l6oAZaKyJ^TL?Ejo?rCTM6JcF}?rOT}?i4SSZIDL0kJ(a^xl;=NE&?tZ0$Z)z;(md1hh&h85z-k#zzAfISui(hWbkK0_7E6 z9+v+Yc?y{HQ#Qh@5Q^q!4m6m*)cViH{Q(2^*LnQ@n%GYC!9h@UP$9;++E>$E;MdasS&-4?^l~e#%l_ttVNBsgwabzt<;rgK(&jI=ujni<~<-6As|FMt4Dm;1zpN^sCM~etLa< zy=n@%N!cX$6*lQQ7j~wfq+)AOcxmeuLa#BmYT5)3+qdgE$I6(rn>mGIk5;WM7w>Guxj`p=alq*aBiR&+WCX~ zE3B)#yNo1f<#sz^fw|Wslh*=C5ae)+B{QRw|F3*PBApop!Q_#iYd3F`PjoR-zhTVmfEZkCXy^{6|a)5v|76*bnURo^+{T6xBstxv_y7)+g*w+M8?CbddVy-yjC@kg~UyJ_TH zVpl$o4!do&T+Bu^=?lr&z8^j_^}~hBe6>*1uJfh$_TG`+&}jk#YnPzDMP5L0Xf*d+!ES(f5=&x%S%(jz(OK7=jDojR z2@xB!&#P?(<{@zY`NBqj3XG*>$*wtMeeRYw(^zIx{b8vWkknns0FTygP`44z87|)D z7pPr;wr9-TH<+MmG+a-dek;vztv6v`O0TxDai@$sA=SHCu#CWMt^Tw}W!VyGG?u9UNNsCFoRfBzqei7mo8oBF7dg!yzuaJaK+2Gq;B}) zdA#lXeQL2VxgxqkHj$E!+yF?E_N1HyOUF)L8@-1d?A1q0dg^E7COq0H-rLL^&@}9T z*Fq3M6N%kYv2He8uhQBn5i1A(e5bbX%JE>fZ~FAhlY_b52#2Sv&h#1CQ&~h9YtP>Jd~`ki zjwNwN;$^Za&gp7rx=TPAP~f-};d9sc^(_gRIOh0ijdqpo{MKRbu^EjtmnoI5?~sX3 zf64vN+mVmd8h*)w*6p8Z-J}#7cvS~4<7o^*sLkX_%}l`7gV?l&aa$$Pr%QU=8`=Uf4|3* zHcn`C`#xS@#EYPAb8O-%!;hC0lx}`?lxL@hmt(^Jp&gw|Fr)u1lgs%H{bn~u>oNY- zvx8wX#9MHPhN_WZV#6=gf2Dbsb@UJSu2;dZ)`aT zGYjNt{Ly0D^PM75vL$rAJam$mQuy9NUg54{-1wruz%4Rg;?+SavOu-Kn$R$b`!rwkI7pT6D&slIo3FDDPqp0A$?NpT`FZb# zD^r>~y?MZ7nq7#E{M$t0sGZ~jR+ogm@lIO*T3rOahn-liolt&SDf-u8aPIM=wA~lF z+AS}AY(e zcS^lxCiF#oz|B2fg&vbkycNM};;cXpm>7aZZ@Z2k#$YoMme_&9om4sqO7jdBI+H$+ zna30;i!Km(&*#i{b3jd5%A|8w`-zWE0%udb-1-697|3ZwZQ8uZg?r9woB3HIEt>?e~A)5DpGB0+8* zsb!TK%P#T;OUUW&!$aQEA?JX5qo?&op1G@X-^IYO?;7s0YYs%AH9D-f^c2Vixb7Lh z5V%`oMc&r`xh-jOJGQFb3=AP#Xo=b8thYK=vc&^ZgpK-~fT28gIIk3R$Sp z8oVMksP>^)S)Vi}Xp8!F!#Xgw_694Z9<+IejF^}6Z82f^Jp|l4RTexrJLubS3*_WL zajCZ;FC|8w(c7&dK}~VLnpA}#vu-5}^r3#_dyuDy9K&kw=&DDerpX&t3T78tovDyA zp=^EAHX=gmk0)iJ8w2r8=UmsCk2Jwjy~(Ck_*|i!%jdTU{=UgC>~fhM$4$N*IOr( z>IX6@zU>eEl6~h5UY86TvWxM(kxCumpMX~jglO=_JG*jH(l>i=y_oew{XZQpK5}+^ z1%1?b`xCT0uwLb<@y`Qt1rJa5cFxrg&QOw4xBP}$>g>a_UtP&wNxj|mQXG^o;QQ06@OZdm`m-}3L10G2W3|gw(J_u&6`97RVbq*Y? zx4_;%nF*m#xs|vcvR;#^DP1_yZ*$!bN$m3Um#w9e5bPBudM>vT7Y zIpqrDbhYHN!Uu08Ws3BLzn_UL{MkeK%*LK^u-~YE$J+66X3{A;^~1zoM#pA%e4iTx z0q-l8!)j?eOVY!frqr^%tCkr=UnSHuJ$=zEqe7i#f@}YkbF>!s*>d7-IwYGLx%rb< z5bJM-kFkcyvQJmLsqyeV&ZjKfB+F}s9u?l-M!BT#m2JFZTh*drgj7zN^6Fi8&|~Sp zx{R^C4jTM=&y~(@{q2heTVg`a@pxY-6;i0e?XRG z2|~Cu;v7_SR@(ZY`42eC9|QP$54WVW{qnXi&vPC(+V#@ZwYWWd+!k$vL_dtZI6=R! z#pYl%2a-3&!eQ#g4bfr5FDke=9p;c0(wBSrC&QZN`3p#d6a?lfF*1J<$V9^n$9@)> znGz^3?o2AW=}k%2{4j}c?2eBu4%7N&=b!;6Gp2EoC{F5KXR zn44o}+l#17oIFbYhJP;s8cJ7LjkFxS)5`AK=Ey($k` zWg!hM%4G|;{o~%`xCAjiO1;j7bkg+CJI3F$-m)!` zw*V~EMo-H-Qs(f`ra011)mAKjj0hC#&D%I#oVO9BR5C(XF;Jq%k^XsrdwnYn{ZAHp z81)wu4dk6*k4lr|&C+snAr=kanZ1r|ANQMIbN4UTiOSHNYL2151brItuvCSF>=ktt z@es)_Zi=c>23A>KuUl3^8oPYS5lLS$*xX7M&F5(; zoNO<0CEw);IQV1P!$<0Sn||-tT7-P_bg$x3W%qCa15L)vid%kIFhgO)fH48(pKK{d_D|;nS51n4 zS-l^U;Ws~{u=$*=S za?FCt)>7g25pyqy=nnoXp&-wWP%drue=4O7fcGd4scm#)6WtMz&)a2jbyWLF&H^~^U5;UNK1L(t{8kh)Akox^|RO$V1F0J$Kj|)_k zZ2GG=K*7nM9z}T{NDQ$`gXbyoFXJDng$c#!Yse?p+=l2LAf{*4ILlOqGb~NrOS_s4p?fU2V{J|oXIN)SNhpM4%fv{ zi^alczqE<^`1W3<{kT9nN>FJTbSF;jYOQiL6an0jCZMvu|0OoQ{4X!g2NK%XEsjK6TH@;sNt05453o2s|B(!dh#241 z^FMVc0{Y&pu%sB4NCnZQ{o7IJ6riWGEO@8-;ERON%D?KBQOjSJ2f}gCXRh~t?@u@w zJ)zCMz6Ndm;m<-8q+KUJNpVa)PaYPIvxRXebNhBOZA-%l~8Q$ zKHvMW7ow1~QKzpI@-0Xr!YX+dW>>?6-HmU3fIVi@XDjf7L3? zM{yAQ)9U?U4`5fGfh(j;I{VbVdpi;iTBLX9yA71S;FZ#y+W&pA(3VUR2Z3+W!3HQ5 za$gL6naKUk6Y`97GBrle_A)WW<024?Ba3M2P=p<>In=@xOT90lBeZCoBe~7@ z72BNL%SMS-Ti%$(S3HFi@QHkjrwg5ZC zkre6P(Qpx<4s@XQOoB8K2WXu8oO?uyaQd4-3(jHDaTQ@!!`X+{#WYV2FBD*{_ zJZMxWGCrakNwEhi{kC1?w5fRdEVBW?wN%QPO~cxVCd-$P7L^K@wMKP>EGu@NI*zvN zn-$?!NP+Rd`bZU0SEAwHzG`=Xb6LQfXjZbc(+DDmN77RZ5JGeEEXw%ehMgpfvcziP!>vI*mwBGg6 zO%)4kM{9MCk*3$a{XT&8b*pU!S|4PypA-%0I@(buf8vnJyU0XR(UtY|^J;jTQSS85 zl2r>DP4l~Q!z$cil`Ji^E()wHW0cxmCtY&2Lnc#c)Q5zi5vns8^tcgYo_o~fNfBNu zT3ue`-;^GmsRr3e7%}|b%B2o!O2IfYF==>K*dgiB^ow&S{b4lH8$^HBNpvUNHrVd2 zqm3!mqkQLQYcko?lng)-@G0QyMKfXRTKBsZbOV09Fk(_}A7<<2eDJDPOgViOG`f2S zqZJP4ZHIM)lT*W=c1@X7`b+ev{) zmi1)-bm2+Mx1M22CK|J^2xak4(yl=WCilDVeJY{z$aNp?w`*!pa! zt1UsFOT4&f8Ux&-so@d}(bShu1<9Fq{z&j{9xXK{vucXlx2P&0mC=*foMJX~+fI`1 zdvj4_-#VPtwEjhBGD$v-v(>HV=iW<@-tLY{hP(4@I)u>|YI5nS0`qn-16lrhP2*ri zCPmMHv=4En>6_Js1~q&=f1?)?OZMtl@Th*V^9E~mKCSKao+-SsS^v}%F=*7v^N5+~ z1gIa|q0~63lBKJp@?Kg^+}@AmMyHQ-$|V<+e6eLkh*PZ8$zgt|@=CTAbEs+L=knbi z*W@rOLzVZ=1@y@~?FMl-3Nr&6S8D&6rh{r%4Bsq4%%-ZJGeG^H{zmz{Be^B zjXUY4K;SLZ=8OXN~R~h)KgjPI9zyM!9jSKl19m>5Tpfe zwa&uYxv)-iacisHKNIswq-c-ZdqKAz*7rk)5>|aTyYTaT?SZH6Z(;20f_Ry^x89B0 zi$6q5_w?`|z`(g;(x#bgm2EU&!|03>J#7Q5(7CAmaq$Py9Q> zxp;uP4}Y*qIGlW$O}a|fC|A=xjkGLpiy%-i4dx^SayN2Nx9Qr`IpQpA3bD3Q@lhVN zkrFd~tAXpc0R&C};~j?0q{tFpW*MyHnYjpFXoWiJ*2R8?It9c!Gh*951G7K*ZUZ}U zK~3W@!7x$#i(#|mT>ax;|?yRKd{m`{PPte zK#^(_1doDovqC6LeqG@j-?~5)d=Pa6`4$Y~bW)^$ynbj4Hv!n|Qdw}o6kU;t)2|i| zuYd5yngTT!41Ny=KEYrclSy#BLU}^yD3nA80f~v>L7XsUS-t)NGiyQAkZ8Dqpl*Ru z$wUz8)!7ejAZihLFbK~@Jf8d8w<{oN&4d~d31`{T$BQtS7C77x`}G01x`N68>}XI1 zm|Gc~%|ZF=hN9p7T|Mq>WQzh`0E=60Yt>|qwh?L~|B%)& z6^dS5NQtQD$E(XhehsvL_GX5XV*k2L0M)M=z6zPMTDw1ZMWp0(6iRq|l`OuGx8xD@ ze0={M-6sOSteBv{-W&4gp;zJ_zV`>P*fixoK)=Gq`f>o>BpI+^7ZzJufoK2SK7RuA zt$d7voE}or*U!S~qtXkKOqgr-ZY;IXdyIdeRWbq)B!?V$M=xP1_N^-g04e}w z$7DYrUu{x38){xsNYfsSW6kv|Mj7V_Bv|2(_n{Zu3ufQ#^n4>iw1Nxuct;b{*kmdHQqk% z)p7%WPkltJEUz>-f9fxm-J0{8ygsTE`Llqwg|hM$`G-k>0D!x6UShvRui8*70cN(E z^W$G0;alhZS0IQ84EHP1)vf+nK<^7b|MfoE{^4)qXh(AEN^Uw|{wx41%3{3Qx-;cJ zW`-1hxJ-4`21BI;O{>tkZ@NmdEww=a((W=hi=6Vhdom-mHHEhTRNZi839AassK~-U zYg~~Ui%L-dG4~~bqX~t@$?+MUhUYLs3oX>e+AHLDEd=1cfpbd7vM%A9I+o*HS6X6p z_E!VjNO7>f^Ur@+flj+QlC_R@lfmLcUc3=f;UyjO@+?k<^UA)wVr1AJU=Gk`1v(kd z+|D-E*Nm|0!5+^~Tv5;YL1QMw_r83GT5kr~Kydq-`+bJp4i-Bd-)eNU2^9$*yftJw zfZslKER#qz~|8WxBGQx=mD(9Jp%o*lHJ|rhY4P0k-|tky2sS zKAIhTU{f#0NcDN)6Ejo$xJzO8J@!W)te~av2M`Pg>6h1==Y`_W9K!I8mSizKm;H0LdPD7JoM4h($!ul;kLCs@y_Z6bw%?>n=zWi@CwJrqc6{HY;&r1Ge@`t0e z%3zv`s_nx}S@I9PcW<)l{-Q}hyZ20feBZjGcWTg#h@H3GfZv|#HyHnD#^*iA{OLbE z&SwHt%5kKU1q=3FY1XM0h|)Vf*eArh#cD*wAz8rT9+n?LsK2gRbl^aOxxYP5hZ0+K ze^l|SVBpjhC)Z@7^{KSrzx~2c0F*3g2o!S1L@ApMI#f8N-K58YGZtP+KEb^nbge<9 zsPvG-5Zua8ynAn|F`h|Zp;x-#=W*||cy?O_Z~nL2mPBJbEe<-}WiSf3wa!{In= ziTFL!c84PPW$oCB2Y53CRGn`I;EZ)k-DBOwufc~ z>qn|?Wtr5j!fiD%+YUC^ktIp(u8hWB#q?^6cx?U>Pv(RuX>kyi!~y=awZK^c;@DK? zarBRL_K5e#i&^_IukLlXvm7;N(T)FBCRnvQ9pFL263mFVqLS#neAON#9n@ z5z{q#hHu5x5Q)LeMzOG!R?4Kh&1jEhbM>3asXhplBuwb%#%||m z?9K@#EEy=42BEHA0sjXH;8${cKykZw%!IYZ3C$`WuNSQR;;B%}QvJfy^wpNwqHTtG zkaFLqI(6J6)G42KNYb6EWHq{y#C&CF_)d}i*}B?f)bYh{?$kzr(u7G~m4bOnnIF#EOa%^Y#fWp9PeFB>O_B^}$-+To zce_P#->ll`UqDEo+(Fq0Za83Yr83~A3YLyg0k^UBg*)6AnHv}5rDXLy-lO@?+J8;{ zJP|K3ac@zU03A@qlZQM79^xrCcqQPRVXGN)tlWB}*l&8g?_tCHX`p;bv}bjvFf~U# z)0#A40eSq1-mnCQlj&>2OBz?Ha7&e=v#@PAi+@9rZHt$PpuvocH#X|Qj0sNjFXbQd z=5j9*eSR?KYT1Ai2lVw0plx!llPu+L4QWEed=7FxpyUa}<%_KwHj}tfnaPo}+kr>{ zG7+4aY}|Hz#twSj`~hKZnBkt-keeABE9+*r_lC?HL8{~xMurVVHS8xzj}7dnCPIOO zVeeLY-FGa9mYgqUK19%xJ;luG!p z*b?2}%T@Up-MSw{9x3h4JkMj!(!@XwMiaej$n0Lc!QAUUh$&oX7J~>D|c0*^r!ODtqR72;)uKxnlhXpI+3M9IcytU7F zQBq@K&2Lj*d#FT*AW5~W#}xAW;G*S_v-%@_tsHSw(r`oijBep z2neVkDMLzkcS;OH4c#RmT?&XuOLs^!14DNyDIg);Al*op$al{S`26->-}}A$kGS$9bGF0zA`^XEK3UI*0nBgMS9YyXwXC)dhDU2maUOuj^mVYLG&@sgE_V zDt}HGJVUX1=Ght^5r>Q1Lr}nr!@0t-4_*ae@qwd0ree@osk@lpCR9)bX-)TM0BibL zfRNsH5^t!ksP{42vq&I8FOYmlG@mzQYNp6K-JYHJwnA@T6Wa8+VExqnf_Zx1s+m}) zF%rB_e!6iPPYfbWxoi!HsdiK)sJ}UI&gl`sB0P4A)u(A7bxzi!{yVdqPE_X1lhYHf z;d|H@^tunUs|dHZ^0bJf!d|lHc0b&gntX7O#jJ*n;NR5K=UiSL$A#S2B3*lQ|Bk9j zC=DVDEU(Vo&p-8D&Btp{`ed}=mAu+Uzo=^Lp`mB7-9w^>k`k7q*|}eCqd}YyGG1_Y z0H@d(54G~!3!C(CooCCMN%Q%czR1Q+h90PRcjP42fK#eQJf&2=N|+iib8&Za5co`g z!Dk4JPJdFwdWf4g6Z>i|ScOyjNi}7BeA*`(X^Tx|^DNDtTI}XsffQTw0xR+@`Q2h= zOA(sp;)D#qva$~)rg*OR6Ow#ov+}rP+Lw@2@YV`!Kgxeo~UO&8+kI$;6aQ~B12X^;K_y&?DJgECupMJeJp;~ShgZA&dI7{%g-FIN&za4i{4 z4RlXAzyUK+9*vY|`864d!m6vg(;hTyd^(4T`|`cGnOJGq@t0L&dPGRj_7nAw`Z5g&&X07A(ogrTwWW7;Z=Ec3AD7I5xE_|X9KL3EN>OP9 z8BXsfNidoX&oTEsFIQ~0FD@Rss!ObT`&M$Mwr;Sm1EyGYGa5)4eIl;?o*Df?m&R&S z;yknGH`^xlA~IRTv{ioNUdj02VZ@02>qA|gI1*NZ`)d@=%x5^t2Tpg>b(ds*XEk|4 zZXCgRr8-;zbE3i(9lzBR?(bL6RsmM~V3Ma7RqXX4Gz-U3W2)=2>QfI{*s$5-HdLom zxoH`vGbN^*?_bXT@n8|>7sa<*U%N=|XdQ&tg%at#r&_#0I3`qajJP6j}d-wit?qC=|Z9v3l`5{#3FW_fEMKji8*Ko1W z5`OIlDo^X438${UFvq3Qdc*e5-K($GQCEOvh9E$$MVIykXn;X&4?e!4M}htC=el0g z5x>86S-;&dglzijrbl{AC1GQWgWf(;=audbz~j!R-?o}ROIn0{82jB0;TSkf*wX9UI<_tK?&Wzx@f;2-4s!0o6b`+ zSQnpE0QbvPlaxJvRnhAYBo>n+FO8RwUJfz>lXM*FOZ06Hl4$~S|4_2`w3(H!_sZ*7 zc8rU}d%IU_$zT-ypCi9QF+HCPsQI4R{&i5uD7#F)Ch!0@)Sir{4FE_Y zrwF{LRwlgawdZN`^{}hhBOl3?ufW*#RPe8ufqLX(ZT@o|LI>iv>fDs~kwbmRc_9X+!zO>y*`8z`>E=ho7X^Fwc&7f={7u{sb; zuXAM^s?2Uoy{b`#I{vt6hJCrzz^;e_4*|?fn*BR7$=jE_?;(}LknmFs2ZF0DF~92| zdJoF}P`mDG;cQ<4FZCY5lWX{{=So^ocm52|_6{PvT1KouV7>alcN`z|XcKF z4|i-|^@(3yTn(UC047K#F7N8NFV|6KZtWV@{hGfLB7>53&3mlTSL?%d4P`r@ei0TX zyb>Dq0N@701K@Va(lIM1ha{1Qi2$jCc*OPhtao2xkbc{(2ITN(e`zQ`e~Xm#bAH9f zC$cAhp9smT9XNtNDOVVPyYlhfh1svZBC|aKo`bC#aep1Ms;Q^mF;_CXNQx)+m({aC ze(jBb6`qfs;@4P|zs$VMF<-kefm9ro6_`V0G^70DE%>I8$fGNz64IjrUM4{V0zQIG zxDClXP5ZY*eH0h@l@J8X)5!qwTV|}%8n3*Gqkpxp$j&jq%-KU1pViKm$hL<5I|*}? z8aPu^7ZSbJp^KMKP*QOZulV27{E%9of=D>4JD8`akMz_ZL}NuH--4pSvmv^kjtU_f zZ)Xx;H=J}RdhIIx`&94VQT#%F)Xh1wVM#+r)CaBz`)jcQ0)7{$*zYQB!u>0_-+?B* z1WA9-e_z=Z=>AyL(7z%8uiab%dIP#Mng#x@b`c-Y2(X)9bX7!f?ZzH*%*PM?fBQex zQ-H=4S2gu3k-p~b3fy^brRZOkp;y#Xhwua4S#kqF7g2DHJJ5;cTI+tGx#^De;*wqh zmPJcePY7~LDSD{Cz>>8Re2@XADIbS)7ytWw6>(qQA!^iCU*L%9;AVN~4Atz;TpU3V z+O^q1+9NiOQyym|y*GVfpBdW3%>I4P=g7{S+A`<$@s06<>&Xi(k%ZFvewN{$gzM>W zf>3N!$chRAvAkCuz;>&@IzxJEwF>1THB6kZ;ru(nL_Gqyzvd~AUbT)NLKBzU|VqJJ{X>n%gpy_PH^o z8SaM(<6~H~KszJLC$SuC7oDE|ieacuy-^IJ0ia8&iP`f??PoPt| z{&!zDfGH|N0>R{-S1YMUf^0>p_)OPwr4>NyXf)8is=PD6b>;ls#ejAVNndHml{QzY z0@5JcsyP4mop6BbK6^+Fy=ps{RR)>{8Sw=}F3U5>iX@ReCjTD|9Z$I~8#*ZeN)*ax zIygCTQ z{-WNWXJi^PbBm_iD0^t^N^}Hs(G@m{NH@yv7@aB*7N#~#%==ROgv`@x zK$K_c!oWbiAUaK0EA+#VCJ{wxkw?T5wOt9b>I2mB0V@Mo08i*ps=&ehJ}amFJX`!# zlPYot2l~BN#}_M7BUJ{Ab^k=gawTcx6Aia0%kuBR^c)7%UX`@cM=#<&d>&<{BQe;U z(JuKiK`-+>40o@?YkSLdW{o+vg#gy=@VjLRx9039nJ}Oed^*~`o5gdlf27;Bz0b|) z=ZrH{2J;i~6`hlGOsafj8TKeE4JqqF@G%Wb@=4tW67yS)IvfXU|T$ThRH2+ zDuR`bQu9a`=^yiH^+O3ljp#Sdjwliy#qHCUtB9*xQxD)yslUhd%ZYsszxz?2B+bI4 z@|P#3V_99bujmdg>C`LrtjPSiTZ+KAx-k{d;+-57f=_I(67^T+vet z1vwVcW&+kF=1MIdJJDz3QRB3+0aaYzP-Y30>lvu20qxu^H64^1+W*OEUDNrdNFjP! zj!#oMIsmUAFL5-poPizQP5DY0Ke5P@4nshtG7vjROck;wo?tcLUwN<8if#9gs4%{M zd7iZ!WLorwb$XvMeGv?C{b;d2)WUC`f_>MTh*4K*clx_X|7dAH3^ZPnr6FVTJs%YN z*mHnD-$^}GiqW~c(;6Q~NS#cYu5)wn&@~2=#;We%pHUGj{b6(Oy1Oa!Ro(Ap zDNMUY?O|?Z0M@+T%13oPfqD3Q#&vOv;vIKc9Br3F6JG^YNB}nfea|NgC~vPffs_k= zC{<($v-Cjlcjmyn1a}zi45fkwt!hP}BlX>E2Um#+ei`Q@u>qRbYKcw(AU0fTPRk+9 z)e|}Lw50nCxIaEsupSTPHN2jeQNEVr_+I?Wp%ezhZY9CsfsPcH+wl7Xp2&SwM~}>n z6)6Z@JHrp|g{MUS2;-+mjRfn&LNj}~WmQwt_h_0_MCM24Gh7xP@}LhaoH_o{)lVPC z7Af*W!fz7P_k73|`&pgQ<1d5m53E4u!vK(?k0=eY2eE0#PgbU6`g|9)DI}EAmnb3X zqPvz@?~!e$0@FOOVC`}IAuyRo|I!4{x?hrtD zd?}2fY&MgGUTc_ckgP?v_>&n)!th8g{?uw+4KfWjT1`h|VB}O(GK11kNcJ~Fy0url z1cKeQPj4D5g|G_}e14OgUF~b^alWMey3BVtbmy==3$FK)<{?cze&*KzUMv<$)w@typ`O&18o7ED8n0vtZ&0o zf-L&oh03Vk@%r4|2VPH0%C&h(j(75i6R2P_kK;n+guOeEJXMq9kP(0Df8ZE;0P_Ro zguKs?yc^2o={Wkb8hSntFZ3{0DXd_0gdcVm`x51^M zbM_8I`7zt)NRZWlSW`@OyY=yik<|aC^SZXVCv9wtj|J;f8oTkO`mWRItzWNRa1XLD zx)!Nk2W<>eD2u;+K?gY$!Jn>8!eSFZzcCiD2nBiY{k%k;=zzc%l@2M9joasxrr7dP z&;~6N0;{>qI1}k3m7@)2_HXX70~p*G{QgtBFt%Wa?D#h|Dl!jBXd~Iw5lyqdsN6~B zbD6?I#Z8td#57BP#$bF)>DuP)#}wf2n4B1$@AhotR`h&BMY|46v{rj>PBrXiWu%J8 zYt%7~%#X1l5vKZ%=1;8nkq-rj<9=fP+%h)NtGGCyw-=SVOqdLUYc`~Bc9ufj+?;=Z z+^*=dBw|S(@0SQ>lygh4YKp0n^vt%wCnX5LX6oKN{b??|#|6^1U(*x0LCJ{uZxHuSN%{sl&K&;GfZUC~=9S}aF6*${e%1Ppo zUL)i2_yajE2YL@{(Kf3U*q+lsg##}H{`F;-e)h^bdcnFn_> z*3)n{A>+uNf*0wTCkBphy4+)L(Oe+W4~p~ARD`syeuEkYEzIXr`_(y*G0J_2-ODBv z?9$XEdt`cMp%SLUdE`$nIYj9@C>O*P?vf^_H+xkJV9_-4w9ArLNBMyme5k;4v(;9r z13nC*2y+IXK*b*{+EJQJtM9d0rj=rJ%O>$dvkHj5>)a`L%36%yGa3x;^M0~or41{o zz1R@oT<1ik=>9>auaTBvpl__We(AQw%3^J~_7M|3VllMS67Ls9NkwVX*Cjr}?jm$d zJRt(?2GEkQEAoEodTHA){BbL|>O67s_$sXRquh98w`&TUToNiy{$d1ZlEY76dQZyS z-M@g%Dh9ADec;5W*mZwVVJ&bVz{5-xH+=dAX(-8qXst`W!;+}Cr}LdAAW-O^q*NgI z5QwhY&_-5b7ZkuJ3)4Giv=5lS$NMjBtW{T+-5621Q19&uHod+>E23p*M*RBMR%Jg^ zd^%@GI})|A=;vJWCnU%+Zxm{C-QK<3_9nC|D1HQDNoCfTMF3O2P_s0~N;;{fMw*4a zc_Sv2`O-$V>lOgB@CI^pt^*PUY@VRDY?N9Q#ffMCuHQXBQ6`a|wx6yl_PN!L%~d+F z*8LNA4nC^>AO%Iyxf^yb>e-~6F2C^`*8xfNDjpgzK4Ya;dcQ4VQA_%Pt^ERJdWu?+ zrY9RAQ+Q%)i3h+Y6_teLx!;7-oH=;%US{bVyFi|VI-5UH^sF)i)LL1fd6BlCYC3*UyXl9WNU8pC9D&Il@@T87P1=-U1ksTX_S9wZ2FIURr8u z;0e1z9LwUw1M58xa2KQu>RM4MX)GH#T+yOzeQNAq<)DXE5Y1eE8f7vt2#n!@JgDv8 zNEWCI*-oy;_ADgN=P2jjwqO$bfYkplTJY3xoh7Dnba+oL*V z+&vaI4zg#0t|*^`lGOw$$SsiF@J?x!{9cos6aMX*n@W!)Ea~!qT%*9=#SndNqQ1fa zk^GuP;%8V?@agyIQag~WO1_g8CnltrYPvT6cp$p$MmX-FpH7DAqB*Ssf2YoCyz|zt zeKM~o!oXDr~TjhcA6?I6AR_}GLN{ZW03aRj}lAuPwx8)*>oDerSl*KT|YM^?<+ zrXe>w1raz9t~1|1#?oV<47dPeVOg^kk|1LShA45Pq3mZ>I?B0jvboV^gM(*z>7uUX zrG>mPBTjXhk3XuxCvq*A+z8dXKQr8v2^fkxAaPvjz7tCEa<46cQ>V_kTJIa0?X^O3 zIKzYBP5w(I|6Z?NX61g48Pql0V#9j#_eZ&jrX@Z_5}IkW%_9dkoJS05jQQ+PrPOhj z5T)Zh_2Y{*NF5LVU(#{Ol-p{z8|~?`5{)ZS%GMdp%GWvPw$1NNko7-s{6X-FMWSrL|yZw&3&o! z1D?cZD;B+Bl~|Feh}(M#Y1kYc!Rh3(s^_bQwl$rl8CMa@J=Qoy$#(re`$xRs#QJuh zMA5`QcRY{|{X9_LIiVhiS6)7!J@;>b{PpLi-CtN5ByU$YLbPmu?oBV|O8UuNDQJs( z?76o%5*>d@L1Hbj8c<(QX=rMzbpl~U37i=@xx(O|%GC=Cg28U)pXRsBH76b%+!g^P z5s)}8RK&gHigN_9c{?qxtB zU}7#MLrID)@o>Cd_!5~T3(2lDvVL)-x14-dTRP4ngXM+J%GpAs3KsYsEr^OS*DM8$!-lHNETld8`i)tx{MZ%lDVk!2tUg!x#;xNN?pc|PDsAotwzSA z$N{Qgy32JWMYIi#aRvVu&DOOU#}W^PDQuWCh;QcvVgdHgCri>$WSK)!k9am*Kf=x~ z!vNobtVe8M)?(H9Kd%yO-FQMin*YZ(Qp zyV`?+0ayT|q<#CpxNJ8;q&mZwm-_E(E1tiRVq#6X)sl19y7jYp-Pc-xXfX<$rX-Ez zN^R+QpWK9xy2OSYkDEWzMw0{)^=Irj0Lb8I&}YLVry!b_po;37{@bkIYqBuEF-rWGXq75UMSfrqZq*Ge@7r zm~_WP@y_0!CTbJ~9GaVdKOf}hm16)--k;sQUn{V~7aUU(n?XD9otwWWvxQ8n^1ePEs;)IW5#c?j2E%-bv0&{*DmbO1KupB z&Xe0w%&BN3aDaSt1VTp0C?N2Nk+3ue7ROv_ao?g@?_Qk+D~|kq>54l4S=ye_;TYou z={foBVx=9s{MRLUq#KMqcM|&qT6-ylmG@0v$L~k&?Jl;AkLZ{V<--^?y9aW4NSi(h82B;QqLMtX ziJGyMipSCgT;mmIb5b@U3sFG?lj+Fq-QXw5DdDqhPqHCE7{~%Ha!0Z?+b2(KA`O0u zp~;Nx6@-&V93ihu#YLZSrO{}LpqcqG4th>eQ8L;q0!G)INTNJ3)l&RRbp+)-g)Tw{ zVE^!{cIc>ZjfHmacRWw|+1>YN{jv7S$IR;>1`rK^$Ob5|QHcv=EfHW7?Cyy$-B}&P zH)W>N1ar=6)aA*npN-`w$ueU(o^=pd_mdv#+#N* zA4Fwz;1UDRm311*uHAO0c?J^1=~I8-lUiuvY!!W`B1Pm@hzz>7Fog~bC`@(w4ZyY$ zvo-mce)MR8WkS?2<+LL}tpa9!1|0<-PV8fnswv3;ev8;rn#pW#l<`An3%(S=rPL&F}j zzmr@bHpZd7+-QXR#>WUff!pdjT1$7a(|-46e~>}}YvNr8Zev8wsaa3gJ<4*omwjqh zSv$A4z39#(?&&MedEnCEIo*e#W%fFnWH?!kJYsyU$G2Qn7w76(uI6(aYVjF#@VZ=W z=}Ua0B{I}!5mY!t(`=|ho6{PYpywJs*>vr9z^gQui`>i>m_ zsjd01ims<+QJ-RbK4>z1_KIX4P|`n^5m_-0EBRXo#>+$ZeH5P`W36df`9Rjg-=r7- z&^Qrec(xhKS=-fLLL$uQU}W9OZNdDaHJVT?tHh&F#rpSzeHU3knyf50b{-wA9NNfA0#w;^$o}V`MAW_Rx6_USypm z5FiUt0|Byy2G^oHQcag~I!8_T3DAI8WkeZLu5C+3l_Ai@ai4XFVo1x&G7-+{x?M7- zh^cmqoN5EJULvn^04#p+!o6O8pRk=8`%}eF0Z;syq`2eypZ|2j>VIu?-%eh!k3H^7 zgFe1A6YGDYWAUUht)<`EbT@jUDMQ-bW6y9i(v3#CwOw^@lJwRMQqJ@iH4m22tH;Rm zRHlF2CpXT0Z4=f%kwj;CppEZu`9b)7M|txC$1$GY@?xD>$7Mu>fi?$$Z$1GaIgjoV zB2n2s&q^m~Md(An_6sTB$eSa^&=;5Q z3P--HJani>#I*e|fg~5@cQ^k>eix@EstD`1NaAPsurRmMlz|xaF0Bp6vTHxH9lF*e z5}D!jTZeVH1ycOYd=zN_8YKd;cH;YW^&@qtif$n7?%aMVLauw#nXO~pUD?tb;Jm1| zbevR*)-D<;^g!1y1W05yUIV(g)_GxN@v$Yz)vO2cTrpN<$t@w(%z^uJiyG7I@xTJM z>Dj5*S-LFG*QApa=Zu;ZexkcWIFOQMxdsyS+?`&07`*%>mXNiua9E}rcUO z=M%0yXL)3NzSQ2KgL?2PE2J6Fg&gE4*53e7A|PUKt72XhypVL+-Zf5rVTK+WhAPo# zcLck!Woae8fPm)8OEc>hw$EOr_hy&TP#s?EbC&95g!Mj1-OAY-)3@+GV$7j=t2TO` zK^tBFL>?LI+EG*89s^~6wa35h&onmiczsXO(`sCGG8U8>Ug^~CtJp5G*f&=qw`&%H zuXvv!0Wb2(<=WY>PXL;%oXamb-7196z&m*(oyHJ2KsxT#+)Z`)Vy}U0y2Q`TEsr_t z;$6xjKzJt1sem%7-uqC7J$5Zd5wjPAuoYt)+M}4R*s>9)kh>qk2HK=6D$SVplFkRU z=RXXuM$Y~P@{|*oB>gpN1om-WR>C)9h<;H7f;c4&1~^eis^>Rf$Xg~>XWpfKgq_p$ zoj^2mqDEul*R=7`QW7&k)-iKmN+Kt`6Ie=|%PSHhX_$4OSGqQdaepOCz7RtU@RON4 zzystL;HU)Lvl*(e$6FMc!#Z#P zC;9G|B&Fsl16cK$OQanz(u|fI&T+7E5Fs*@SZI=RUhos6ko*w9%@aEjO&%X0_eh`K zs!s=}tKHiKf`i1I@MN5y^<7T%;mBa5$Qh}**$9i}oIMBk?uUbYUCx%(Te|AnT~DJ& zsAnaOtWEuVyImO*u0m^MQVz*D-y%QaZHvBB)MuiVMGV>M4~5W681*;&FQL?glmc$S z(P*vsZ6~X3%5M2NBYKfeG_K}F;%aN?q8EOa;N2(BU!sD}mNBYFwJCu3i~lA82qn^M z=Mm!a92==ECzJ}rK5gi_9!8Yx41!VUl#qA&KeU9(2wq;ovW*HgJE3XhXh3`y>CgTf znG|%ySnD6H^KI;%1Ud?bqW$SAD`ROD2&ExqDt{WVsocIY-z};ty>xmWCja5|_!d;M zmTHl6KSm|wjPxhUf@xA$iW+U`daUxd^ zWIQ)*d#S71JsyH&>iWrW3FW*C~wNvk@!Bpqujh8{IRo+JT)kzHYIu z!E=qa4F-y0@LUFpgdR{%l8FkJ`Y(ZIvr@8u9^mQ3#1CXMf2@f59@>3EWiY(0ku6;Z zKoc5Nfk%kR`ZOMVGl@<*9aw}TDhcrpL}K+WWe_v~rEv!jP#PC4ucna6UB6~(0}zcB zshb~>6%JpP&^{A?JN^fqDvx8UhWvd%v-9WgJ@hmzruTkd@-csW(6FWZ{PQ;@_6{=k z125Q0k%xvoESDlOGoa#=fbX&c#Sft};t%D8^^dV=&-O!PF50)UNH&to;00%)j|7n-|L@k+3GLJ$Bh0zKvfhyB>R7*hoT9| z9ccBD;pG2MNEM)f_x+VA9xoyDLm^zX@8>QG>)%MnXn_fG+ZV47iRsk+!K$>G4{c`X zZ8?9#L-XYO?Bu)OpWewZ%H#@62YtYN{!Nacgf#^Ajd{Q={RiF#b99)uQ%+0=lS`Op z8$sfcPcKzd>|G6JA~94E&X<6j{YKo6A>v2U(~Ec`j-va9zfV0i?H{-eNJRIn3owwp zva!@mw+Z+(v5|#F)qFuh3H0srupge|BxJjck_d6Zz2NJ~x6Z2zrey2075)qduDJ+6p7Bflc{%z-@vE}o-ps8b z{)C%+2CI*+D=-o|*r6?jyKepUzGfKLxEoeKHCptn5Qo1eM?LgDpa&yedx z457szODCR^+^^a~z?|72M704x2LXsmrf)RHK4R2Hd2H{GM>FaNU=Io^N zUp%zxE4^9-p4}BYaotMxo61@ZPEWr%Q?-Mo6ToW!Z5}%C&-aTF+?zUI+3%0Bbx^s# zEnLy*S#-A|I&+>fFfEiEf<~+|Q(?okr3YMiKw!e5O&n*V=PrHy`DbqV3NThhpScjXM^FW zJD4eUd#UA2Kv(cvt<|w3c8)MGDA6!jsm5uSk%Spt{V@9y`SJviaAyrpK6cJ zF0?9GWRxi4!+n`qv?5=uDM&=rNZb$}$kiF(_;$i7ZDo%#|1 zBf+6N#7-F9$VzX6xKfi^OO}$$MyJxa=EdJdXNir0H*|H;F1O0Q`)|m)j4KuCSG6{I zAjE_6i-3}C%aX|x%)ySJ>;CFqM~U+86R?@_Y0g6%hBJf0DJW96ED8G2aJW#IOg^UB z4t#75K1+mqw}+CBSF?X~)fA1(TUK>VcsccYtEY6nCoBH9&rz;riY4h+K$%X&a=4}B6UfaXh3`&(^-x`u9QA2LLO_9svqRn2T=#C*aZM@6a-ds-E z5Bm8rOS+@#*J)`M2lw zT3afo>%;2DdG7eR#NTSQv8j&cU(DmSY_{%xQ%fi}=`AVaGSXt>-D7<)sx7d&ZK)ml zopQV6$0EAQc-jY8d&zl_AGwxlidT!0co?tSN?iQtR7LQCH#AG^p4*1YpDK?FYvOsX zOxb~T>RSQNJ{?n*&Bd_ISoi-D5j05U;*CwsSuGN?bvC!i$mP0~?8v2Bw_eRH8BXk8 zvx^%Q;apAKtR#cdpE{uhXoX}f>h0eCQ{ahY=*0&ZQNk->Fc7!0P1`0~H|Z<7jaX;vbjGoT7g;OH?J{bma2+K47?HU~sL9qwvAz4) zz#+u>?}Np1XV7dB5SLA^)muW8oL*7&NlvQxz!qMlfFb-OxXrHKWW zk6v*8Zv4mK`CGKyC;kI$thzS1fz@!kDm|y417)iDD5w_h{**fB1<&Donk{fg*c6uz z8Pl=kNYc1sw}-fH7QS*FP_KeGt?3sj62Of+Un@(|w9{TAqAU59JBt?*ZCgnGVFJp?kpAKzCTigiq-i98O{Ur!# z0?f3txJpE6dkT04?P+vsYD3+FEgixZ{xV-U*eH~HhVz%@2RW6%%2WqUYNn>&*> z%AX#4mh~W51M0fE8Ka5@{t*jsgfCsIU1;eUIddnQlbu%#Itu6&s!Y>JllW+uwlbbT zMk8xkN&rj$DZEBm%b&dVPP*jUwMRnIV#2E3b|qPiho^inqCxZ?ECMT9@&eJR9Y_(> zlN}(q;XW*XXTryf7}F4Nxn3G>eu6wqX0c;A@sg3n*{osFF>n@Nm+gG(AJt1r~_5&pWNzYAo|Feo^16C|n3=hb*O6K90on(gyJzNPFAZ8 zw6^WdrcaxIxLzqaf?RFxQ?9~kOYKP>qH9npbc(}oR3h;kb4JF~K~#V>JA~YIdIb4) z$$PAnMb1;rd$S_4P7PEnRUR$`Glisr0tvEjNr-Q^y>B?U{zw;bd&Mano}2eEBT1c8 z*!3Y3v07_Ll0?*c#Af3YiiLL>Q<*FQ@?7BBykG_rw|d@f(&k@nO=*0xd-zHM)~W;d zo^RV{N}@I524Jv4TfuW*_o))95pzTPi+yhe-R_WXNi@<=+@LnFY;N|{kJ7`^8I1l( zPRsd`t#O#GzN9ppLn}we2MgOiWSNvMg4 zbUUpx$wTc8ruH9ounXB+@+cu@Op2`|WI_TxGKGRFiAjPJzbn+V_NX=MnxYH;+m?n$Z@26XFyEcjU9&d(c&}q3nDFobRBY!fKPsrB9BmSnj{3ca$h^JL+S!lK%d`POrWiB zCWop$jni6n_4n3535My#Fs`HQ`;CbX^pFh~4&;`$gEP*`I04s0y=~8@O_%ilYCu5p zdpL*G=$TXGakCl{VJ#jGUfeJwkUf2J9ZXwMxj>3Wc|kKR-s5p{AXh>U+n#&dwO6xz&N*mdB;12d$dVf2#}Mr5k-6eok-oV8=sc3=CA`cmeuspxK|p^3>t1z=;u?Z9oy zbqWp~8Csk5vrxOC3g5N5G^k0c2}TRYK)ifROwW>sA)IXrHp0kWyK%O#|F-@*Xo7yS zfo)>JhGN)iPomJO$E?osMkkGHY*$Y|Sf-l=3SIvlr$N6WYho9Dv;XiB$9^@c9^IRe z25?8kRe^O~6`tujtTm>9nFwlg5u6a~T7Vwf7X6-z_{W%H0Y5^E&k{@KWFlVNg$=-J zXpJwioks(($4GgkN36{h{pb?lMjUbtK(6wWq~-XXR}q(mLTBTao$oXgOPG zNc1+5xvnVahPe0>_W~QYz=hMbfr97TyHs^nY?|In!-4OOaWjc>njV59f!WhYZL33W zBT_fHd<+vv>)cSf>HL4yP0&olPQiN$_z2p^#}Dy_Spz;^?0?6{7w87e6d&0z@Ul4D zppa{5_$0a@lJLsMgJ9O_>kfYYrzWmB zkR_?>>iy$?;~W(x+Tx*?Fkzi>L%xg`e{m{aLTC5lY|?<~!FlGTdKS;K?wMX;D&W5TAk#on5l0KZKIEP{nULsQ{V@nu9&au1Z|7;*n&mOu%w(1y5kX_I#Yo zdGC!0DOvMa${H%$Of$-J&O8V=pD>>Buj?|tkbAdOZWf8hyYh{}X?@(0t|#SMH3@*6 zjbB=cYm4W%JXcEI4{+Yw9oD5~i!iK|e_k;~#qxc{?2aP}M!7g5@mUc3TP^+11k;m{ z2DKYieG$p810FDv3U<9w-3oUe6V>sJz`*@UuoA-1Q)fR-O-Ypou{W4k*?c2-Iz8x1kdwcP_wNY%kUtQN6(_6c*&0WhClF-1o z4r%0(O2(`i%PHu!`f(~&ywW0&7l(Agj+oNP+$J0lr6@!5t!48ARbUzYCeSqwKnbihX z>mNvR?K7#X%H>&4;N?WL4;n1b|LNNHDm8zt_1bO7tup59F+CDUdmgf)^Ci#3F9T9= z)cLEmPHpGkeLV$qO(}g(Of(hQ+oHej^U*6zEPM5^Jwc2kurwlaO~qO(u#y5nssh`TyxhsXZD5CDE}RaB78?ssA1e}~cs zgWXuQsggvMgk6SU^I^^EpUdxgks;~m|1l&T2VLlP4ktI;MT>+pfD{GO3pRg_XZRm3 zJmU==dwpWdjjwe9gbCYZykELYdl-1guaxF6M?-&A@=k@UXPoH7DQO^6%6Xfg5}F=y zCi_&;d142aP??l>;7F#3)dH}spgA$7$$w(ohV8|+xE?;WOlUMwi@T|Hsf$>Sb!iTwDVPoP~1glf%L9u8Z-9fRqM5|LY1?@A2d}2E&Ue)tX2G*jdZ1>nX?zFXy z7Fg13+v~}p`(>XAky%F%AVW{tmDyUHmsp>!F_xM2?e6*3bw#vR{qbCRanfdLM(V4# ztWlXDX3+4HDN*(lA;LbEOf8+uT{@5R&5=z!5aC0DkYeqvD2BQoY%X-8KFRZXjtjdv zB{B$VW>uaJ@{7yOJ2WvpyyZhpN?kX_{MhC^GK9HnG2-Nn56 zh-VX$3Aqf<&yBi{u+Jyn{^76G1A);xZ+^oZoH zAKdI$<7P$NZwxFUm3b&3-5lx*{L+*7C|*Ld-27LSunHq1_KGsedwk_cf8qPXPhUP~Ur~#qLvx`pY!CL~q zeND3rF^_=SeLCswTC|iG#Z$n#pTqgVCU(0hK>5AfJw!6z(6EHy7j zu=sbMa-Zv~KQq~5dcG89=cF_tf16G^j0P5A|G?6mNs84cf6866qHXfeOjx0=V1kXl zpMzJ)-6@<`-~{Zun4q!e;lwXq~HC=KRM$jreaAWqo<_q)|7o}ziwq)6OyuY*G6U+fI@Kr6#9rXe}W3uk0fbJ#-4SI zYp2~L?Z>5b>q}SJ3$xQ}$ga#TpUIZ!1@{#fxBd3{UfaZcED z#qjG&8E2k$Ersvp?MBVhDxfLjCnSVBmoE@_l;w7?s~c6>N}XO^ zl$%vG6EJCeg4r75Rv6S{q~*M()!udggLFvWQhMbiXGeo}gQfbA4dRi!^D;Ch8Mck9 z9nJY}_g00OM44d4*`U#lsRAB4Ujp71H=3N-)cSHPA~o*d|%IA7^?KKawGEf%}xcd4)u&x1bb9uqu?a2cPa+T-u$xp`T0uM+~Sc?-CkWlZf`!T^7w`Xa#Gn)IKLH&i^e>A&g% z6;cG`^CGImkp}o-d)>rM;5UEjj(P5N9U#Z4?*O0G0#2R%tcPx(0ZyI-{2)N$ z)-nrv_0cuzdl*kV14unoOR%@#N8ot>dDt(*muJGW_({Ce&dbC2`if>)tkAvg{5Zzb}DV{t&A;uLLjey#$|*Td(A(=c5<(4n|SDDJYkc1i~sU) z4B&7eECk5UqRc}%kNB$ym<2>=ZL2ZKJLrSs0GYR5t8NEdYXjW}-I!|JK&JmH;zS8XF zbcA2a0KI9fq#b=dW-4yQX6g>6PW6z(-EhfTPSr}Gx{Eg3aJsh@N*~)98^H5OAG*QG zlHswQZ|xWaAKd6K=$;D92AH)VP$DGi$+`e0C&zR>6EKF~k;S;ME0%uRk@d==p&qz> z4(9T%k_WQ0aGS+VJFlGp+iqOgwa9s$j=RTf8u5_!y{wY~-p;%z9wA})B2}EFBZIFT z(Nu#KoLb{sCT&FN&hSvdfxu_+=4ek}{_WkqZkm4e^dj;|o~Mgz91egQ$DQvx`4ec$ zxwq!-m9Q^g=~g?M#f|pNZyZ8m<9@s+wWvaip`he^gHE`+Y z2)9VT*1v;Co&kY2pfmnVIDdbS(FG3$Me9474E|2Z#6||kGjA{IS?9W3wnqweXBs@u z1kua_%Bu*G{o0(U)NS+%DUPhPY5{wN{l}MIHgEp9^YSN0#3t|W!E4v9VHvASo5;&w b0|EcP3;ad?=~szF{v$1}Achdp^Z9=OwG7_o diff --git a/main.py b/main.py deleted file mode 100644 index ca10da9..0000000 --- a/main.py +++ /dev/null @@ -1,406 +0,0 @@ -import sys -from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, - QHBoxLayout, QComboBox, QPushButton, QTextEdit, - QLabel, QMessageBox, QCheckBox) -from PyQt5.QtCore import Qt, QThread, pyqtSignal -import frida -import subprocess -import time -import os -import requests -import platform -import lzma - -class FridaWorker(QThread): - output_signal = pyqtSignal(str) - error_signal = pyqtSignal(str) - - def __init__(self, action, **kwargs): - super().__init__() - self.action = action - self.kwargs = kwargs - - def run(self): - try: - if self.action == "scan_devices": - self._scan_devices() - elif self.action == "inject": - self._inject_script() - elif self.action == "launch": - self._launch_app() - except Exception as e: - self.error_signal.emit(str(e)) - - def _scan_devices(self): - try: - # Kill and restart ADB server - subprocess.run(['adb', 'kill-server'], capture_output=True) - time.sleep(1) - subprocess.run(['adb', 'start-server'], capture_output=True) - time.sleep(2) - - # Try connecting to Nox - nox_ports = ['62001', '62025', '62026', '62027', '62028', '62029'] - for port in nox_ports: - try: - subprocess.run(['adb', 'connect', f'127.0.0.1:{port}'], - capture_output=True) - except: - continue - - time.sleep(2) - self.output_signal.emit("Device scan completed") - except Exception as e: - self.error_signal.emit(f"Error scanning devices: {str(e)}") - - def _inject_script(self): - try: - device_id = self.kwargs['device_id'] - process_id = self.kwargs['process_id'] - script_content = self.kwargs['script'] - - # Special handling for Nox - if '127.0.0.1' in device_id: - # Ensure frida-server is running - try: - subprocess.run(['adb', '-s', device_id, 'shell', 'su -c "killall -9 frida-server"'], - capture_output=True) - time.sleep(1) - subprocess.Popen(['adb', '-s', device_id, 'shell', 'su -c "/data/local/tmp/frida-server &"'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - self.output_signal.emit("Restarted frida-server on Nox") - time.sleep(3) - except Exception as e: - self.error_signal.emit(f"Error restarting frida-server: {str(e)}") - - # Try to attach multiple times - max_retries = 3 - last_error = None - - for i in range(max_retries): - try: - device = frida.get_device(device_id) - session = device.attach(int(process_id)) - script = session.create_script(script_content) - - def on_message(message, data): - if message['type'] == 'send': - self.output_signal.emit(f"Script message: {message['payload']}") - elif message['type'] == 'error': - self.error_signal.emit(f"Script error: {message['description']}") - - script.on('message', on_message) - script.load() - self.output_signal.emit("Script injected successfully") - return - except Exception as e: - last_error = str(e) - self.output_signal.emit(f"Injection attempt {i+1} failed, retrying...") - time.sleep(2) - continue - - raise Exception(f"Failed after {max_retries} attempts. Last error: {last_error}") - - except Exception as e: - self.error_signal.emit(f"Injection error: {str(e)}") - - def _launch_app(self): - try: - device_id = self.kwargs['device_id'] - package_name = self.kwargs['package_name'] - script_content = self.kwargs['script'] - - # Special handling for Nox - if '127.0.0.1' in device_id: - try: - # Setup frida-server first - if not self.setup_frida_server(device_id): - raise Exception("Failed to setup frida-server") - - # Ensure connection to Nox - subprocess.run(['adb', 'connect', device_id], capture_output=True) - time.sleep(1) - - # Start frida-server - subprocess.run(['adb', '-s', device_id, 'shell', 'su -c "killall -9 frida-server"'], - capture_output=True) - time.sleep(1) - subprocess.Popen(['adb', '-s', device_id, 'shell', 'su -c "/data/local/tmp/frida-server &"'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - self.output_signal.emit("Started frida-server on Nox") - time.sleep(3) - - # Kill existing app - subprocess.run(['adb', '-s', device_id, 'shell', 'am force-stop ' + package_name], - capture_output=True) - time.sleep(1) - - # Get device and spawn app - device = frida.get_device(device_id) - pid = device.spawn([package_name]) - self.output_signal.emit(f"Spawned app with PID: {pid}") - - # Attach to the spawned process - session = device.attach(pid) - - # Create and load script - script = session.create_script(script_content) - - def on_message(message, data): - if message['type'] == 'send': - self.output_signal.emit(f"Script message: {message['payload']}") - elif message['type'] == 'error': - self.error_signal.emit(f"Script error: {message['description']}") - - script.on('message', on_message) - script.load() - self.output_signal.emit("Script loaded") - - # Resume the app - device.resume(pid) - self.output_signal.emit("App resumed with injected script") - - except Exception as e: - raise Exception(f"Nox launch failed: {str(e)}") - - self.output_signal.emit("App launched successfully with script") - except Exception as e: - self.error_signal.emit(f"Launch error: {str(e)}") - - def download_frida_server(self): - try: - # Get device architecture - device_id = self.kwargs.get('device_id', '') - abi = subprocess.check_output( - ['adb', '-s', device_id, 'shell', 'getprop', 'ro.product.cpu.abi'], - text=True - ).strip() - - # Map Android ABI to Frida architecture - abi_to_arch = { - 'arm64-v8a': 'arm64', - 'armeabi-v7a': 'arm', - 'x86': 'x86', - 'x86_64': 'x86_64' - } - arch = abi_to_arch.get(abi, 'arm64') - - # Get latest Frida release version - response = requests.get('https://api.github.com/repos/frida/frida/releases/latest') - latest_version = response.json()['tag_name'] - - # Construct download URL - download_url = f'https://github.com/frida/frida/releases/download/{latest_version}/frida-server-{latest_version[0:]}-android-{arch}.xz' - self.output_signal.emit(f"Downloading Frida server from: {download_url}") - - # Download frida-server - response = requests.get(download_url) - response.raise_for_status() - - # Decompress XZ file - decompressed_data = lzma.decompress(response.content) - - # Save as frida-server in current directory - frida_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'frida-server') - with open(frida_path, 'wb') as f: - f.write(decompressed_data) - - # Make executable - os.chmod(frida_path, 0o755) - - self.output_signal.emit(f"Successfully downloaded frida-server {latest_version}") - return frida_path - except Exception as e: - self.error_signal.emit(f"Error downloading frida-server: {str(e)}") - return None - - def setup_frida_server(self, device_id): - try: - # Check if frida-server exists locally - frida_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'frida-server') - if not os.path.exists(frida_path): - self.output_signal.emit("Downloading frida-server...") - frida_path = self.download_frida_server() - if not frida_path: - raise Exception("Failed to download frida-server") - - # Push frida-server to device - self.output_signal.emit("Pushing frida-server to device...") - result = subprocess.run( - ['adb', '-s', device_id, 'push', frida_path, '/data/local/tmp/'], - capture_output=True, - text=True - ) - - if "error" in result.stderr.lower(): - # Try with su if normal push fails - self.output_signal.emit("Trying with root permissions...") - subprocess.run(['adb', '-s', device_id, 'shell', 'su -c "mount -o rw,remount /system"']) - subprocess.run(['adb', '-s', device_id, 'shell', 'su -c "chmod 777 /data/local/tmp"']) - subprocess.run(['adb', '-s', device_id, 'push', frida_path, '/data/local/tmp/']) - - # Set permissions - subprocess.run(['adb', '-s', device_id, 'shell', 'su -c "chmod 755 /data/local/tmp/frida-server"']) - - self.output_signal.emit("Frida server setup completed") - return True - except Exception as e: - self.error_signal.emit(f"Error setting up frida-server: {str(e)}") - return False - -class FridaGUI(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("Frida Script Injector") - self.setMinimumSize(800, 600) - - # Create main widget and layout - main_widget = QWidget() - self.setCentralWidget(main_widget) - layout = QVBoxLayout(main_widget) - - # Device selection - device_layout = QHBoxLayout() - self.device_combo = QComboBox() - scan_button = QPushButton("Scan Devices") - scan_button.clicked.connect(self.scan_devices) - device_layout.addWidget(QLabel("Select Device:")) - device_layout.addWidget(self.device_combo) - device_layout.addWidget(scan_button) - layout.addLayout(device_layout) - - # Process selection - process_layout = QHBoxLayout() - self.process_combo = QComboBox() - list_button = QPushButton("List Processes") - list_button.clicked.connect(self.list_processes) - process_layout.addWidget(QLabel("Select Process:")) - process_layout.addWidget(self.process_combo) - process_layout.addWidget(list_button) - layout.addLayout(process_layout) - - # Launch button - launch_button = QPushButton("Launch App") - launch_button.clicked.connect(self.launch_app) - layout.addWidget(launch_button) - - # Launch with inject checkbox - self.inject_on_launch = QCheckBox("Inject on Launch") - self.inject_on_launch.setChecked(True) # Enable by default - layout.addWidget(self.inject_on_launch) - - # Script editor - layout.addWidget(QLabel("Frida Script:")) - self.script_editor = QTextEdit() - self.script_editor.setPlainText('''Java.perform(function() { - console.log("Loaded!"); - // Enable SSL logging - var modules = Process.enumerateModules(); - modules.forEach(function(module) { - if (module.name.indexOf(".so") !== -1) { - console.log("Module " + module.name + " SSL logging started."); - } - }); -});''') - layout.addWidget(self.script_editor) - - # Output area - layout.addWidget(QLabel("Output:")) - self.output_area = QTextEdit() - self.output_area.setReadOnly(True) - layout.addWidget(self.output_area) - - # Initial device scan - self.scan_devices() - - def log_output(self, message): - self.output_area.append(message) - - def scan_devices(self): - self.worker = FridaWorker("scan_devices") - self.worker.output_signal.connect(self.log_output) - self.worker.error_signal.connect(self.log_output) - self.worker.finished.connect(self._update_devices) - self.worker.start() - - def _update_devices(self): - try: - self.device_combo.clear() - devices = frida.enumerate_devices() - for device in devices: - self.device_combo.addItem(f"{device.name} ({device.type})", device.id) - except Exception as e: - self.log_output(f"Error updating devices: {str(e)}") - - def list_processes(self): - try: - device_id = self.device_combo.currentData() - if not device_id: - raise Exception("Please select a device first") - - device = frida.get_device(device_id) - - # Get installed packages instead of processes - packages = [] - adb_output = subprocess.check_output( - ['adb', '-s', device_id, 'shell', 'pm', 'list', 'packages', '-f'], - text=True - ).strip().split('\n') - - self.process_combo.clear() - for line in adb_output: - if line: - # Extract package name from line - package = line.split('=')[-1] - # Get app name using aapt - try: - app_path = line.split(':')[1].split('=')[0] - aapt_output = subprocess.check_output( - ['adb', '-s', device_id, 'shell', 'dumpsys', 'package', package], - text=True - ) - app_name = package # Default to package name - for line in aapt_output.split('\n'): - if 'applicationInfo' in line and 'label=' in line: - app_name = line.split('label=')[1].split(' ')[0] - break - - self.process_combo.addItem(f"{app_name} ({package})", package) - except: - # If we can't get the app name, just show package name - self.process_combo.addItem(package, package) - - self.log_output("Applications listed successfully") - except Exception as e: - self.log_output(f"Error listing applications: {str(e)}") - - def launch_app(self): - try: - device_id = self.device_combo.currentData() - package_name = self.process_combo.currentData() - script = self.script_editor.toPlainText() if self.inject_on_launch.isChecked() else "" - - if not device_id or not package_name: - raise Exception("Please select device and application first") - - if self.inject_on_launch.isChecked() and not script: - raise Exception("Please provide a script to inject") - - self.worker = FridaWorker("launch", - device_id=device_id, - package_name=package_name, - script=script) - self.worker.output_signal.connect(self.log_output) - self.worker.error_signal.connect(self.log_output) - self.worker.start() - except Exception as e: - self.log_output(f"Error launching app: {str(e)}") - -def main(): - app = QApplication(sys.argv) - window = FridaGUI() - window.show() - sys.exit(app.exec()) - -if __name__ == '__main__': - main() diff --git a/requirements.txt b/requirements.txt index 1821c8b..b8374a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ -frida-tools>=11.0.0 -frida>=16.0.19 -PyQt5==5.15.9 -requests>=2.26.0 \ No newline at end of file +frida +PyQt5 +qtawesome +requests +beautifulsoup4 +psutil +aiohttp \ No newline at end of file diff --git a/src/core/__pycache__/android_helper.cpython-311.pyc b/src/core/__pycache__/android_helper.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a9ae0dec867d7cbcad6a22ad792b5839bead768 GIT binary patch literal 7719 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 L^ye?&Y9shxAqP0Z literal 0 HcmV?d00001 diff --git a/src/core/__pycache__/history_manager.cpython-311.pyc b/src/core/__pycache__/history_manager.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..104c0d6a0fd4ac8ed2535d69915ab7593dc47db4 GIT binary patch literal 3422 zcma)8Uu@gP8NVYbiL|N6Qsc_16<0x=R%#NYG zYOo{uyYIfc`*?TX_q)38vhB=gbj|6hwyo6n%B5@NiOsQ7IV}QHPzBBqoPlh+AZ9SwhQ~`LAXzJ zf|jQ_jebJV0-%7_r$d?m7}i375iJasBhHC84b!Tz+ka52>a5O6zAZEvf{%x9|33it zi3RAVxuD&8Fj~J3^Pg#p){Z5OZh*Cifklne_zh_JFlfC*6n{0z5?RM_9ZMUac^GJ+ zN7yfD1ZFBsJ*9US`^ILNp{dI3ys;>M=*@g=&a72oCAgaD2zAx^&=KZpM%C%lZ`IYR zHgD?8@!KjhMp<{{a!u8gc6TSz79LPXF!l0`i4(~@y_SC;dYbuK+4u-@ORe5C^aZ1| znEx#f;rjc(%bTo}FV&cyZ(C3*o)gCEw;fSYjH+QN%3|`(E)R|Q6*xchb%15EmmXQW z{>59Lz4foeqvVs^t2?<@zy3`#_nMu1?VIzBxBt{kD|T905o|HFn;d?U%Y%j6EV z`u&!K%mofzIubA?4u}L#cYg|QKSK({6c)H!{0C%#Qt|Dr;{HiK=eoy^{WWrjyUSHYFJGVw#Irq%LB>xxlr1LTV64V67$in@Hq&Sf4#e^*(3R8Y}}5Gw+I)_?hU3^ zN9`BRZNA$aEZBpE7NPO%<9GIkUtF75f9KPyYgb!5Xk7In{D`f7LO9g*k`4|IdaJ*W z4MBy3ziY#u9B(A@n?(S4oAS6Vk2j9JVDJD2Bo!e4HWpuz%quX-k0***^6!(yG`|&- zLEK6U#Y|)?!=rqLqCD!#*}lmW{MIiLlM=rzg+T5d|9|%5A9>wQhudG*Mi+2lJ&1n4 z2j&p%!8X<%SsH!B9gW`3*zfEKo8xhwU<|a=T*Q~47Y3Z0mZ&B?3UzO*?m^UpH@IQ; zW{y{5C48%d0C!FScLonNTKL)AK^sTelZ#Bfhv%D13Z-__p$k9YVuCJgR5iV7Ek5tD z%2YqJ3G-Fx?&Gq`R2Wbe4{Dfu;m*d z?8OKFuG!DOy!l)+e!-4kfXI(tejMK$IQb80b#(3gCVhOmF>t9laLFFH1Oh~P^s?K? z1z=qCGBB=FcUXHw@@V&rrUz~Q!5%;*4_y9@Z5A4dF?gGD-j?%?UawEVaS%+r=zLs^ zPKL==>{M}r-#Xuq@`O;l7TLPSgS<^B$lKJF!+nz%`0atjWR~B~hCnWLvId!g<^8V! z;PTOveH&1BcH3alvw@sV^oC)5c;7F>bDQ?B$(2=7dsKMYj{|K;5zE7QAOMg1DHu5A@ z*hv+dsf%{%;_k^8catNnh!F2<5rCCYtB*+0d-4-;ct;#wEw0Wt#k00}wjrL~6+xTY z5mQZZ*cOKy{>zXcT^lRT+I6gVFP?=ZRY2t>xZQO)+Sd}&?Zo!-(y1KhaTyng#Q?m^ zmwPTc(NbAgS-|4-k!9$r9mT$<0hY;bGKIIIXHIA;C*lnacNj%+`V^&7)8@-4%Zl=s zdA01-Fx)8J^VxMFD`4>=zysp0i+6tGb(SIEXX08c2TzORLg(OY_Cbt<5-k!yA{+w3 z1~3o_rLndR2@a*>;Qbr_20?|caQ8;xdVJEUiGH;YxXP9p?}<{flZ0h jU2>|?{qBLnm8Z6(xfIyuN8MJ>h9WIRbQQ5 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 E?OpM;+?Rf!UAks6%`XQ3)h91@h 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 S`_IDFPnEa7eo7=ZME^f1#2GCB literal 0 HcmV?d00001 diff --git a/src/gui/main_window.py b/src/gui/main_window.py new file mode 100644 index 0000000..caf57e9 --- /dev/null +++ b/src/gui/main_window.py @@ -0,0 +1,785 @@ +from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QPushButton, QStackedWidget, QLabel, QListWidget, QTableWidget, QGroupBox, QCheckBox, QSpinBox, QMessageBox, QScrollArea, QGridLayout, QLineEdit, QTextEdit, QFrame, QDialog, QFileDialog) +from PyQt5.QtCore import Qt, QSize +import qtawesome as qta +from .widgets.device_panel import DevicePanel +from .widgets.process_panel import ProcessPanel +from .widgets.script_editor import ScriptEditorPanel +from .widgets.output_panel import OutputPanel +from .widgets.codeshare_browser import CodeShareBrowser +from .widgets.app_launcher import AppLauncher +from .widgets.process_monitor import ProcessMonitor +from .widgets.injection_panel import InjectionPanel +from .widgets.device_selector import DeviceSelector +from .widgets.history_page import HistoryPage +from core.history_manager import HistoryManager +from core.android_helper import AndroidHelper +import frida +import subprocess +import os +import json +import requests + +class FridaInjectorMainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Oliver Stankiewicz's | Frida Script Manager") + self.setMinimumSize(1400, 800) + self.history_manager = HistoryManager() + self.favorites = [] # Initialize favorites list + self.load_favorites() # Load favorites on startup + self.setup_ui() + + # Connect codeshare and favorites browsers + self.codeshare_browser.favorites_updated.connect(self.refresh_favorites) + + def setup_ui(self): + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Main horizontal layout + layout = QHBoxLayout(central_widget) + + # Left sidebar for navigation + sidebar = self.create_sidebar() + layout.addWidget(sidebar) + + # Stacked widget for main content + self.stack = QStackedWidget() + layout.addWidget(self.stack) + + # Set layout ratio (1:4) + layout.setStretch(0, 1) + layout.setStretch(1, 4) + + # Initialize pages + self.init_pages() + + def create_sidebar(self): + sidebar = QWidget() + sidebar.setObjectName("sidebar") + sidebar.setStyleSheet(""" + QWidget#sidebar { + background-color: #2f3136; + border-right: 1px solid #202225; + min-width: 180px; + max-width: 180px; + } + QPushButton { + text-align: left; + padding: 6px 8px; + border: none; + border-radius: 4px; + margin: 1px 4px; + min-height: 32px; + max-height: 32px; + font-size: 13px; + } + QPushButton:hover { + background-color: #36393f; + } + QPushButton:checked { + background-color: #404249; + } + """) + + layout = QVBoxLayout(sidebar) + layout.setSpacing(1) + layout.setContentsMargins(0, 5, 0, 5) + + # Add navigation buttons + self.nav_buttons = {} + + nav_items = [ + ("home", "Home", "fa5s.home"), + ("inject", "Script Injection", "fa5s.syringe"), + ("codeshare", "CodeShare", "fa5s.cloud-download-alt"), + ("favorites", "Favorites", "fa5s.star"), + ("history", "History", "fa5s.history"), + ("monitor", "Process Monitor", "fa5s.desktop"), + ("settings", "Settings", "fa5s.cog") + ] + + for id_, text, icon in nav_items: + btn = QPushButton(qta.icon(icon, color='#b9bbbe'), f" {text}") + btn.setCheckable(True) + btn.clicked.connect(lambda checked, x=id_: self.switch_page(x)) + # Set icon size + btn.setIconSize(QSize(14, 14)) + self.nav_buttons[id_] = btn + layout.addWidget(btn) + + layout.addStretch() + + # Add status indicator at bottom + status_layout = QHBoxLayout() + status_layout.setContentsMargins(8, 4, 8, 4) + self.status_icon = QLabel() + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#43b581').pixmap(8, 8)) + self.status_text = QLabel("Ready") + self.status_text.setStyleSheet("color: #b9bbbe; font-size: 12px;") + status_layout.addWidget(self.status_icon) + status_layout.addWidget(self.status_text) + layout.addLayout(status_layout) + + return sidebar + + def init_pages(self): + # Create pages + self.pages = { + 'home': self.create_home_page(), + 'inject': self.create_injection_page(), + 'codeshare': self.create_codeshare_page(), + 'favorites': self.create_favorites_page(), + 'history': self.create_history_page(), + 'monitor': self.create_monitor_page(), + 'settings': self.create_settings_page() + } + + # Add pages to stack + for page in self.pages.values(): + self.stack.addWidget(page) + + # Set initial page + self.switch_page('home') + + def switch_page(self, page_id): + # Update button states + for btn in self.nav_buttons.values(): + btn.setChecked(False) + self.nav_buttons[page_id].setChecked(True) + + # Switch to page + self.stack.setCurrentWidget(self.pages[page_id]) + + def create_home_page(self): + page = QWidget() + layout = QVBoxLayout(page) + layout.setSpacing(20) + + # Welcome header + header = QFrame() + header.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 10px; + padding: 20px; + } + QLabel { + color: white; + } + """) + header_layout = QVBoxLayout(header) + + title = QLabel("Welcome to Frida Script Manager") + title.setStyleSheet("font-size: 24px; font-weight: bold;") + + subtitle = QLabel("A powerful GUI tool for Frida script management and injection") + subtitle.setStyleSheet("font-size: 16px; color: #b9bbbe;") + + author = QLabel("Created by Oliver Stankiewicz") + author.setStyleSheet("font-size: 14px; color: #7289da;") + + header_layout.addWidget(title) + header_layout.addWidget(subtitle) + header_layout.addWidget(author) + + # Quick actions section + actions = QFrame() + actions.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 10px; + padding: 20px; + } + QLabel { + color: white; + } + QPushButton { + background-color: #7289da; + border-radius: 5px; + padding: 10px; + color: white; + text-align: left; + font-size: 14px; + } + QPushButton:hover { + background-color: #677bc4; + } + """) + actions_layout = QVBoxLayout(actions) + + actions_title = QLabel("Quick Actions") + actions_title.setStyleSheet("font-size: 18px; font-weight: bold; margin-bottom: 10px;") + + # Create action buttons + inject_btn = QPushButton(qta.icon('fa5s.syringe'), " Script Injection") + inject_btn.clicked.connect(lambda: self.switch_page('inject')) + + browse_btn = QPushButton(qta.icon('fa5s.cloud-download-alt'), " Browse CodeShare") + browse_btn.clicked.connect(lambda: self.switch_page('codeshare')) + + favorites_btn = QPushButton(qta.icon('fa5s.star'), " View Favorites") + favorites_btn.clicked.connect(lambda: self.switch_page('favorites')) + + monitor_btn = QPushButton(qta.icon('fa5s.desktop'), " Process Monitor") + monitor_btn.clicked.connect(lambda: self.switch_page('monitor')) + + actions_layout.addWidget(actions_title) + actions_layout.addWidget(inject_btn) + actions_layout.addWidget(browse_btn) + actions_layout.addWidget(favorites_btn) + actions_layout.addWidget(monitor_btn) + + # Add sections to main layout + layout.addWidget(header) + layout.addWidget(actions) + layout.addStretch() + + return page + + def create_injection_page(self): + page = QWidget() + layout = QVBoxLayout(page) + + # Add device selector + self.device_selector = DeviceSelector() + self.script_editor = ScriptEditorPanel() + self.injection_panel = InjectionPanel() + self.injection_panel.script_editor = self.script_editor + self.output_panel = OutputPanel() + + layout.addWidget(self.device_selector) + layout.addWidget(self.script_editor) + layout.addWidget(self.injection_panel) + layout.addWidget(self.output_panel) + + # Connect signals - ensure we're passing both device_id and pid + self.device_selector.process_selected.connect( + lambda device_id, pid: self.injection_panel.set_process(device_id, pid) + ) + self.injection_panel.injection_started.connect(self.inject_script) + self.injection_panel.injection_stopped.connect(self.stop_injection) + + return page + + def create_codeshare_page(self): + page = QWidget() + layout = QVBoxLayout(page) + + self.codeshare_browser = CodeShareBrowser() + # Connect codeshare signals here, after creating the browser + self.codeshare_browser.open_in_injector.connect(self.open_script_in_injector) + layout.addWidget(self.codeshare_browser) + + return page + + def create_favorites_page(self): + page = QWidget() + layout = QVBoxLayout(page) + + # Toolbar + toolbar = QHBoxLayout() + + # Search bar + search_input = QLineEdit() + search_input.setPlaceholderText("โŒ• Search favorites...") + search_input.textChanged.connect(self.filter_favorites) + + # Upload button + upload_btn = QPushButton(qta.icon('fa5s.file-upload'), "Upload Script") + upload_btn.clicked.connect(self.upload_script) + + toolbar.addWidget(search_input) + toolbar.addWidget(upload_btn) + + # Grid for favorite scripts + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setStyleSheet(""" + QScrollArea { + border: none; + background-color: #36393f; + } + """) + + self.favorites_grid = QWidget() + self.favorites_grid_layout = QGridLayout(self.favorites_grid) + self.favorites_grid_layout.setSpacing(10) + scroll.setWidget(self.favorites_grid) + + # Add components to layout + layout.addLayout(toolbar) + layout.addWidget(scroll) + + # Initial population + self.refresh_favorites() + + return page + + def filter_favorites(self, text): + """Filter favorite scripts by search text""" + search_text = text.lower() + for i in range(self.favorites_grid_layout.count()): + widget = self.favorites_grid_layout.itemAt(i).widget() + if widget: + title = widget.findChild(QLabel).text().lower() + desc = widget.findChildren(QLabel)[-2].text().lower() + widget.setVisible(search_text in title or search_text in desc) + + def upload_script(self): + """Upload a custom script to favorites""" + file_path, _ = QFileDialog.getOpenFileName( + self, + "Upload Script", + "", + "JavaScript Files (*.js);;All Files (*.*)" + ) + + if file_path: + try: + with open(file_path, 'r') as f: + script_content = f.read() + + # Create script info + script_name = os.path.basename(file_path) + script_info = { + 'id': f"custom/{script_name}", + 'title': script_name, + 'author': 'Custom Script', + 'description': 'Uploaded custom script', + 'likes': 0, + 'seen': 0, + 'content': script_content + } + + # Add to favorites + self.add_to_favorites(script_info) + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to upload script: {str(e)}") + + def add_to_favorites(self, script_info): + """Add a script to favorites""" + # Add to favorites list if not already present + if not any(s['id'] == script_info['id'] for s in self.favorites): + self.favorites.append(script_info) + self.save_favorites() + + # Create card widget + card = self.create_favorite_card(script_info) + + # Add to grid + count = self.favorites_grid_layout.count() + row = count // 3 + col = count % 3 + self.favorites_grid_layout.addWidget(card, row, col) + + def create_favorite_card(self, script_info): + """Create a card widget for a favorite script""" + card = QFrame() + card.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 8px; + padding: 10px; + } + QFrame:hover { + background-color: #40444b; + } + """) + + layout = QVBoxLayout(card) + + # Title and metadata + title = QLabel(script_info['title']) + title.setStyleSheet("font-size: 14px; font-weight: bold; color: white;") + author = QLabel(f"by {script_info['author']}") + author.setStyleSheet("color: #b9bbbe;") + + # Description + desc = QLabel(script_info.get('description', '')[:100] + '...') + desc.setWordWrap(True) + desc.setStyleSheet("color: #b9bbbe;") + + # Action buttons + buttons = QHBoxLayout() + + view_btn = QPushButton("View") + view_btn.clicked.connect(lambda: self.view_favorite(script_info)) + + inject_btn = QPushButton("โšก Inject") + inject_btn.clicked.connect(lambda: self.open_script_in_injector(script_info.get('content', ''))) + + remove_btn = QPushButton("โœ• Remove") + remove_btn.clicked.connect(lambda: self.remove_from_favorites(script_info, card)) + + buttons.addWidget(view_btn) + buttons.addWidget(inject_btn) + buttons.addWidget(remove_btn) + buttons.addStretch() + + # Add all components + layout.addWidget(title) + layout.addWidget(author) + layout.addWidget(desc) + layout.addLayout(buttons) + + return card + + def view_favorite(self, script_info): + """View a favorite script's details""" + dialog = QDialog(self) + dialog.setWindowTitle(f"View Script - {script_info['title']}") + dialog.resize(800, 600) + + layout = QVBoxLayout(dialog) + + # Script content + content = QTextEdit() + content.setReadOnly(True) + content.setFont(QFont('Consolas', 11)) + content.setText(script_info.get('content', 'Script content not available')) + + # Action buttons + buttons = QHBoxLayout() + + copy_btn = QPushButton(" Copy") + copy_btn.clicked.connect(lambda: self.copy_to_clipboard(content.toPlainText())) + + inject_btn = QPushButton("โšก Inject") + inject_btn.clicked.connect(lambda: self.open_script_in_injector(content.toPlainText())) + + buttons.addWidget(copy_btn) + buttons.addWidget(inject_btn) + buttons.addStretch() + + layout.addWidget(content) + layout.addLayout(buttons) + + dialog.exec_() + + def remove_from_favorites(self, script_info, card): + """Remove a script from favorites""" + reply = QMessageBox.question( + self, + "Remove Favorite", + f"Remove {script_info['title']} from favorites?", + QMessageBox.Yes | QMessageBox.No + ) + + if reply == QMessageBox.Yes: + # Remove from grid + card.setParent(None) + + # Remove from favorites list + if script_info['id'].startswith('custom/'): + self.favorites = [s for s in self.favorites if s['id'] != script_info['id']] + self.save_favorites() + elif hasattr(self.codeshare_browser, 'favorites'): + self.codeshare_browser.favorites.remove(script_info['id']) + self.codeshare_browser.save_favorites() + + # Refresh display + self.refresh_favorites() + + def copy_to_clipboard(self, text): + """Copy text to clipboard""" + QApplication.clipboard().setText(text) + QMessageBox.information(self, "โœ“ Success", "๐Ÿ“‹ Copied to clipboard!") + + def create_history_page(self): + page = QWidget() + layout = QVBoxLayout(page) + + self.history_page = HistoryPage(self.history_manager) + self.history_page.script_selected.connect(self.open_script_in_injector) + layout.addWidget(self.history_page) + + return page + + def create_monitor_page(self): + page = QWidget() + layout = QVBoxLayout(page) + + # Pass self (main window) to ProcessMonitor + self.process_monitor = ProcessMonitor(main_window=self) + layout.addWidget(self.process_monitor) + + return page + + def create_settings_page(self): + page = QWidget() + layout = QVBoxLayout(page) + + # Add settings categories + settings_categories = [ + ("General", [ + ("Auto-inject on launch", "checkbox"), + ("Save script history", "checkbox"), + ("Dark theme", "checkbox") + ]), + ("Script Editor", [ + ("Font size", "spinbox"), + ("Show line numbers", "checkbox"), + ("Auto-completion", "checkbox") + ]), + ("Monitoring", [ + ("Update interval", "spinbox"), + ("Show memory usage", "checkbox"), + ("Log to file", "checkbox") + ]) + ] + + for category, settings in settings_categories: + group = QGroupBox(category) + group_layout = QVBoxLayout() + + for setting_name, setting_type in settings: + setting_layout = QHBoxLayout() + setting_layout.addWidget(QLabel(setting_name)) + + if setting_type == "checkbox": + widget = QCheckBox() + elif setting_type == "spinbox": + widget = QSpinBox() + + setting_layout.addWidget(widget) + group_layout.addLayout(setting_layout) + + group.setLayout(group_layout) + layout.addWidget(group) + + layout.addStretch() + + return page + + def on_process_started(self, name, pid): + self.status_text.setText(f"Process started: {name} ({pid})") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#43b581').pixmap(12, 12)) + + def on_process_ended(self, name, pid): + self.status_text.setText(f"Process ended: {name} ({pid})") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#f04747').pixmap(12, 12)) + + def on_memory_updated(self, pid, memory_mb): + # Update memory usage in process monitor + pass + + def inject_script(self, script_content, pid): + """Inject script into process""" + try: + if not script_content: + QMessageBox.warning(self, "Error", "No script to inject!") + return + + # Update status + self.status_text.setText(f"Injecting into PID: {pid}") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#faa61a').pixmap(12, 12)) + + # Get device and process info + device_id = self.device_selector.current_device + process_info = self.device_selector.get_selected_process_info() + + if not process_info: + raise Exception("No process selected") + + device = frida.get_device(device_id) + + # Check if Android device needs frida-server + if device.type == 'usb': + if not AndroidHelper.is_frida_running(device_id): + self.output_panel.append_output("[*] Starting frida-server on device...") + if not AndroidHelper.start_frida_server(device_id): + raise Exception("Failed to start frida-server") + self.output_panel.append_output("[+] frida-server started") + # Re-get device after starting server + device = frida.get_device(device_id) + + try: + # Try to attach first + session = device.attach(pid) + self.output_panel.append_output(f"[+] Successfully attached to PID: {pid}") + except frida.ProcessNotFoundError: + # If attach fails, try to spawn + try: + if device.type == 'local': + # For local processes, use executable path + import psutil + process = psutil.Process(pid) + executable = process.exe() + pid = device.spawn([executable]) + self.output_panel.append_output(f"[+] Spawned process with PID: {pid}") + else: + # For Android/remote devices + if device.type == 'usb': + package_name = process_info['name'] + pid = device.spawn([package_name]) + self.output_panel.append_output(f"[+] Spawned Android app: {package_name}") + else: + pid = device.spawn([process_info['name']]) + + session = device.attach(pid) + device.resume(pid) + except Exception as e: + raise Exception(f"Failed to spawn process: {str(e)}") + + # Create and load script + script = session.create_script(script_content) + + def on_message(message, data): + if message['type'] == 'send': + self.output_panel.append_output(f"[*] {message['payload']}") + elif message['type'] == 'error': + self.output_panel.append_output(f"[!] {message['description']}") + + script.on('message', on_message) + script.load() + + # Update status on success + self.status_text.setText(f"Successfully injected into PID: {pid}") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#43b581').pixmap(12, 12)) + + # Store session and script + self.current_session = session + self.current_script = script + + # Show success message + self.output_panel.append_output(f"[+] Script loaded successfully") + + # Add history entry + self.history_manager.add_entry('script_injection', { + 'script': script_content, + 'pid': pid, + 'device': device_id, + 'status': 'success' + }) + + except Exception as e: + error_msg = f"Injection failed: {str(e)}" + self.output_panel.append_output(f"[-] {error_msg}") + QMessageBox.critical(self, "Error", error_msg) + + # Add history entry + self.history_manager.add_entry('script_injection', { + 'script': script_content, + 'pid': pid, + 'device': device_id, + 'status': 'failed', + 'error': str(e) + }) + + finally: + if hasattr(self, 'injection_panel'): + self.injection_panel.reset_ui() + + def stop_injection(self): + """Stop the current injection""" + try: + if hasattr(self, 'current_script') and self.current_script: + self.current_script.unload() + if hasattr(self, 'current_session') and self.current_session: + self.current_session.detach() + + self.current_script = None + self.current_session = None + + self.output_panel.append_output("[*] Script injection stopped") + self.status_text.setText("Ready") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#43b581').pixmap(12, 12)) + + except Exception as e: + error_msg = f"Error stopping injection: {str(e)}" + self.output_panel.append_output(f"[-] {error_msg}") + QMessageBox.critical(self, "Error", error_msg) + + def on_process_selected(self, device_id, pid): + self.current_device = device_id + self.current_pid = pid + self.status_text.setText(f"Selected PID: {pid} on device: {device_id}") + + def open_in_injector(self, device_id, pid): + """Open the selected process in the injector tab""" + # Switch to injector tab + self.switch_page('inject') + + # Select the device and process + self.device_selector.select_device(device_id) + self.device_selector.select_process(pid) + + def open_script_in_injector(self, code): + """Open a script in the injector page""" + # Switch to injector page + self.switch_page('inject') + + # Set the script content + self.script_editor.set_script(code) + + def load_favorites(self): + """Load favorites from file""" + try: + favorites_file = os.path.join(os.path.expanduser('~'), '.frida_gui', 'favorites.json') + if os.path.exists(favorites_file): + with open(favorites_file, 'r') as f: + data = json.load(f) + self.favorites = data.get('scripts', []) + except Exception as e: + print(f"Error loading favorites: {e}") + self.favorites = [] + + def save_favorites(self): + """Save favorites to file""" + try: + favorites_file = os.path.join(os.path.expanduser('~'), '.frida_gui', 'favorites.json') + os.makedirs(os.path.dirname(favorites_file), exist_ok=True) + with open(favorites_file, 'w') as f: + json.dump({'scripts': self.favorites}, f) + except Exception as e: + print(f"Error saving favorites: {e}") + + def refresh_favorites(self): + """Refresh the favorites page""" + # Clear existing grid + for i in reversed(range(self.favorites_grid_layout.count())): + widget = self.favorites_grid_layout.itemAt(i).widget() + if widget: + widget.setParent(None) + + # Get all favorites + try: + # Combine CodeShare favorites and custom scripts + all_favorites = [] + + # Add CodeShare favorites + if hasattr(self.codeshare_browser, 'favorites'): + response = requests.get(self.codeshare_browser.api_url) + codeshare_scripts = response.json() + for script in codeshare_scripts: + if script['id'] in self.codeshare_browser.favorites: + all_favorites.append(script) + + # Add custom scripts from our favorites + all_favorites.extend([s for s in self.favorites if s['id'].startswith('custom/')]) + + if all_favorites: + # Add scripts to grid + for idx, script_info in enumerate(all_favorites): + row = idx // 3 + col = idx % 3 + card = self.create_favorite_card(script_info) + self.favorites_grid_layout.addWidget(card, row, col) + else: + # Show message if no favorites + msg = QLabel("No favorite scripts yet.\nBrowse scripts and click the โ˜… to add favorites!") + msg.setAlignment(Qt.AlignCenter) + msg.setStyleSheet(""" + color: #b9bbbe; + font-size: 14px; + padding: 20px; + """) + self.favorites_grid_layout.addWidget(msg, 0, 0, 1, 3) + + except Exception as e: + error_msg = QLabel(f"Error loading favorites: {str(e)}") + error_msg.setStyleSheet("color: #ff4444;") + self.favorites_grid_layout.addWidget(error_msg, 0, 0, 1, 3) \ No newline at end of file diff --git a/src/gui/widgets/__init__.py b/src/gui/widgets/__init__.py new file mode 100644 index 0000000..166ce0b --- /dev/null +++ b/src/gui/widgets/__init__.py @@ -0,0 +1 @@ +# Empty file to make the directory a Python package \ No newline at end of file diff --git a/src/gui/widgets/__pycache__/__init__.cpython-311.pyc b/src/gui/widgets/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cb549ed03f932c199abb225b98b1ceef3f2695b GIT binary patch literal 170 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 literal 0 HcmV?d00001 diff --git a/src/gui/widgets/__pycache__/codeshare_browser.cpython-311.pyc b/src/gui/widgets/__pycache__/codeshare_browser.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27b82b23a87a30ce3e48a28deaf000e4c626cb6e GIT binary patch literal 33294 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 literal 0 HcmV?d00001 diff --git a/src/gui/widgets/__pycache__/device_selector.cpython-311.pyc b/src/gui/widgets/__pycache__/device_selector.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..869a4fd2cc8800bdc6371ff728e021e288f9b009 GIT binary patch literal 16942 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 literal 0 HcmV?d00001 diff --git a/src/gui/widgets/__pycache__/output_panel.cpython-311.pyc b/src/gui/widgets/__pycache__/output_panel.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..108fb6615f9af6c33e36ab451bc52519ff4ca71e GIT binary patch literal 1908 zcma)6&2Jk;6rWkI?MWRsB+9`x(3mO(ZGvnrAVd%XBq)(k64SI5zKm9$X}YlfV0KMD zWTYG_;i3w-pcILNPeE0V{3EtysntqIl@LhWT!jPbiT8HBn{|>(%Z|2u* z_5y+N+xP2RpKFBtftzq7$BW~)U~xbM5!5C%TA?+qqSX?W1SJ}|Muc{kh=inv2||wG z3r0m3`gLL?|HiG6@Y3a5me`W6mnq-;u(9*0x!Y*D-c)&0?zkTb%dL*}{0SoQ$MG95 zeG9%NsZc>GT8G@Gg5G771bPJ@37|=#15I_v4y~lorEZw#tZw;J}BLAq;g6P+xi-2*Q|eO$!)8;SNd34 z!n}54wdAO3X{%+GwtbjE)Q5mK5rblLS7pG`-h2$0ViUg@{soIkT-)Rjusn+J{D!(HIUbWp zwF!^#Y?CjdbvLFFY7%J3Ny}J|m<}d(W(e(!4(*1mJ|P{qby^4OX(ZM*_woUvg>B2W z3udz^O;xxfl`IyEMoM7>3XOZ2aXm~WO)mo_|4f=9P%bFkU4&8oc9u!U53AiOD4 zh~T^!n#!Vn%Jxr8&=3VRgNCPpOqMikB?cU43kXzn_N51#{n;zS*(;sYpD!-{yzK zstrwJP`}O&`U=YHP5X>6nOr=XIA zzNmXgvoI|u1(Je;%e$b3*6h(sQI1&h4 zC*d>Fv}A5X&LJ$FTpp2#%+chlBNCAmO}>KL=Mz6K-|re+{4*Q7W%tcu*$oblqcE70 zQ|QY*B!Lbdp=a#f0E73`B5W}o=N6Drf>QdJtn{A$j`TSSVVxyEzV;OV{&Nn?cl;ly C{FM{{ literal 0 HcmV?d00001 diff --git a/src/gui/widgets/__pycache__/process_monitor.cpython-311.pyc b/src/gui/widgets/__pycache__/process_monitor.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f50df96e6d52025a25f74d741fd02b0268fa4d4 GIT binary patch literal 21326 zcmd^nX>c27mRRHD2NEC&0)%*gBnY06L`oD7iIhZ%A|+85N2g`O{-oJ_{q=Xe?|$FA8o#vJN-4Ph;RoNF{_y}s{U>};E|VPi=D$PW zU5ch?U6_jK{JMzVua6k~hKSK`jF|kUh}mzBl=w>`#7`m?za>)YFO8J>%OY04HDdGI zBIW+_NQJ*5Qt7XZ*!}iMmA^`d`1D~%#OZfNs{Pdwm){lH;@=|18^Se_T7PY%&R-X) z_t%SY#<1J(hBC%*gTFz1Hu@XIXOrIpPgA%#((G@R^LqVWJ$00#&975*NwD%428#L& z{-w*`BBl{YvjlYoX|1&J9OWy0h*^DALdltnA$mHP5X#OxcPMuKWZ*_@4qwLeUrx`( zuN<07Bw|s)eCA}}QZOu7&YTQIgNNx*La?4WAGj0_%6KaC-;O1M5y5okR4_URHH}Oq zLV&_@W;7PL6oU#dcqKS>6@%t8N0>k)h*bvzbdY&26ubtJ=Mp!XoH?Qa@>K)y?)Vv{;mz)d!0)n5Zue9zJ zig_)ePBew(l%}bTLOZUB`#Qd3R$7abkytd8h%w!>H<&K$RP8#8 zv|DUb1tiB)03P3@vSpP&+4ttYH->KwFF(hYwZMJfS-af!!PfV--rjy^d#0{CUDv%5 zUp>Ut?dI!tXX*~5>kiyA-n+!r9pmecan9qs^Z4T7Y=v`)c{90~yzkqwYT?npd1tS z0Dv)2{?_y*rtURrbAahrP)$KVy_CK7&GQhIBO`PMon z1;D#hLfy&~HKT^-DeX4YQ6XxcnpE-7_=Z@-3pN@xsgk&PDy438B?msWTUG}cQnrDoje@$Uh+N!=$E zJgHfBa-@+bjVx)**48f?Zjx_P4 ziB)cK*-WXE02MrCvb4hl6QGux&zr~@b;{PK?~qrWxWXyV$Fu&F(z?PjB6ZbIgjk@+ zJ~Oig`XE^6X7OB|#DkR)O7MYvMhP6c(cn}?~LOd)WWa>R zbe6WPwUW0sWUSt_)yr91d21`H+-n9%7c&))4eow%Y0=w42^aS7WDJ_ z^%hESR5CM~P;=loXUC2_zuvwhq>dT}gqFP{XMzB{u&_ z7qkh_yj+h3A* zS$>;myh@1;$G=6%QHUph_ZCnO%O5~}OSPvpjB=||GqhL@Z-OGXDdmb&3u@TNZN>J2ZT`KRS$hCOhQk0WQ zsr*DPUm*pO)h87b$@fY{t%M9D{hjAkO(dq})FEAQI|cvPXo(+R4Y*%*_Edzj#K@VZYhofV&E!7vgI3r5tw2>|JMuyYJ#suR8Xep8=$m3}f%u)ux zmIx%~;>=mdD3rhplL*pDE9gigfhg@kHLFk}cR}eEF!+M;L?|3iTIGH+FeJezmEW5U zMm?dZ=U8+G#HtwM+s>ev!k|h=uz?UP>Qm2M2?i4ka=HvoxL}n;=E*74%L)!zkUoa$ zsL?9{Kuj}7F`qpam8I*+DLLMZ@rIW{31$p|Fb5J{I5c%NNDCE;L?_{n3l^9+Nk_~e zKh5C42-bKoz)W2MaefxW!76CrX;h?LiG^VuoDW_HRdfQN+zP=Kn4JyZn7k}bTbw}- zS11!_3K|58WRNUl)6-$7-YVshyF{RF)E&o#D!FrrTj?8WECrjq2qqJ#%ofU_DWD++ zRo^Hm@)ANVypPA2P!hDofv}{a7B%5aLxagwVr@00K5poDTN_9mtca4sVm~tNSc3NsVHs>=CEW4P*%oe zQ8#as*NL3yP}(L>u~?%r@Nx-x8srj{@^oVtK|2F`3D%^z0OSm|fM_F_lwRX#yphQZ zJS4TuA&4td%fJ+f>K&xa@l&wmL6)_%@tHt(RIaS-5I?2`a@HIV)wg>LE+b8av(5Mc&GNeCE`d4Xw;3P** z@#GXsPCaP!{i@~j&egr#)&u<31MJ9IuJIh-c#Z}252@!#ONMMqlWi+w{Pvu_;cHSY zDH}dEr*3eb9^TW#kzG95#gbiFB`sxKH=6bIa-@$ZeJttw13L%BKr^!E&K{Q3+^=b2 zTl=`0e!ixEy>4g7_B7efcAe(P8J?VB$r(kH(v~4zY0|Yq^WC|gJ*aO@UF7P!`1&qI z^#uwro_I2(Gfg^IoV;%@NA~e#A4~SB!>~bDF?uIQdU(>qk{)PvLwmubNb||IOiMRM zuJGgvORgww+@2wQY0|fPp6@@(k#U}kvt(SYmd(Sy!LUQ6jazU3;LZ=QH+3S~I$49% z%#rOp3=P??L}OKJlzyNz2Od#+OWC&wAPzt&Ww8OG4B3$;JAR|*caCx7Fi#G%ZiuTN=j%l+ z7it;{hx-6W4)Ww6OAbD$Y592cqw$p&xz<6xb@1*0S96H3IRqL@8iVUeqgWlE6 zk#?T6v!p#s%AXSF0n6*BDH0RJ52zktjYy#F5JcXEj;!_{nisu zxq|tQnt=%ENdr)SHJd zS3ivkCiCy9DXgifM6SuVi9V5nnk>Jk zrk|r0wS`JlD&s&=trXL+V91vg>C$<_`_#{hxbqjlT)1F(&44(h&^6sP>bl_t>YC11 zHX#`1;+N)d?AJc{M}O%_nmwHt&K>dzW(N4bL{Lt-wd6@!Je_C7IN&P7u_>_fNu}QU z8&8sWI!|JhFIj(>VPcF2&VJC82NJ;6dF11D$OETy04fqpQ(-tY!3K#oKf$i#4a&?} zTrehX%mUXab3K9)ZD4{vL<^R~*QbKAxp5YF#)Qu(9rz^*n`V{*f-V?G&Jj;v>D--$ zw<1+?0SB-MdE!H`0l;u5YTBjqjU&yVa%V~Nn!eQ1f8SNlySg~n4&Jq6@o2WL8BSi? z^EwGGnX``CxA&&n(vA++(UEo5@Xof3vn%cFV!My6UjFq}cu4nG1HT*OoELcK1y;PX z)%9<`m>N%4Z)dBwYYU!~3Z7)U@&4-a??RmOdEWUvE8bGUqv`5xZ1py6!4p!!6Kpr$ zUp@D`7dYod-g%LAUVOY}%QgEkFbg!hgtj`7weuvn~bt}szn*W!>w z%hlcjN*ptB9GwuWwN+^Qyr9q5C(~f@)Kbp{!@S{-F}7*2>x;0XML=!$`f~*HF0KIR zQIYjlgWXVs9dz!Sz^$rgIiq2*CEEo>SN(<(etR2Kk!7MPM?kH((VX)Oy%v%vn3g*A6b0 zXNgNAG92Q_C{IRNGWs=`(#zj0UwV}zEg)R6qy>%}F=jzE+bS`yJLVmr+4`LE zV*Nc`P)?t3AJNpR?tGXi^_tg%r4gvcXF1BD?qM|(1hlnicLC5fD#tN0UU*ZBZn*E% zoeS<)n)>17fbq)byk^v$33hV>SS5iP=e!xE{GwZnDd`&rome>N5w#eeSk%)|sPXFX zToOGMCm8IpU=rD>q+=o`vr!&JU(yWw6;Bjtqk$emQlm&}A_D_XBL?7)K&^#7j7HSo zh^LuQS*TKB&_pMcTnjK!bSIG+D)2aw5Hg=)vSBtv7cf*v4U!#&C*KD8n$IC~{7V2g zsjn*>d_@zo8IG*AYVpX|jxFMX?oB&-IY&S5=wBq+G6(82j_a~jb;~_(Z+mygTRXU_ z7QU)warAyw^%BjyIy0`Ww5w}nYPE-R_4BU&jB6&sKc_J3HF>KcbV3EVo!Sn4VS8OsQDh*y5jf|U-tGIxT~ zdPyc((lhVmJ09lt0@%t7g|@OWYDxdns|$LIRw`WgeA4bZai1xTq*Ch1rA9#jb`#K< z&s16i)&qTR|A5zkK80ghbQRPDvRX>@F38VZkk1VHHenN(QGE?)V-o6@;6e$MM$L24 zRZt7abE!PpESJ3?pC!?fOPf*c?NB<;wwBAGEv+55lxkPc<%f0`a25-tv^kaMzmdxe zb*dKZT^?>E~pF(JQL`aDPJ@7N=RD|>;WrXnk&A(KftCj zmHo*uS^qSc5`#*=VgFmTeFyDO^Y?-7E!Y;yHRtlW{s8ladE0z>;rV*YyQIJMBIrl* zD8h{D8=#3*E&rGGpg}R}s270R-MXRZLu9Ip$EtkIG%F$6Mq>+A8ckQwmD48L{&|&_ zKS+6%68TT?BXk%Hcp(b7s-(T`g|4PYy57vs*;2|<%f+0X(dZO=&!`Bg4z#CuC&&JP#L;kNI?TN1R zAu>1*kpp`8zu`(6CU%TGL}o5oHZpd|6GR>9kSFPul%=v35_P4rTpa}O0bi#qLxXYf zZ0wq7$CVUPKL$iXDSY)LD@>Usj4;o}=Mtf?U{z#u;QSeZfFvN9fGwdYj0K{~jUe++ zp(OKbEN)=pappJp`p*!cx0GPJ%)}z->v+nY)-9B_P27 zDd3+TOrUXkBn)3Jfqb8kEXif!`zX9an=GovS15t(D6$B)iP*WhsVlNLAy`MI@C&4| zU^E1V<!1oJL7*oZhq`6f3af&tc)V3yXBI39u#*ATQ+a%~VhESONNibLjDm_gY_0DJT$ zrBOy)TKK&TIASo%kQzt+4pq?wm!z22yUKSJC#s?d9D& z)+nlHLZ1&GQB=eC^xukbjcT_yKSHoHwx(~ZZia6I_{N=?#=&&sAlJB;Z``|V$kw>| znys0dzI09B>N!bW!qyCeS*oe!&J{>+YWd*CdpB;sdgs+;BOu5$cBdP=xyGG<2~wNe zGR-~d<{qxOm&bee-Bzx7_@0w%KD=y()L+(rSpV~ej~d_w5G}m7aQlaMezCK z_uO@629Ko&k8y)1_`wsL_ayH6)J3cz@l^)r{~pBW%q`wx;1vm;$!ft-Y)LoNti#4TAJtdlaNugtw0^kEWbiw}*Fk zXWRp6_rPlW?kEhr`v~to!ix7J1Lk~;#_JZ|z4JHiAU?D8M*u-Jm_HPdt?}F@U5&Tax zwZ;8b@Tb#nPk#`6FPIv;ef7@OwGyhj2@KE8+g5s3cCGCCsCqpjQ)BXC!<+ z^X~nud;izojuqWUosizrxl;Af;PM#sxw&W6!Fl?5Pyc<-&Q&Mp8Q?tw7_8O6Fi z_w34ehSHv)yS?`e_s(;k!xbF{1!0d>+D@Q0AIebb=y{S z0O01F+j-}9cD);>4-Go@E)2i`NT#6yz+{ntXd3EU1^ZAR+?=(axAwE^-7+T200iOz zAYmU5pBbk9w)I%`@xA)L8`Q(g-|ekGQD^$C%K+iu*6J`^Cx$(TyUtWlTt&s1y#}ta z>db(F+i8SM+<*yV_F^V(xb$qBAx)~zdJJig3BsC>kMPJ9^)ep<;oOW~M$12MQWKhI z|4t!uP;_arf;BwP&as&<$iUK@r-y*jDv!RtmPK2imP-{Sh<~$Sq;>F}LOv!{i<#ei z_{gk+Y)WxL8GH%_RJfYouFIGvHR4F zsw0B$0L@x?4s>zG1yi&EI9oJr%2!Tf1U#+@bi9z~1u9yqNRo)>6&)j%1s%3Y_71Sj zm(b>`A`S*L#enxeL#pCCAf<|Wa5*&tsYEVY1D^PKt3YW9@aY9QjktvQuc2(IT=ruA z_aK{^^)l@!Y-X%e_FS8CA=*mYw73f9gv=AVTpkl?tiKN}uaH~*+%vblQd`>}(Q^Cm zX}M+ILRaOtTr;w&elIr`u+EM1CToG+RR=Qtt>2m`l$DLBJ9n9|aR4V}L_P?l^uel(@ol zV#ckQ5!_=@-WU(jbTF#;W|qk=lW39<%47#i(c!}ajP|7H4l5ZQh-k?J0Y;SeBp&_W zBl7zI)^)B7LqgImv;Ok8!`&LETRRL$d>XVkz(%|3cvnZpwLR_H&bhjIS9iwMmv;4W zt^wXP05(B6y*KUZl}ro}hYlUB8E1Fe+08k3^3I)$mIn^_dKSF1GPQl_+CHv!fUh0k z9J_hP?lsC|Iq(Ppm?E~+FOFwrTgTS4W9tgIR&b78ykl3!u{-V9%{c~n$6&^BAniE7 zIYxK~_yatPtpgbC%K9~m*xIrVH+<%8Z(GwFt9|#In(y3Lsb8hJroDXAUO2;RyRseI zm&YGJzF*&PXAeYF`*HyIo@Ii6$jABM)c(5Kvtk5~6m&{yYF!!^t69FBu4rW|T2~w^ ziBIcR>az8oC9*`ac6W-T?VYT>bLHIX*rzY9yqK3#p0;%bq^V zxzF+LbDZlu?>fI^$U5+I{5?y1#O6N6HrdklE*3aYV6_u`Q%|O8Al-yq?g-a3%r^~Z znnu%2qg>NrzUgqL=~TMu)K@fn{yDDcBHwh8t9_oYeO~nISss0B-_kx2?brBSX=BE^ zEp6SlGP?Tws++S8^VVV3It+oe5gp9AIQwxV17DVG8Eevi*<>H+|hPK)awB(zQMp15~--tKjuNsebASY!K~AW~C;@ zM#XgGb2vHvrqEur8Gc7cMYnJ8)he8f(9c}$Cfc0O2Nc#eUGM`Sps9a`Z%lw=P`^2o zJ@$;6?^jFajT`FMjISAG{ThUF`Ze*CAbI*Q=5JsqnSX)cUjh(}=uMH_BC0(+7|zow zfljGXBIuuB{!bAgL6=B79uJa!>0d)gD3PfsxPk6N#PDT_3BnqRqsm1)VQ7Cu{mAex^+x{whuGpvyRSv6bN$(=&YK2WzrLr) zBK!u?W%yB_*GayQh^AfZ`iN-TrR6Tip6(fRlO=^uYHiI@u z1!k(cX3azar-pt~Lum)-OE|{?-f1Mpz*YjS1}f?))s z2++#J96~USU<$!?1PchnlL>_Z$#D@!?h{Hv+=sDy6@Vgr{67G=ss63$%_a{x`e;C< z!PE|JL<%U?o8YHIR6xG3Gl54_9-P-(O+b}Z;5J|Z6_i$(K*gs5uhoRUMS0MDL1zM0 zTON$++f2Yxs$fE=A{VQ1TTD2D>qDvQ*lS;#P;vUknM8m0nS}H?V_f`EBI!Fzq^}IJ zN4akt84JFaA}B*(MPNfvjsP_hOeF%8{u$(U86<(yI=3oSjmDUuXbED_?L_=h9R|l< zC{uDAoePP-KgQs%8Zh+;kPl&gjNrck5G?SS=Cxov1|JsC80kiUEQ$29SK^#fu=I2D z{Dkb2eoku`bu^ep#UBBL{jAfi=?%I`7_t9vAfa@-S!&?vcb3}DZhU7c+fDH=OIdG< ze_6_MQ~b+PTUh1JQVp#3&Qf+(xz~(D2fssB4A{D^M;QHWF3ow(sKaHw@nh-YBPsRU Kr(_bB#Qy;h@27A8 literal 0 HcmV?d00001 diff --git a/src/gui/widgets/__pycache__/process_panel.cpython-311.pyc b/src/gui/widgets/__pycache__/process_panel.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6437ca7f8fa7dedf49261fcd3acf5d9dde197b6a GIT binary patch literal 3806 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 0: + script_path = self.favorites_table.item(current_row, 2).text() + if script_path: + if sys.platform == 'win32': + os.startfile(script_path) + elif sys.platform == 'darwin': + subprocess.run(['open', script_path]) + else: + subprocess.run(['xdg-open', script_path]) + + def launch_app(self, package_name=None): + if not package_name: + package_name = self.package_input.text() + + try: + cmd = ['adb', 'shell', 'am', 'start'] + + if self.debug_check.isChecked(): + cmd.extend(['-D']) + + if self.wait_check.isChecked(): + cmd.extend(['-W']) + + cmd.extend(['-n', f'{package_name}/{package_name}.MainActivity']) + + process = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + stdout, stderr = process.communicate() + + if process.returncode == 0: + self.add_to_recent(package_name) + # Get PID of launched app + pid_cmd = ['adb', 'shell', 'pidof', package_name] + pid = subprocess.check_output(pid_cmd).decode().strip() + if pid: + self.app_launched.emit(package_name, int(pid)) + else: + raise Exception(stderr.decode()) + + except Exception as e: + print(f"Error launching app: {str(e)}") + + def add_to_favorites(self, name, package): + self.favorites[name] = package + self.save_favorites() + self.update_favorites_table() + + def remove_from_favorites(self, name): + if name in self.favorites: + del self.favorites[name] + self.save_favorites() + self.update_favorites_table() + + def load_favorites(self): + try: + if os.path.exists(self.favorites_file): + with open(self.favorites_file, 'r') as f: + self.favorites = json.load(f) + else: + self.favorites = {} + except: + self.favorites = {} + + def save_favorites(self): + os.makedirs(os.path.dirname(self.favorites_file), exist_ok=True) + with open(self.favorites_file, 'w') as f: + json.dump(self.favorites, f) + + def update_favorites_table(self): + self.favorites_table.setRowCount(0) + for name, package in self.favorites.items(): + row = self.favorites_table.rowCount() + self.favorites_table.insertRow(row) + + name_item = QTableWidgetItem(name) + package_item = QTableWidgetItem(package) + + launch_btn = QPushButton("Launch") + launch_btn.clicked.connect(lambda checked, p=package: self.launch_app(p)) + + self.favorites_table.setItem(row, 0, name_item) + self.favorites_table.setItem(row, 1, package_item) + self.favorites_table.setCellWidget(row, 2, launch_btn) + + def show_context_menu(self, position): + menu = QMenu() + remove_action = QAction("Remove from Favorites", self) + remove_action.triggered.connect(lambda: self.remove_selected_favorite()) + menu.addAction(remove_action) + menu.exec_(self.favorites_table.mapToGlobal(position)) + + def remove_selected_favorite(self): + current_row = self.favorites_table.currentRow() + if current_row >= 0: + name = self.favorites_table.item(current_row, 0).text() + self.remove_from_favorites(name) + + def add_to_recent(self, package_name): + current_text = self.recent_combo.currentText() + if current_text != package_name: + self.recent_combo.insertItem(0, package_name) + if self.recent_combo.count() > 10: + self.recent_combo.removeItem(10) + + def launch_recent(self): + package_name = self.recent_combo.currentText() + if package_name: + self.launch_app(package_name) \ No newline at end of file diff --git a/src/gui/widgets/codeshare_browser.py b/src/gui/widgets/codeshare_browser.py new file mode 100644 index 0000000..b195f4a --- /dev/null +++ b/src/gui/widgets/codeshare_browser.py @@ -0,0 +1,539 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, + QLineEdit, QPushButton, QListWidget, + QTextBrowser, QSplitter, QComboBox, + QLabel, QProgressBar, QMessageBox, QGroupBox, QDialog, QTabWidget, QMenu, QFrame, QTableWidget, QHeaderView, QFileDialog, QScrollArea, QGridLayout, QTextEdit) +from PyQt5.QtCore import pyqtSignal, Qt, QThread, QUrl +from PyQt5.QtGui import QFont, QDesktopServices, QIcon +import aiohttp +import asyncio +import qtawesome as qta +import json +import os +from bs4 import BeautifulSoup +from core.script_templates import SCRIPT_TEMPLATES +from core.script_history import ScriptHistory +import time +import requests +import threading +import re + +class CodeFetcher(QThread): + code_fetched = pyqtSignal(str) + error_occurred = pyqtSignal(str) + + def __init__(self, url): + super().__init__() + self.url = url + + def run(self): + try: + response = requests.get(self.url) + if response.status_code != 200: + self.error_occurred.emit(f"HTTP Error: {response.status_code}") + return + + # Find the script content in the Vue.js data + script_match = re.search(r'projectSource: "(.*?)",', response.text, re.DOTALL) + if script_match: + # Unescape the JavaScript string + code = script_match.group(1).encode().decode('unicode_escape') + self.code_fetched.emit(code) + else: + # Try alternative method - look for the editor content + soup = BeautifulSoup(response.text, 'html.parser') + editor_div = soup.find('div', {'id': 'editor'}) + if editor_div and editor_div.string: + self.code_fetched.emit(editor_div.string) + else: + self.error_occurred.emit("Could not find script content") + + except Exception as e: + self.error_occurred.emit(f"Error fetching script: {str(e)}") + +class CodeShareBrowser(QWidget): + script_selected = pyqtSignal(str) # For injector + open_in_injector = pyqtSignal(str) # New signal for opening in injector + favorites_updated = pyqtSignal() # New signal for favorites updates + + def __init__(self): + super().__init__() + self.scripts_cache = {} + self.api_url = "https://konsumer.js.org/frida-codeshare/codeshare.json" + self.favorites = [] # Initialize as empty list + self.load_favorites() + self.setup_ui() + + def load_favorites(self): + """Load favorites from file""" + try: + favorites_file = os.path.join(os.path.expanduser('~'), '.frida_gui', 'favorites.json') + if os.path.exists(favorites_file): + with open(favorites_file, 'r') as f: + data = json.load(f) + # Make sure we get a list, even if loading from a dict + if isinstance(data, dict): + self.favorites = data.get('favorites', []) + else: + self.favorites = data if isinstance(data, list) else [] + else: + self.favorites = [] + except Exception as e: + print(f"Error loading favorites: {e}") + self.favorites = [] + + def save_favorites(self): + """Save favorites to file""" + try: + favorites_file = os.path.join(os.path.expanduser('~'), '.frida_gui', 'favorites.json') + os.makedirs(os.path.dirname(favorites_file), exist_ok=True) + with open(favorites_file, 'w') as f: + # Save as a simple list + json.dump(self.favorites, f) + except Exception as e: + print(f"Error saving favorites: {e}") + + def is_favorite(self, script_id): + """Check if script is favorited""" + return script_id in self.favorites + + def toggle_favorite(self, script_info): + """Toggle favorite status of script""" + script_id = script_info['id'] + if script_id in self.favorites: + self.favorites.remove(script_id) + else: + self.favorites.append(script_id) + self.save_favorites() + self.refresh_favorites() + self.favorites_updated.emit() # Emit signal when favorites change + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Create tab widget + self.tab_widget = QTabWidget() + + # Create tabs + self.browse_tab = QWidget() + self.favorites_tab = QWidget() + + self.setup_browse_tab() + self.setup_favorites_tab() + + # Add tabs + self.tab_widget.addTab(self.browse_tab, "Browse") + self.tab_widget.addTab(self.favorites_tab, "โ˜… Favorites") + + layout.addWidget(self.tab_widget) + + self.refresh_scripts() + + def setup_browse_tab(self): + """Setup the browse tab (existing functionality)""" + layout = QVBoxLayout(self.browse_tab) + + # Move existing toolbar and grid here + toolbar = QHBoxLayout() + + # Search bar + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("โŒ• Search scripts...") + self.search_input.textChanged.connect(self.filter_scripts) + + # Category filter + self.category_combo = QComboBox() + self.category_combo.addItems(['All', 'Android', 'iOS', 'Windows', 'Linux', 'macOS']) + self.category_combo.currentTextChanged.connect(self.filter_scripts) + + # Sort options + self.sort_combo = QComboBox() + self.sort_combo.addItems(['โ˜… Most Popular', '๐Ÿ‘ Most Viewed', 'โฒ Latest']) + self.sort_combo.currentTextChanged.connect(self.refresh_scripts) + + toolbar.addWidget(self.search_input) + toolbar.addWidget(self.category_combo) + toolbar.addWidget(self.sort_combo) + + # Grid layout for scripts + self.grid_widget = QWidget() + self.grid_layout = QGridLayout(self.grid_widget) + self.grid_layout.setSpacing(10) + + # Scroll area for grid + scroll = QScrollArea() + scroll.setWidget(self.grid_widget) + scroll.setWidgetResizable(True) + scroll.setStyleSheet(""" + QScrollArea { + border: none; + background-color: #36393f; + } + """) + + # Add all components + layout.addLayout(toolbar) + layout.addWidget(scroll) + + def setup_favorites_tab(self): + """Setup the favorites tab""" + layout = QVBoxLayout(self.favorites_tab) + + # Create grid for favorite scripts + self.favorites_grid = QWidget() + self.favorites_grid_layout = QGridLayout(self.favorites_grid) + self.favorites_grid_layout.setSpacing(10) + + # Scroll area for favorites + scroll = QScrollArea() + scroll.setWidget(self.favorites_grid) + scroll.setWidgetResizable(True) + scroll.setStyleSheet(""" + QScrollArea { + border: none; + background-color: #36393f; + } + """) + + # Add to layout + layout.addWidget(scroll) + + # Initial population of favorites + self.refresh_favorites() + + def refresh_favorites(self): + """Refresh the favorites grid""" + # Clear existing favorites grid + for i in reversed(range(self.favorites_grid_layout.count())): + widget = self.favorites_grid_layout.itemAt(i).widget() + if widget: + widget.setParent(None) + + # Get favorite scripts + try: + # Get all scripts + response = requests.get(self.api_url) + all_scripts = response.json() + + # Filter to only favorited scripts + favorite_scripts = [s for s in all_scripts if s['id'] in self.favorites] + + if favorite_scripts: + # Add scripts to grid + for idx, script_info in enumerate(favorite_scripts): + row = idx // 3 + col = idx % 3 + card = self.create_script_card(script_info) + self.favorites_grid_layout.addWidget(card, row, col) + else: + # Show message if no favorites + msg = QLabel("No favorite scripts yet.\nBrowse scripts and click the โ˜… to add favorites!") + msg.setAlignment(Qt.AlignCenter) + msg.setStyleSheet(""" + color: #b9bbbe; + font-size: 14px; + padding: 20px; + """) + self.favorites_grid_layout.addWidget(msg, 0, 0, 1, 3) + + except Exception as e: + print(f"Error refreshing favorites: {e}") + error_msg = QLabel(f"Error loading favorites: {str(e)}") + error_msg.setStyleSheet("color: #ff4444;") + self.favorites_grid_layout.addWidget(error_msg, 0, 0, 1, 3) + + def fetch_scripts(self): + """Fetch scripts from API""" + try: + response = requests.get(self.api_url) + scripts = response.json() + + # Sort scripts + sort_option = self.sort_combo.currentText() + if sort_option == 'Most Popular': + scripts.sort(key=lambda x: x.get('likes', 0), reverse=True) + elif sort_option == 'Most Viewed': + scripts.sort(key=lambda x: x.get('seen', 0), reverse=True) + + return scripts + except Exception as e: + print(f"Error fetching scripts: {e}") + return [] + + def create_script_card(self, script_info): + """Create a card widget for a script""" + card = QFrame() + card.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 8px; + padding: 10px; + } + QFrame:hover { + background-color: #40444b; + } + QLabel { + color: white; + } + """) + + layout = QVBoxLayout(card) + + # Title + title = QLabel(script_info['title']) + title.setStyleSheet("font-size: 14px; font-weight: bold;") + title.setWordWrap(True) + + # Author + author = QLabel(f"by {script_info['author']}") + author.setStyleSheet("color: #b9bbbe;") + + # Stats + stats = QHBoxLayout() + stars = QLabel(f"โ˜… {script_info.get('likes', 0)}") + views = QLabel(f"๐Ÿ‘ {script_info.get('seen', 0)}") + stats.addWidget(stars) + stats.addWidget(views) + + # Description + desc = QLabel(script_info.get('description', '')[:100] + '...') + desc.setWordWrap(True) + desc.setStyleSheet("color: #b9bbbe;") + + # Action buttons + buttons = QHBoxLayout() + + view_btn = QPushButton("View") + view_btn.clicked.connect(lambda: self.fetch_script_code(script_info)) + + fav_btn = QPushButton() + if self.is_favorite(script_info['id']): + fav_btn.setIcon(QIcon()) + fav_btn.setText("โ˜…") + else: + fav_btn.setIcon(QIcon()) + fav_btn.setText("โ˜†") + fav_btn.setStyleSheet("color: #b9bbbe;") + fav_btn.clicked.connect(lambda: self.toggle_favorite_ui(script_info, fav_btn)) + + buttons.addWidget(view_btn) + buttons.addWidget(fav_btn) + buttons.addStretch() + + layout.addWidget(title) + layout.addWidget(author) + layout.addLayout(stats) + layout.addWidget(desc) + layout.addLayout(buttons) + + return card + + def fetch_script_code(self, script_info): + """Fetch and show script code""" + # Remove author name from ID if it's included + script_id = script_info['id'].replace(f"{script_info['author']}/", "") + url = f"https://codeshare.frida.re/@{script_info['author']}/{script_id}" + print(f"Fetching script from: {url}") # Debug print + + # Create preview dialog + dialog = QDialog(self) + dialog.setWindowTitle(f"Frida CodeShare - {script_info['title']}") + dialog.resize(1000, 800) + dialog.setStyleSheet(""" + QDialog { + background-color: #2f3136; + } + QLabel { + color: white; + } + QPushButton { + background-color: #7289da; + color: white; + padding: 8px 16px; + border-radius: 4px; + min-width: 100px; + } + QPushButton:hover { + background-color: #677bc4; + } + QTextEdit { + background-color: #36393f; + color: #dcddde; + border: none; + border-radius: 4px; + padding: 10px; + font-family: 'Consolas', monospace; + } + """) + + layout = QVBoxLayout(dialog) + layout.setSpacing(15) + + # Header + header = QHBoxLayout() + title = QLabel(script_info['title']) + title.setStyleSheet("font-size: 18px; font-weight: bold;") + author = QLabel(f"by {script_info['author']}") + author.setStyleSheet("color: #b9bbbe;") + header.addWidget(title) + header.addWidget(author) + header.addStretch() + + # Stats + stats = QHBoxLayout() + likes = QLabel(f"โ˜… {script_info.get('likes', 0)}") + views = QLabel(f"๐Ÿ‘ {script_info.get('seen', 0)}") + stats.addWidget(likes) + stats.addWidget(views) + stats.addStretch() + + # Description + desc = QLabel(script_info.get('description', '')) + desc.setWordWrap(True) + desc.setStyleSheet("color: #b9bbbe; padding: 10px;") + + # Code preview + code_view = QTextEdit() + code_view.setReadOnly(True) + code_view.setFont(QFont('Consolas', 11)) + code_view.setLineWrapMode(QTextEdit.NoWrap) + code_view.setText("Loading script...") + + # Usage instructions + usage = QLabel(f"Try this code out by running:\n$ frida --codeshare {script_info['author']}/{script_info['id']} -f YOUR_BINARY") + usage.setStyleSheet(""" + background-color: #202225; + padding: 10px; + border-radius: 4px; + font-family: 'Consolas', monospace; + """) + + # Action buttons + buttons = QHBoxLayout() + + copy_btn = QPushButton(qta.icon('fa5s.copy'), "โŽ˜ Copy Code") + copy_btn.clicked.connect(lambda: self.copy_to_clipboard(code_view.toPlainText())) + + inject_btn = QPushButton(qta.icon('fa5s.syringe'), "โšก Open in Injector") + inject_btn.clicked.connect(lambda: self.open_in_injector_page(code_view.toPlainText(), dialog)) + + download_btn = QPushButton(qta.icon('fa5s.download'), "โค“ Download") + download_btn.clicked.connect(lambda: self.download_script(script_info['title'], code_view.toPlainText())) + + open_btn = QPushButton(qta.icon('fa5s.external-link-alt'), "โง‰ Open in Browser") + open_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url))) + + buttons.addWidget(copy_btn) + buttons.addWidget(inject_btn) + buttons.addWidget(download_btn) + buttons.addWidget(open_btn) + buttons.addStretch() + + # Add all components + layout.addLayout(header) + layout.addLayout(stats) + layout.addWidget(desc) + layout.addWidget(usage) + layout.addWidget(code_view) + layout.addLayout(buttons) + + dialog.show() + + # Create and start the code fetcher thread + self.code_fetcher = CodeFetcher(url) + self.code_fetcher.code_fetched.connect(code_view.setText) + self.code_fetcher.error_occurred.connect(lambda err: code_view.setText(f"Error loading script: {err}")) + self.code_fetcher.start() + + def refresh_scripts(self): + """Refresh scripts from API""" + # Clear existing grid + for i in reversed(range(self.grid_layout.count())): + self.grid_layout.itemAt(i).widget().setParent(None) + + # Fetch and sort scripts + scripts = self.fetch_scripts() + + # Add all scripts to grid + for idx, script_info in enumerate(scripts): + row = idx // 3 + col = idx % 3 + card = self.create_script_card(script_info) + self.grid_layout.addWidget(card, row, col) + + # Refresh favorites tab + self.refresh_favorites() + + def add_script(self, script_info): + """Add a script card to the grid""" + # Calculate grid position + count = self.grid_layout.count() + row = count // 3 + col = count % 3 + + # Create and add card + card = self.create_script_card(script_info) + self.grid_layout.addWidget(card, row, col) + + # Cache the script + self.scripts_cache[script_info['id']] = script_info + + def filter_scripts(self): + """Filter visible scripts based on search and category""" + search_text = self.search_input.text().lower() + category = self.category_combo.currentText() + + # Show/hide cards based on filters + for i in range(self.grid_layout.count()): + widget = self.grid_layout.itemAt(i).widget() + if widget: + title = widget.findChild(QLabel).text().lower() + desc = widget.findChildren(QLabel)[-2].text().lower() # Description label + + show = True + if search_text and search_text not in title and search_text not in desc: + show = False + if category != 'All' and category not in desc: + show = False + + widget.setVisible(show) + + def copy_to_clipboard(self, text): + """Copy text to clipboard""" + clipboard = QApplication.clipboard() + clipboard.setText(text) + QMessageBox.information(self, "โœ“ Success", "โŽ˜ Copied to clipboard!") + + def download_script(self, title, code): + """Download script to file""" + filename = f"{title.lower().replace(' ', '_')}.js" + file_path, _ = QFileDialog.getSaveFileName( + self, "โค“ Save Script", filename, "JavaScript Files (*.js)" + ) + + if file_path: + try: + with open(file_path, 'w') as f: + f.write(code) + QMessageBox.information(self, "โœ“ Success", "โค“ Script downloaded successfully!") + except Exception as e: + QMessageBox.critical(self, "โœ— Error", f"Failed to save script: {str(e)}") + + def toggle_favorite_ui(self, script_info, button): + """Toggle favorite status and update UI""" + self.toggle_favorite(script_info) + if self.is_favorite(script_info['id']): + button.setIcon(QIcon()) + button.setText("โ˜…") + else: + button.setIcon(QIcon()) + button.setText("โ˜†") + button.setStyleSheet("color: #b9bbbe;") + + # Refresh favorites tab when status changes + self.refresh_favorites() + + def open_in_injector_page(self, code, dialog=None): + """Open the script in the injector page""" + self.open_in_injector.emit(code) # Emit signal to main window + if dialog: + dialog.close() # Close the preview dialog \ No newline at end of file diff --git a/src/gui/widgets/data_visualizer.py b/src/gui/widgets/data_visualizer.py new file mode 100644 index 0000000..578fd96 --- /dev/null +++ b/src/gui/widgets/data_visualizer.py @@ -0,0 +1,44 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout +from PyQt5.QtChart import QChart, QChartView, QLineSeries +from PyQt5.QtCore import Qt, QTimer +import json + +class DataVisualizer(QWidget): + def __init__(self): + super().__init__() + self.setup_ui() + self.api_calls = [] + self.setup_timer() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Create chart + self.chart = QChart() + self.chart.setTitle("API Calls Over Time") + self.chart.setAnimationOptions(QChart.SeriesAnimations) + + self.series = QLineSeries() + self.chart.addSeries(self.series) + + chart_view = QChartView(self.chart) + chart_view.setRenderHint(QPainter.Antialiasing) + + layout.addWidget(chart_view) + + def setup_timer(self): + self.timer = QTimer() + self.timer.timeout.connect(self.update_chart) + self.timer.start(1000) # Update every second + + def add_api_call(self, call_data): + self.api_calls.append({ + 'timestamp': time.time(), + 'data': call_data + }) + + def update_chart(self): + # Update chart with new data + self.series.clear() + for i, call in enumerate(self.api_calls[-50:]): # Show last 50 calls + self.series.append(i, len(call['data'])) \ No newline at end of file diff --git a/src/gui/widgets/device_panel.py b/src/gui/widgets/device_panel.py new file mode 100644 index 0000000..16947c4 --- /dev/null +++ b/src/gui/widgets/device_panel.py @@ -0,0 +1,46 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, + QComboBox, QPushButton, QLabel) +from PyQt5.QtCore import pyqtSignal +import frida + +class DevicePanel(QWidget): + device_selected = pyqtSignal(str) + + def __init__(self): + super().__init__() + self.setup_ui() + + def setup_ui(self): + layout = QHBoxLayout(self) + + # Device selection combo box + self.device_combo = QComboBox() + self.scan_button = QPushButton("Scan Devices") + + layout.addWidget(QLabel("Select Device:")) + layout.addWidget(self.device_combo) + layout.addWidget(self.scan_button) + + # Connect signals + self.scan_button.clicked.connect(self.scan_devices) + self.device_combo.currentIndexChanged.connect(self._on_device_selected) + + # Initial scan + self.scan_devices() + + def scan_devices(self): + try: + self.device_combo.clear() + devices = frida.enumerate_devices() + for device in devices: + if device.type in ['usb', 'remote']: + self.device_combo.addItem(f"{device.name} (ADB - {device.type})", device.id) + elif device.type == 'local': + self.device_combo.addItem(f"{device.name} (Local)", device.id) + except Exception as e: + print(f"Error scanning devices: {str(e)}") + + def _on_device_selected(self): + device_id = self.device_combo.currentData() + if device_id: + self.device_selected.emit(device_id) \ No newline at end of file diff --git a/src/gui/widgets/device_selector.py b/src/gui/widgets/device_selector.py new file mode 100644 index 0000000..038c7ae --- /dev/null +++ b/src/gui/widgets/device_selector.py @@ -0,0 +1,307 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QComboBox, + QPushButton, QLabel, QFrame, QLineEdit, QMessageBox, + QApplication) +from PyQt5.QtCore import pyqtSignal, QSize +import frida +import subprocess +import qtawesome as qta +import psutil +import sys +from pathlib import Path + +# Add project root to Python path +sys.path.append(str(Path(__file__).parent.parent.parent)) +from core.android_helper import AndroidHelper + +class DeviceSelector(QWidget): + process_selected = pyqtSignal(str, int) # device_id, pid + + def __init__(self): + super().__init__() + self.current_device = None + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + # Create main frame + frame = QFrame() + frame.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 8px; + padding: 10px; + } + QComboBox { + background-color: #36393f; + border: none; + border-radius: 4px; + padding: 8px; + color: white; + min-width: 200px; + } + QComboBox::drop-down { + border: none; + padding-right: 10px; + } + QComboBox::down-arrow { + image: url(down-arrow.png); + } + """) + + frame_layout = QVBoxLayout(frame) + + # Device selection + device_layout = QHBoxLayout() + + self.device_combo = QComboBox() + self.device_combo.setPlaceholderText("Select Device...") + self.device_combo.currentIndexChanged.connect(self.on_device_changed) + + refresh_btn = QPushButton(qta.icon('fa5s.sync'), "") + refresh_btn.setToolTip("Refresh Devices") + refresh_btn.clicked.connect(self.refresh_devices) + + device_layout.addWidget(QLabel("Device:")) + device_layout.addWidget(self.device_combo, 1) + device_layout.addWidget(refresh_btn) + + # Process selection + process_layout = QHBoxLayout() + + # Add filter input + self.process_filter = QLineEdit() + self.process_filter.setPlaceholderText("Filter processes...") + self.process_filter.textChanged.connect(self.filter_processes) + + self.process_combo = QComboBox() + self.process_combo.setPlaceholderText("Select Process...") + self.process_combo.currentIndexChanged.connect(self.on_process_changed) + self.process_combo.setMaxVisibleItems(20) # Show more items in dropdown + self.process_combo.setStyleSheet(""" + QComboBox QListView { + min-width: 300px; + } + """) + + # Add refresh button + refresh_btn = QPushButton(qta.icon('fa5s.sync'), "") + refresh_btn.setToolTip("Refresh Processes") + refresh_btn.clicked.connect(self.refresh_processes) + + process_layout.addWidget(QLabel("Process:")) + process_layout.addWidget(self.process_filter) + process_layout.addWidget(self.process_combo, 1) + process_layout.addWidget(refresh_btn) + + # Add layouts to frame + frame_layout.addLayout(device_layout) + frame_layout.addLayout(process_layout) + + # Add frame to main layout + layout.addWidget(frame) + + # Initial device scan + self.refresh_devices() + + def refresh_devices(self): + self.device_combo.clear() + try: + devices = frida.enumerate_devices() + for device in devices: + if device.type == 'usb': + self.device_combo.addItem(f"๐Ÿ“ฑ {device.name} (USB)", device.id) + elif device.type == 'remote': + self.device_combo.addItem(f"๐ŸŒ {device.name} (Remote)", device.id) + elif device.type == 'local': + self.device_combo.addItem(f"๐Ÿ’ป {device.name} (Local)", device.id) + except Exception as e: + print(f"Error enumerating devices: {e}") + + def on_device_changed(self, index): + if index < 0: + return + + device_id = self.device_combo.currentData() + self.current_device = device_id + self.refresh_processes() + + def refresh_processes(self): + self.process_combo.clear() + if not self.current_device: + return + + try: + device = frida.get_device(self.current_device) + + if device.type == 'usb': + # Show loading message + self.process_combo.addItem("Checking device status...") + QApplication.processEvents() + + # Check frida-server for Android devices + if not AndroidHelper.is_device_connected(self.current_device): + raise Exception(f"Device {self.current_device} not connected") + + if not AndroidHelper.is_frida_running(self.current_device): + # Show installing message + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText("Installing Frida Server") + msg.setInformativeText("Please wait while Frida server is being installed on the device...") + msg.setWindowTitle("Installing Frida") + msg.show() + QApplication.processEvents() + + success = AndroidHelper.start_frida_server(self.current_device) + msg.close() + + if not success: + error_msg = QMessageBox() + error_msg.setIcon(QMessageBox.Critical) + error_msg.setText("Frida Installation Failed") + error_msg.setInformativeText("Failed to install and start Frida server on the device. Please check your device connection and try again.") + error_msg.setWindowTitle("Installation Error") + error_msg.exec_() + return + + # Show success message + success_msg = QMessageBox() + success_msg.setIcon(QMessageBox.Information) + success_msg.setText("Frida Server Installed") + success_msg.setInformativeText("Frida server has been successfully installed and started on the device.") + success_msg.setWindowTitle("Installation Complete") + success_msg.exec_() + + # Clear loading message and get processes + self.process_combo.clear() + + try: + # Get Android processes using frida-ps + processes = device.enumerate_processes() + for process in processes: + if process.pid > 0: + name = process.name + pid = process.pid + # Only add user apps (filter out system processes) + if '.' in name: # Simple check for app package names + self.process_combo.addItem( + f"{name} (PID: {pid})", + pid + ) + except Exception as e: + print(f"Error getting processes: {str(e)}") + raise Exception("Failed to get process list from device") + + elif device.type == 'local': + # Handle local device processes + processes = device.enumerate_processes() + for process in processes: + if process.pid > 0: + self.process_combo.addItem( + f"{process.name} (PID: {process.pid})", + process.pid + ) + + except Exception as e: + error_msg = QMessageBox() + error_msg.setIcon(QMessageBox.Critical) + error_msg.setText("Error") + error_msg.setInformativeText(f"Failed to refresh processes: {str(e)}") + error_msg.setWindowTitle("Process List Error") + error_msg.exec_() + + self.process_combo.clear() + self.process_combo.addItem("Error loading processes") + + def filter_processes(self, text): + """Filter processes in combo box""" + text = text.lower() + self.process_combo.clear() + + try: + device = frida.get_device(self.current_device) + + if device.type == 'local': + processes = device.enumerate_processes() + for process in processes: + try: + if process.pid > 0 and process.name and text in process.name.lower(): + pid = int(process.pid) + name = str(process.name) + self.process_combo.addItem( + f"{name} (PID: {pid})", + pid + ) + except (ValueError, AttributeError) as e: + continue + else: + # For Android devices + processes = device.enumerate_processes() + for process in processes: + if process.pid > 0 and text in process.name.lower(): + if '.' in process.name: # Only show Android apps + self.process_combo.addItem( + f"{process.name} (PID: {process.pid})", + process.pid + ) + + except Exception as e: + print(f"Error filtering processes: {e}") + + def on_process_changed(self, index): + if index < 0: + return + + try: + device_id = self.device_combo.currentData() + pid = self.process_combo.currentData() + + # Debug output + print(f"Process changed - device_id: {device_id}, pid: {pid} ({type(pid)})") + + # Only emit if we have valid data + if device_id and isinstance(pid, int) and pid > 0: + self.process_selected.emit(device_id, pid) + else: + print(f"Skipping invalid process selection - device_id: {device_id}, pid: {pid}") + + except Exception as e: + print(f"Error in process selection: {e}") + + def get_selected_process_info(self): + """Get info about selected process""" + try: + index = self.process_combo.currentIndex() + if index >= 0: + device_id = self.device_combo.currentData() + pid = self.process_combo.currentData() + name = self.process_combo.currentText().split('(')[0].strip() + + # Debug output + print(f"Selected process - PID: {pid} ({type(pid)}), Name: {name}") + + if device_id and pid: + return { + 'device_id': device_id, + 'pid': pid, + 'name': name + } + return None + except Exception as e: + print(f"Error getting process info: {e}") + return None + + def select_device(self, device_id): + """Select a device by its ID""" + index = self.device_combo.findData(device_id) + if index >= 0: + self.device_combo.setCurrentIndex(index) + + def select_process(self, pid): + """Select a process by its PID""" + for i in range(self.process_combo.count()): + if str(pid) in self.process_combo.itemText(i): + self.process_combo.setCurrentIndex(i) + break \ No newline at end of file diff --git a/src/gui/widgets/history_page.py b/src/gui/widgets/history_page.py new file mode 100644 index 0000000..1e84f44 --- /dev/null +++ b/src/gui/widgets/history_page.py @@ -0,0 +1,148 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QLabel, QTableWidget, QTableWidgetItem, QHeaderView, + QMenu, QMessageBox) +from PyQt5.QtCore import Qt, pyqtSignal +import qtawesome as qta +from datetime import datetime + +class HistoryPage(QWidget): + script_selected = pyqtSignal(str) # For opening scripts in injector + + def __init__(self, history_manager): + super().__init__() + self.history_manager = history_manager + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Header with title and clear button + header_layout = QHBoxLayout() + title = QLabel("Action History") + title.setStyleSheet("font-size: 18px; font-weight: bold; color: white;") + + clear_btn = QPushButton(qta.icon('fa5s.trash'), "Clear History") + clear_btn.clicked.connect(self.clear_history) + + header_layout.addWidget(title) + header_layout.addStretch() + header_layout.addWidget(clear_btn) + + # History table + self.table = QTableWidget() + self.table.setColumnCount(4) + self.table.setHorizontalHeaderLabels(["Time", "Action", "Details", "Actions"]) + + # Style the table + self.table.setStyleSheet(""" + QTableWidget { + background-color: #36393f; + border: none; + border-radius: 8px; + } + QTableWidget::item { + padding: 8px; + border-bottom: 1px solid #2f3136; + } + QHeaderView::section { + background-color: #2f3136; + padding: 8px; + border: none; + color: white; + font-weight: bold; + } + """) + + # Set column stretching + table_header = self.table.horizontalHeader() + table_header.setSectionResizeMode(0, QHeaderView.Fixed) # Time + table_header.setSectionResizeMode(1, QHeaderView.Fixed) # Action + table_header.setSectionResizeMode(2, QHeaderView.Stretch) # Details + table_header.setSectionResizeMode(3, QHeaderView.Fixed) # Actions + + self.table.setColumnWidth(0, 180) # Time + self.table.setColumnWidth(1, 120) # Action + self.table.setColumnWidth(3, 100) # Actions + + # Context menu + self.table.setContextMenuPolicy(Qt.CustomContextMenu) + self.table.customContextMenuRequested.connect(self.show_context_menu) + + # Add components to layout + layout.addLayout(header_layout) + layout.addWidget(self.table) + + self.refresh_history() + + def refresh_history(self): + self.table.setRowCount(0) + + for entry in self.history_manager.history: + row = self.table.rowCount() + self.table.insertRow(row) + + # Time + time_item = QTableWidgetItem( + datetime.fromisoformat(entry['timestamp']).strftime('%Y-%m-%d %H:%M:%S') + ) + + # Action + action_item = QTableWidgetItem(entry['type']) + + # Details + details = entry['details'] + if isinstance(details, dict): + details_text = "\n".join(f"{k}: {v}" for k, v in details.items()) + else: + details_text = str(details) + details_item = QTableWidgetItem(details_text) + + # Action buttons + action_widget = QWidget() + action_layout = QHBoxLayout(action_widget) + action_layout.setContentsMargins(4, 4, 4, 4) + + if 'script' in entry['details']: + inject_btn = QPushButton(qta.icon('fa5s.syringe'), "") + inject_btn.clicked.connect( + lambda x, s=entry['details']['script']: self.script_selected.emit(s) + ) + action_layout.addWidget(inject_btn) + + action_layout.addStretch() + + # Add items to row + self.table.setItem(row, 0, time_item) + self.table.setItem(row, 1, action_item) + self.table.setItem(row, 2, details_item) + self.table.setCellWidget(row, 3, action_widget) + + def clear_history(self): + reply = QMessageBox.question( + self, + "Clear History", + "Are you sure you want to clear all history?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + self.history_manager.clear_history() + self.refresh_history() + + def show_context_menu(self, position): + menu = QMenu() + + copy_action = menu.addAction("Copy Details") + copy_action.triggered.connect( + lambda: self.copy_details(self.table.currentRow()) + ) + + menu.exec_(self.table.viewport().mapToGlobal(position)) + + def copy_details(self, row): + if row >= 0: + details_item = self.table.item(row, 2) + if details_item: + clipboard = QApplication.clipboard() + clipboard.setText(details_item.text()) \ No newline at end of file diff --git a/src/gui/widgets/injection_panel.py b/src/gui/widgets/injection_panel.py new file mode 100644 index 0000000..187054d --- /dev/null +++ b/src/gui/widgets/injection_panel.py @@ -0,0 +1,220 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QLabel, QProgressBar, QFrame, QMessageBox, QFileDialog) +from PyQt5.QtCore import Qt, pyqtSignal +import qtawesome as qta +import os + +class InjectionPanel(QWidget): + injection_started = pyqtSignal(str, int) # script, pid + injection_completed = pyqtSignal(bool, str) # success, message + injection_stopped = pyqtSignal() # Signal to stop injection + + def __init__(self): + super().__init__() + self.current_pid = None + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Status panel + status_frame = QFrame() + status_frame.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 8px; + padding: 10px; + } + """) + status_layout = QHBoxLayout(status_frame) + + self.status_icon = QLabel() + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#99aab5').pixmap(16, 16)) + self.status_label = QLabel("No process selected") + self.status_label.setStyleSheet("color: #99aab5;") + + status_layout.addWidget(self.status_icon) + status_layout.addWidget(self.status_label) + status_layout.addStretch() + + # Action buttons + button_layout = QHBoxLayout() + + self.load_btn = QPushButton(qta.icon('fa5s.folder-open'), "Load Script") + self.load_btn.setStyleSheet(""" + QPushButton { + background-color: #7289da; + color: white; + padding: 8px 16px; + border-radius: 4px; + } + QPushButton:hover { + background-color: #677bc4; + } + """) + self.load_btn.clicked.connect(self.load_script_file) + + # Attach button (for running processes) + self.attach_btn = QPushButton(qta.icon('fa5s.link'), "Attach") + self.attach_btn.setStyleSheet(""" + QPushButton { + background-color: #43b581; + color: white; + padding: 8px 16px; + border-radius: 4px; + } + QPushButton:hover { + background-color: #3ca374; + } + QPushButton:disabled { + background-color: #2f3136; + color: #72767d; + } + """) + self.attach_btn.clicked.connect(lambda: self.start_injection(mode="attach")) + self.attach_btn.setEnabled(False) + + # Launch button (for spawning new process) + self.launch_btn = QPushButton(qta.icon('fa5s.play'), "Launch") + self.launch_btn.setStyleSheet(self.attach_btn.styleSheet()) + self.launch_btn.clicked.connect(lambda: self.start_injection(mode="launch")) + self.launch_btn.setEnabled(False) + + self.stop_btn = QPushButton(qta.icon('fa5s.stop'), "Stop") + self.stop_btn.setStyleSheet(""" + QPushButton { + background-color: #f04747; + color: white; + padding: 8px 16px; + border-radius: 4px; + } + QPushButton:hover { + background-color: #d84040; + } + """) + self.stop_btn.clicked.connect(self.stop_injection) + self.stop_btn.setEnabled(False) + + button_layout.addWidget(self.load_btn) + button_layout.addWidget(self.attach_btn) + button_layout.addWidget(self.launch_btn) + button_layout.addWidget(self.stop_btn) + button_layout.addStretch() + + # Progress bar + self.progress_bar = QProgressBar() + self.progress_bar.setTextVisible(False) + self.progress_bar.setStyleSheet(""" + QProgressBar { + border: none; + background-color: #2f3136; + border-radius: 4px; + height: 8px; + } + QProgressBar::chunk { + background-color: #7289da; + border-radius: 4px; + } + """) + self.progress_bar.hide() + + # Add all components + layout.addWidget(status_frame) + layout.addLayout(button_layout) + layout.addWidget(self.progress_bar) + + def set_process(self, device_id, pid): + """Called when a process is selected""" + try: + # Ensure pid is an integer + if not isinstance(pid, int): + print(f"Warning: PID is not an integer: {pid} ({type(pid)})") + pid = int(pid) + + if pid <= 0: + raise ValueError(f"Invalid PID value: {pid}") + + self.current_pid = pid + self.status_label.setText(f"Selected PID: {self.current_pid}") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#43b581').pixmap(16, 16)) + self.attach_btn.setEnabled(True) + self.launch_btn.setEnabled(True) + + except (ValueError, TypeError) as e: + print(f"Error setting process: {e}") + self.status_label.setText("Invalid PID") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#f04747').pixmap(16, 16)) + self.attach_btn.setEnabled(False) + self.launch_btn.setEnabled(False) + + def load_script_file(self): + """Load script from file""" + file_name, _ = QFileDialog.getOpenFileName( + self, + "Load Frida Script", + "", + "JavaScript Files (*.js);;All Files (*.*)" + ) + + if file_name: + try: + with open(file_name, 'r') as f: + script_content = f.read() + self.script_editor.set_script(script_content) + self.status_label.setText(f"Loaded script: {os.path.basename(file_name)}") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to load script: {str(e)}") + + def start_injection(self, mode="attach"): + """Start the injection process""" + if not self.current_pid or not isinstance(self.current_pid, int) or self.current_pid <= 0: + QMessageBox.warning(self, "Error", "Invalid PID!") + return + + script_content = self.script_editor.get_script() + if not script_content: + QMessageBox.warning(self, "Error", "No script to inject!") + return + + # Debug output + print(f"Starting injection - PID: {self.current_pid} ({type(self.current_pid)}), Mode: {mode}") + + # Update UI + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#faa61a').pixmap(16, 16)) + self.status_label.setText(f"{'Attaching to' if mode == 'attach' else 'Launching'} process...") + self.attach_btn.setEnabled(False) + self.launch_btn.setEnabled(False) + self.stop_btn.setEnabled(True) + self.progress_bar.show() + self.progress_bar.setRange(0, 0) + + try: + self.injection_started.emit(script_content, self.current_pid) + except Exception as e: + self.injection_failed(str(e)) + + def injection_succeeded(self): + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#43b581').pixmap(16, 16)) + self.status_label.setText("Injection successful!") + self.reset_ui() + QMessageBox.information(self, "Success", "Script injected successfully!") + + def injection_failed(self, error): + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#f04747').pixmap(16, 16)) + self.status_label.setText("Injection failed!") + self.reset_ui() + QMessageBox.critical(self, "Error", f"Injection failed: {error}") + + def reset_ui(self): + self.attach_btn.setEnabled(True) + self.launch_btn.setEnabled(True) + self.stop_btn.setEnabled(False) + self.progress_bar.hide() + + def stop_injection(self): + """Stop the current injection""" + self.injection_stopped.emit() + self.reset_ui() + self.status_label.setText("Injection stopped") + self.status_icon.setPixmap(qta.icon('fa5s.circle', color='#faa61a').pixmap(16, 16)) + \ No newline at end of file diff --git a/src/gui/widgets/output_panel.py b/src/gui/widgets/output_panel.py new file mode 100644 index 0000000..93c065c --- /dev/null +++ b/src/gui/widgets/output_panel.py @@ -0,0 +1,21 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit + +class OutputPanel(QWidget): + def __init__(self): + super().__init__() + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + self.output_area = QTextEdit() + self.output_area.setReadOnly(True) + self.output_area.setPlaceholderText("Output will appear here...") + + layout.addWidget(self.output_area) + + def append_output(self, text): + self.output_area.append(text) + + def clear_output(self): + self.output_area.clear() \ No newline at end of file diff --git a/src/gui/widgets/process_manager.py b/src/gui/widgets/process_manager.py new file mode 100644 index 0000000..9bd380e --- /dev/null +++ b/src/gui/widgets/process_manager.py @@ -0,0 +1,307 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QLabel, QLineEdit, QTableWidget, QTableWidgetItem, + QMenu, QAction, QComboBox, QCheckBox, QFrame, + QTableWidgetSelectionRange, QHeaderView) +from PyQt5.QtCore import Qt, pyqtSignal, QTimer +from PyQt5.QtGui import QColor, QFont +import qtawesome as qta +import re +import psutil + +class ProcessManager(QWidget): + process_selected = pyqtSignal(int) # pid + + def __init__(self): + super().__init__() + self.processes = {} + self.filters = { + 'name': '', + 'pid': '', + 'cpu': 0, + 'memory': 0, + 'show_system': False + } + self.setup_ui() + self.start_monitoring() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Filter bar + filter_frame = QFrame() + filter_frame.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 8px; + padding: 10px; + } + """) + filter_layout = QHBoxLayout(filter_frame) + + # Search with regex support + search_container = QFrame() + search_layout = QHBoxLayout(search_container) + + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("Filter processes (supports regex)") + self.search_input.textChanged.connect(self.apply_filters) + + self.regex_check = QCheckBox("Regex") + self.regex_check.toggled.connect(self.apply_filters) + + search_layout.addWidget(self.search_input) + search_layout.addWidget(self.regex_check) + + # Advanced filters + self.filter_combo = QComboBox() + self.filter_combo.addItems(['All', 'User', 'System', 'Android Apps']) + self.filter_combo.currentTextChanged.connect(self.apply_filters) + + # Resource thresholds + self.cpu_threshold = QSpinBox() + self.cpu_threshold.setSuffix("% CPU") + self.cpu_threshold.valueChanged.connect(self.apply_filters) + + self.memory_threshold = QSpinBox() + self.memory_threshold.setSuffix("MB") + self.memory_threshold.setMaximum(32000) + self.memory_threshold.valueChanged.connect(self.apply_filters) + + filter_layout.addWidget(search_container) + filter_layout.addWidget(self.filter_combo) + filter_layout.addWidget(self.cpu_threshold) + filter_layout.addWidget(self.memory_threshold) + + # Process table + self.process_table = QTableWidget() + self.process_table.setColumnCount(6) + self.process_table.setHorizontalHeaderLabels([ + "PID", "Name", "CPU %", "Memory", "Status", "Path" + ]) + + # Style the table + self.process_table.setStyleSheet(""" + QTableWidget { + background-color: #36393f; + border: none; + border-radius: 8px; + gridline-color: #2f3136; + } + QTableWidget::item { + padding: 5px; + border-bottom: 1px solid #2f3136; + } + QTableWidget::item:selected { + background-color: #7289da; + } + """) + + # Set column widths + header = self.process_table.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.Fixed) # PID + header.setSectionResizeMode(1, QHeaderView.Stretch) # Name + header.setSectionResizeMode(2, QHeaderView.Fixed) # CPU + header.setSectionResizeMode(3, QHeaderView.Fixed) # Memory + header.setSectionResizeMode(4, QHeaderView.Fixed) # Status + header.setSectionResizeMode(5, QHeaderView.Stretch) # Path + + self.process_table.setColumnWidth(0, 70) # PID + self.process_table.setColumnWidth(2, 80) # CPU + self.process_table.setColumnWidth(3, 100) # Memory + self.process_table.setColumnWidth(4, 100) # Status + + # Context menu + self.process_table.setContextMenuPolicy(Qt.CustomContextMenu) + self.process_table.customContextMenuRequested.connect(self.show_context_menu) + + # Quick action buttons + action_layout = QHBoxLayout() + + self.refresh_btn = QPushButton(qta.icon('fa5s.sync'), "Refresh") + self.refresh_btn.clicked.connect(self.refresh_processes) + + self.kill_btn = QPushButton(qta.icon('fa5s.stop'), "Kill") + self.kill_btn.clicked.connect(self.kill_selected_process) + + self.inject_btn = QPushButton(qta.icon('fa5s.syringe'), "Inject") + self.inject_btn.clicked.connect(self.inject_into_selected) + + action_layout.addWidget(self.refresh_btn) + action_layout.addWidget(self.kill_btn) + action_layout.addWidget(self.inject_btn) + action_layout.addStretch() + + # Status bar + status_bar = QFrame() + status_bar.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 4px; + padding: 5px; + } + """) + status_layout = QHBoxLayout(status_bar) + + self.process_count = QLabel("0 processes") + self.cpu_usage = QLabel("CPU: 0%") + self.memory_usage = QLabel("Memory: 0 MB") + + status_layout.addWidget(self.process_count) + status_layout.addStretch() + status_layout.addWidget(self.cpu_usage) + status_layout.addWidget(self.memory_usage) + + # Add all components + layout.addWidget(filter_frame) + layout.addWidget(self.process_table) + layout.addLayout(action_layout) + layout.addWidget(status_bar) + + def start_monitoring(self): + self.update_timer = QTimer() + self.update_timer.timeout.connect(self.refresh_processes) + self.update_timer.start(2000) # Update every 2 seconds + + def refresh_processes(self): + self.processes.clear() + total_cpu = 0 + total_memory = 0 + + for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_info', 'status', 'exe']): + try: + info = proc.info + memory_mb = info['memory_info'].rss / 1024 / 1024 + self.processes[info['pid']] = { + 'name': info['name'], + 'cpu': info['cpu_percent'], + 'memory': memory_mb, + 'status': info['status'], + 'path': info['exe'] or '' + } + total_cpu += info['cpu_percent'] + total_memory += memory_mb + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + self.update_table() + self.update_stats(total_cpu, total_memory) + + def update_table(self): + self.process_table.setSortingEnabled(False) + self.process_table.setRowCount(0) + + filtered_processes = self.filter_processes() + + for pid, info in filtered_processes.items(): + row = self.process_table.rowCount() + self.process_table.insertRow(row) + + # PID + pid_item = QTableWidgetItem(str(pid)) + pid_item.setTextAlignment(Qt.AlignCenter) + + # Name + name_item = QTableWidgetItem(info['name']) + + # CPU + cpu_item = QTableWidgetItem(f"{info['cpu']:.1f}%") + cpu_item.setTextAlignment(Qt.AlignCenter) + + # Memory + memory_item = QTableWidgetItem(f"{info['memory']:.1f} MB") + memory_item.setTextAlignment(Qt.AlignCenter) + + # Status + status_item = QTableWidgetItem(info['status']) + status_item.setTextAlignment(Qt.AlignCenter) + + # Path + path_item = QTableWidgetItem(info['path']) + + # Set items + self.process_table.setItem(row, 0, pid_item) + self.process_table.setItem(row, 1, name_item) + self.process_table.setItem(row, 2, cpu_item) + self.process_table.setItem(row, 3, memory_item) + self.process_table.setItem(row, 4, status_item) + self.process_table.setItem(row, 5, path_item) + + # Color coding based on resource usage + if info['cpu'] > 50: + self.color_row(row, QColor(240, 71, 71, 50)) # Red + elif info['memory'] > 1000: + self.color_row(row, QColor(250, 166, 26, 50)) # Orange + + self.process_table.setSortingEnabled(True) + + def filter_processes(self): + filtered = {} + search_text = self.search_input.text().lower() + + for pid, info in self.processes.items(): + # Apply regex/text filter + if self.regex_check.isChecked(): + try: + if not re.search(search_text, info['name'].lower()): + continue + except re.error: + continue + elif search_text and search_text not in info['name'].lower(): + continue + + # Apply type filter + if self.filter_combo.currentText() == 'User' and pid < 1000: + continue + elif self.filter_combo.currentText() == 'System' and pid >= 1000: + continue + elif self.filter_combo.currentText() == 'Android Apps' and not info['name'].startswith('com.'): + continue + + # Apply resource thresholds + if info['cpu'] < self.cpu_threshold.value(): + continue + if info['memory'] < self.memory_threshold.value(): + continue + + filtered[pid] = info + + return filtered + + def color_row(self, row, color): + for col in range(self.process_table.columnCount()): + item = self.process_table.item(row, col) + item.setBackground(color) + + def update_stats(self, total_cpu, total_memory): + self.process_count.setText(f"{len(self.processes)} processes") + self.cpu_usage.setText(f"CPU: {total_cpu:.1f}%") + self.memory_usage.setText(f"Memory: {total_memory:.0f} MB") + + def show_context_menu(self, position): + menu = QMenu() + + kill_action = QAction("Kill Process", self) + kill_action.triggered.connect(self.kill_selected_process) + + inject_action = QAction("Inject Script", self) + inject_action.triggered.connect(self.inject_into_selected) + + menu.addAction(kill_action) + menu.addAction(inject_action) + menu.exec_(self.process_table.mapToGlobal(position)) + + def kill_selected_process(self): + selected = self.process_table.selectedItems() + if selected: + pid = int(self.process_table.item(selected[0].row(), 0).text()) + try: + psutil.Process(pid).terminate() + self.refresh_processes() + except psutil.NoSuchProcess: + pass + + def inject_into_selected(self): + selected = self.process_table.selectedItems() + if selected: + pid = int(self.process_table.item(selected[0].row(), 0).text()) + self.process_selected.emit(pid) \ No newline at end of file diff --git a/src/gui/widgets/process_monitor.py b/src/gui/widgets/process_monitor.py new file mode 100644 index 0000000..fe5ae7e --- /dev/null +++ b/src/gui/widgets/process_monitor.py @@ -0,0 +1,335 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QLabel, QLineEdit, QTableWidget, QTableWidgetItem, + QMenu, QAction, QComboBox, QCheckBox, QFrame, + QHeaderView, QStyle, QStyledItemDelegate, QToolButton, QMessageBox) +from PyQt5.QtCore import Qt, pyqtSignal, QTimer, QSize +from PyQt5.QtGui import QColor, QFont, QIcon +import psutil +import frida +import re +import qtawesome as qta +from datetime import datetime +import subprocess + +class ProcessInfoDelegate(QStyledItemDelegate): + def paint(self, painter, option, index): + if index.column() in [2, 3]: # CPU and Memory columns + value = float(index.data().replace('%', '').replace('MB', '')) + if value > 80: + option.backgroundBrush = QColor('#f04747') + elif value > 50: + option.backgroundBrush = QColor('#faa61a') + super().paint(painter, option, index) + +class ProcessMonitor(QWidget): + def __init__(self, main_window=None): + QWidget.__init__(self) + self.processes = {} + self.current_device = None + self.main_window = main_window + self.setup_ui() + self.start_monitoring() + + def start_monitoring(self): + self.update_timer = QTimer() + self.update_timer.timeout.connect(self.refresh_processes) + self.update_timer.start(2000) # Update every 2 seconds + + def stop_monitoring(self): + if hasattr(self, 'update_timer'): + self.update_timer.stop() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Device selection + device_frame = QFrame() + device_frame.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 8px; + padding: 10px; + } + """) + device_layout = QHBoxLayout(device_frame) + + self.device_combo = QComboBox() + self.device_combo.currentIndexChanged.connect(self.on_device_changed) + + refresh_devices_btn = QPushButton(qta.icon('fa5s.sync'), "Refresh Devices") + refresh_devices_btn.clicked.connect(self.refresh_devices) + + device_layout.addWidget(QLabel("Device:")) + device_layout.addWidget(self.device_combo) + device_layout.addWidget(refresh_devices_btn) + + # Search and Filter Bar + filter_frame = QFrame() + filter_frame.setStyleSheet(""" + QFrame { + background-color: #2f3136; + border-radius: 8px; + padding: 10px; + } + """) + filter_layout = QHBoxLayout(filter_frame) + + # Process search with regex toggle + search_container = QFrame() + search_layout = QHBoxLayout(search_container) + + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("Filter processes (supports regex)") + self.search_input.textChanged.connect(self.apply_filters) + + self.regex_check = QCheckBox("Regex") + self.regex_check.toggled.connect(self.apply_filters) + + search_layout.addWidget(self.search_input) + search_layout.addWidget(self.regex_check) + + # Advanced filters + self.filter_combo = QComboBox() + self.filter_combo.addItems(['All', 'User', 'System', 'Android Apps', 'High CPU', 'High Memory']) + self.filter_combo.currentTextChanged.connect(self.apply_filters) + + filter_layout.addWidget(search_container) + filter_layout.addWidget(self.filter_combo) + + # Process Table + self.process_table = QTableWidget() + self.process_table.setColumnCount(8) + self.process_table.setHorizontalHeaderLabels([ + "PID", "Name", "CPU %", "Memory", "Status", "User", "Started", "Command Line" + ]) + + # Context menu + self.process_table.setContextMenuPolicy(Qt.CustomContextMenu) + self.process_table.customContextMenuRequested.connect(self.show_context_menu) + + # Action buttons + action_layout = QHBoxLayout() + + self.refresh_btn = QPushButton(qta.icon('fa5s.sync'), "Refresh") + self.refresh_btn.clicked.connect(self.refresh_processes) + + self.kill_btn = QPushButton(qta.icon('fa5s.stop'), "Kill") + self.kill_btn.clicked.connect(self.kill_selected_process) + + self.inject_btn = QPushButton(qta.icon('fa5s.syringe'), "Open in Injector") + self.inject_btn.clicked.connect(self.open_in_injector_clicked) + + action_layout.addWidget(self.refresh_btn) + action_layout.addWidget(self.kill_btn) + action_layout.addWidget(self.inject_btn) + action_layout.addStretch() + + # Add all components + layout.addWidget(device_frame) + layout.addWidget(filter_frame) + layout.addWidget(self.process_table) + layout.addLayout(action_layout) + + # Initial device scan + self.refresh_devices() + + def refresh_devices(self): + self.device_combo.clear() + try: + devices = frida.enumerate_devices() + for device in devices: + if device.type == 'usb': + self.device_combo.addItem(f"๐Ÿ“ฑ {device.name} (USB)", device.id) + elif device.type == 'remote': + self.device_combo.addItem(f"๐ŸŒ {device.name} (Remote)", device.id) + elif device.type == 'local': + self.device_combo.addItem(f"๐Ÿ’ป {device.name} (Local)", device.id) + except Exception as e: + print(f"Error enumerating devices: {e}") + + def on_device_changed(self, index): + if index >= 0: + self.current_device = self.device_combo.currentData() + self.refresh_processes() + + def show_context_menu(self, position): + menu = QMenu() + + kill_action = QAction("Kill Process", self) + kill_action.triggered.connect(self.kill_selected_process) + + inject_action = QAction("Open in Injector", self) + inject_action.triggered.connect(self.open_in_injector_clicked) + + details_action = QAction("Process Details", self) + details_action.triggered.connect(self.show_process_details) + + menu.addAction(kill_action) + menu.addAction(inject_action) + menu.addAction(details_action) + menu.exec_(self.process_table.mapToGlobal(position)) + + def open_in_injector_clicked(self): + """Handle click on 'Open in Injector' button""" + if not self.main_window: + return + + selected = self.process_table.selectedItems() + if selected: + row = selected[0].row() + pid = int(self.process_table.item(row, 0).text()) + if self.current_device: + self.main_window.open_in_injector(self.current_device, pid) + else: + QMessageBox.warning(self, "Error", "No device selected!") + + def refresh_processes(self): + self.process_table.setRowCount(0) + if not self.current_device: + return + + try: + device = frida.get_device(self.current_device) + + if device.type == 'local': + # For local processes, use psutil for more reliable info + for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_info', 'status', 'username', 'create_time', 'cmdline']): + try: + row = self.process_table.rowCount() + self.process_table.insertRow(row) + + # Get process info + pid = proc.pid + name = proc.name() + cpu = proc.cpu_percent() + memory = proc.memory_info().rss / 1024 / 1024 # Convert to MB + status = proc.status() + user = proc.username() + started = datetime.fromtimestamp(proc.create_time()).strftime('%Y-%m-%d %H:%M:%S') + cmdline = ' '.join(proc.cmdline()) + + # Create items + items = [ + QTableWidgetItem(str(pid)), + QTableWidgetItem(name), + QTableWidgetItem(f"{cpu:.1f}%"), + QTableWidgetItem(f"{memory:.1f} MB"), + QTableWidgetItem(status), + QTableWidgetItem(user), + QTableWidgetItem(started), + QTableWidgetItem(cmdline) + ] + + # Set alignment + items[0].setTextAlignment(Qt.AlignCenter) + items[2].setTextAlignment(Qt.AlignCenter) + items[3].setTextAlignment(Qt.AlignCenter) + items[4].setTextAlignment(Qt.AlignCenter) + + # Add items to row + for col, item in enumerate(items): + self.process_table.setItem(row, col, item) + + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + else: + # For ADB devices + try: + adb_output = subprocess.check_output( + ['adb', '-s', self.current_device, 'shell', 'ps'], + text=True + ).strip().split('\n') + + for line in adb_output[1:]: # Skip header + parts = line.split() + if len(parts) >= 9: + row = self.process_table.rowCount() + self.process_table.insertRow(row) + + pid = parts[1] + name = parts[-1] + + items = [ + QTableWidgetItem(pid), + QTableWidgetItem(name), + QTableWidgetItem("N/A"), + QTableWidgetItem("N/A"), + QTableWidgetItem(parts[7]), + QTableWidgetItem(parts[0]), + QTableWidgetItem("N/A"), + QTableWidgetItem("N/A") + ] + + for col, item in enumerate(items): + self.process_table.setItem(row, col, item) + + except subprocess.CalledProcessError as e: + print(f"ADB error: {e}") + + except Exception as e: + print(f"Error refreshing processes: {e}") + + def apply_filters(self): + search_text = self.search_input.text().lower() + filter_type = self.filter_combo.currentText() + use_regex = self.regex_check.isChecked() + + for row in range(self.process_table.rowCount()): + show_row = True + name = self.process_table.item(row, 1).text().lower() + pid = int(self.process_table.item(row, 0).text()) + + # Apply text filter + if search_text: + if use_regex: + try: + if not re.search(search_text, name): + show_row = False + except re.error: + show_row = False + elif search_text not in name: + show_row = False + + # Apply type filter + if filter_type == 'User' and pid < 1000: + show_row = False + elif filter_type == 'System' and pid >= 1000: + show_row = False + elif filter_type == 'Android Apps' and not name.startswith('com.'): + show_row = False + elif filter_type == 'High CPU': + cpu = float(self.process_table.item(row, 2).text().replace('%', '')) + if cpu < 50: + show_row = False + elif filter_type == 'High Memory': + memory = float(self.process_table.item(row, 3).text().replace('MB', '')) + if memory < 500: + show_row = False + + self.process_table.setRowHidden(row, not show_row) + + def kill_selected_process(self): + selected = self.process_table.selectedItems() + if selected: + row = selected[0].row() + pid = int(self.process_table.item(row, 0).text()) + try: + if self.current_device == 'local': + psutil.Process(pid).terminate() + else: + subprocess.run(['adb', '-s', self.current_device, 'shell', 'kill', str(pid)]) + self.refresh_processes() + except Exception as e: + print(f"Error killing process: {e}") + + def show_process_details(self): + selected = self.process_table.selectedItems() + if selected: + row = selected[0].row() + details = "\n".join([ + f"{self.process_table.horizontalHeaderItem(col).text()}: " + f"{self.process_table.item(row, col).text()}" + for col in range(self.process_table.columnCount()) + ]) + QMessageBox.information(self, "Process Details", details) \ No newline at end of file diff --git a/src/gui/widgets/process_panel.py b/src/gui/widgets/process_panel.py new file mode 100644 index 0000000..12aea20 --- /dev/null +++ b/src/gui/widgets/process_panel.py @@ -0,0 +1,59 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, + QComboBox, QPushButton, QLabel) +import frida +import subprocess + +class ProcessPanel(QWidget): + def __init__(self): + super().__init__() + self.setup_ui() + self.current_device_id = None + + def setup_ui(self): + layout = QHBoxLayout(self) + + self.process_combo = QComboBox() + self.refresh_button = QPushButton("Refresh Processes") + + layout.addWidget(QLabel("Select Process:")) + layout.addWidget(self.process_combo) + layout.addWidget(self.refresh_button) + + self.refresh_button.clicked.connect(self.refresh_processes) + + def update_device(self, device_id): + self.current_device_id = device_id + self.refresh_processes() + + def refresh_processes(self): + if not self.current_device_id: + return + + self.process_combo.clear() + try: + device = frida.get_device(self.current_device_id) + if device.type == 'local': + processes = device.enumerate_processes() + for process in processes: + self.process_combo.addItem( + f"{process.name} (PID: {process.pid})", + process.pid + ) + else: + # For ADB devices + output = subprocess.check_output( + ['adb', '-s', self.current_device_id, 'shell', 'ps'], + text=True + ).strip().split('\n') + + for line in output[1:]: # Skip header + parts = line.split() + if len(parts) >= 9: + pid = parts[1] + process_name = parts[-1] + self.process_combo.addItem( + f"{process_name} (PID: {pid})", + pid + ) + except Exception as e: + print(f"Error refreshing processes: {str(e)}") \ No newline at end of file diff --git a/src/gui/widgets/script_editor.py b/src/gui/widgets/script_editor.py new file mode 100644 index 0000000..813ebc6 --- /dev/null +++ b/src/gui/widgets/script_editor.py @@ -0,0 +1,25 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit + +class ScriptEditorPanel(QWidget): + def __init__(self): + super().__init__() + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + self.editor = QTextEdit() + self.editor.setPlaceholderText("Enter your Frida script here...") + + # Set default script template + self.editor.setPlainText('''Java.perform(function() { + console.log("Script loaded!"); +});''') + + layout.addWidget(self.editor) + + def get_script(self): + return self.editor.toPlainText() + + def set_script(self, script): + self.editor.setPlainText(script) \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..0fffbf7 --- /dev/null +++ b/src/main.py @@ -0,0 +1,70 @@ +import sys +import os +from pathlib import Path + +def setup_qt_environment(): + """Setup Qt environment variables and paths""" + try: + # Get the PyQt5 location + import PyQt5 + pyqt_path = Path(PyQt5.__file__).parent + + # Set environment variables + os.environ['QT_DEBUG_PLUGINS'] = '1' + os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = str(pyqt_path / 'Qt5' / 'plugins') + + # Print debug info + print(f"PyQt5 path: {pyqt_path}") + print(f"Plugin path: {os.environ['QT_QPA_PLATFORM_PLUGIN_PATH']}") + + # Verify plugin exists + cocoa_path = pyqt_path / 'Qt5' / 'plugins' / 'platforms' / 'libqcocoa.dylib' + if cocoa_path.exists(): + print(f"Found cocoa plugin at: {cocoa_path}") + else: + print(f"Warning: Could not find cocoa plugin at: {cocoa_path}") + + # Try alternate locations + alt_paths = [ + pyqt_path / 'Qt' / 'plugins' / 'platforms' / 'libqcocoa.dylib', + Path('/opt/anaconda3/plugins/platforms/libqcocoa.dylib'), + Path('/opt/anaconda3/lib/python3.11/site-packages/PyQt5/Qt/plugins/platforms/libqcocoa.dylib') + ] + + for path in alt_paths: + if path.exists(): + print(f"Found cocoa plugin in alternate location: {path}") + os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = str(path.parent.parent) + break + + except Exception as e: + print(f"Error setting up Qt environment: {e}") + +# Add project root to Python path +project_root = Path(__file__).parent +sys.path.append(str(project_root)) + +# Setup Qt environment before importing PyQt +setup_qt_environment() + +from PyQt5.QtWidgets import QApplication, QMessageBox +from gui.main_window import FridaInjectorMainWindow +from utils.themes import set_application_style + +def main(): + try: + # Create application + app = QApplication(sys.argv) + set_application_style(app) + + window = FridaInjectorMainWindow() + window.show() + + sys.exit(app.exec_()) + except Exception as e: + print(f"Error starting application: {e}") + QMessageBox.critical(None, "Error", f"Application failed to start: {str(e)}") + sys.exit(1) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/utils/__pycache__/themes.cpython-311.pyc b/src/utils/__pycache__/themes.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eac8e2a57d96b2a33ab67e058ecbb366df752940 GIT binary patch literal 6478 zcmcgwT}&I<6`rxL4VWJX2qbt3J0Zz}LyV2V1O|w-kRQl`ftYL(Rtg$CgPDyz^|EzrJ=?(g*haRAZGP6UTF16(%elZsff~E(52TeFEMFOQ8?YZJR_z>36xyRO0Mq0@aXXP@CG!Z z8^c4xqqq@?Mx!xkut}C>IcSWJj*pGA(BP7zrfI$NmEiCWF*%-VTf%E zuhv}F#5P1h32iVLKD|d%eF-ila@5_qzAMzn63?WsP%11u?aRrp z6J!GfBsJowB-4AAVOd^C(bQOWml_()?plMY8fJzULOVRGY|_+FB!cy-t zunRGgB<@&o@fjw?m8}+Y@VM-a*`Q=G&9jsu!RpI060C{RutSnLK`?D^QB=fiDPQiI z6)hvBpztjkSR-ckS71xJ!Sc0tliXbCFpdeJs-T?g8&2>ZWhZ{gq$=NkW<&uZXJpua z`ewu}iRUCL&h0=qBMKslZhNb67EH#-Buz!kVI4W4HyMFVb5+i&sWT=uuE0%eIGq#iT32l=VW`_a#Z#jqL{?1l;id`mz@v(H|4fZAsa;f_1Ftb2!YK z4@-UvTrV8H@*auVO6+Vaf>7}L&3Y_xUnK3tcdMq@Ksg!npaxM zgW;CzbBUS7JJ;7{7Uvi5By^uC#0OLzO*1kuf1HNvv;aHGgW5@%x3Meu{-lfaX|0!@ zZ>p_ftu?Hoh=wA06e)NKPyG+5<9pQc%_9}{YN$7ldjIPsPIv#B{tf+f{t%t|0d;+k zy8h7rEc|lxVDMG97LBU|i`u}Vik39Alt)V?N?IWFa{EAd^{F;GuZ9=2@Pdls8j9yp zyhQ6+=w;_Y%is33$buS-Yr(jR7B#e(M~fv&S|D=p(cgRi(V~stQ=|8_=zSGE(9nZC zdQhe{c;G$w>eYsJbwQ29wMY%FchRzjwXH>Ns=-@Y@Ro{ZH8h(?vn39#G@R94d2LC}i@Q(E_U@j|Pg zF7%R9{^Er;pt=ZDc`Bl{4izu8`J;u4b+JJ4QWuy+9j@Un;EH1J!vQcEalFR@;C%#v z_gD{DOk#^`S~p#s>hVt$CegJ(ajF*t*GViH)4E5BH^^Rp41k8Q0JH}{_^BZ@%EZ4|k5N;SkBdQbtGyn+VN|9G#lmh7tQv{$NfaZaoqDWpR z{XKSMvW5{FkR~8sbAO4&+lJU6-@(WPAUNAQ?k8CM)e-p< ejKlzOkD=ld_q5B8o;DU{Q73u6KvaJiYv=#}6YDMj literal 0 HcmV?d00001 diff --git a/src/utils/themes.py b/src/utils/themes.py new file mode 100644 index 0000000..4c808e4 --- /dev/null +++ b/src/utils/themes.py @@ -0,0 +1,172 @@ +from PyQt5.QtWidgets import QStyleFactory +from PyQt5.QtGui import QPalette, QColor +from PyQt5.QtCore import Qt + +# Discord-inspired color scheme +DISCORD_COLORS = { + 'background': '#36393f', + 'secondary_bg': '#2f3136', + 'tertiary_bg': '#202225', + 'text': '#dcddde', + 'secondary_text': '#96989d', + 'accent': '#ec695c', + 'accent_hover': '#4752c4', + 'red': '#ed4245', + 'green': '#3ba55c' +} + +STYLE_SHEET = """ +QMainWindow, QWidget { + background-color: """ + DISCORD_COLORS['background'] + """; + color: """ + DISCORD_COLORS['text'] + """; + font-family: 'Segoe UI', Arial, sans-serif; +} + +QTabWidget::pane { + border: none; + background-color: """ + DISCORD_COLORS['background'] + """; +} + +QTabWidget::tab-bar { + alignment: left; +} + +QTabBar::tab { + background-color: """ + DISCORD_COLORS['tertiary_bg'] + """; + color: """ + DISCORD_COLORS['secondary_text'] + """; + padding: 8px 16px; + border: none; + min-width: 100px; +} + +QTabBar::tab:selected { + background-color: """ + DISCORD_COLORS['background'] + """; + color: """ + DISCORD_COLORS['text'] + """; +} + +QTabBar::tab:hover:!selected { + background-color: """ + DISCORD_COLORS['secondary_bg'] + """; +} + +QPushButton { + background-color: """ + DISCORD_COLORS['accent'] + """; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + font-weight: bold; +} + +QPushButton:hover { + background-color: """ + DISCORD_COLORS['accent_hover'] + """; +} + +QPushButton:pressed { + background-color: """ + DISCORD_COLORS['accent'] + """; +} + +QComboBox { + background-color: """ + DISCORD_COLORS['tertiary_bg'] + """; + border: none; + border-radius: 4px; + padding: 6px 12px; + color: """ + DISCORD_COLORS['text'] + """; + min-width: 150px; +} + +QComboBox::drop-down { + border: none; + width: 20px; +} + +QComboBox::down-arrow { + image: none; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid """ + DISCORD_COLORS['text'] + """; + margin-right: 8px; +} + +QTextEdit { + background-color: """ + DISCORD_COLORS['tertiary_bg'] + """; + border: none; + border-radius: 4px; + padding: 8px; + color: """ + DISCORD_COLORS['text'] + """; + font-family: 'Consolas', 'Courier New', monospace; +} + +QLabel { + color: """ + DISCORD_COLORS['text'] + """; + font-weight: bold; +} + +QScrollBar:vertical { + border: none; + background-color: """ + DISCORD_COLORS['tertiary_bg'] + """; + width: 14px; + margin: 0; +} + +QScrollBar::handle:vertical { + background-color: """ + DISCORD_COLORS['secondary_bg'] + """; + min-height: 30px; + border-radius: 7px; +} + +QScrollBar::handle:vertical:hover { + background-color: """ + DISCORD_COLORS['accent'] + """; +} + +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical, +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + border: none; + background: none; + color: none; +} + +QListWidget { + background-color: """ + DISCORD_COLORS['tertiary_bg'] + """; + border: none; + border-radius: 4px; + padding: 4px; +} + +QListWidget::item { + padding: 8px; + border-radius: 4px; +} + +QListWidget::item:hover { + background-color: """ + DISCORD_COLORS['secondary_bg'] + """; +} + +QListWidget::item:selected { + background-color: """ + DISCORD_COLORS['accent'] + """; + color: white; +} +""" + +def set_application_style(app): + app.setStyle(QStyleFactory.create("Fusion")) + + # Set the custom style sheet + app.setStyleSheet(STYLE_SHEET) + + # Set up dark palette for system dialogs + dark_palette = QPalette() + dark_palette.setColor(QPalette.Window, QColor(DISCORD_COLORS['background'])) + dark_palette.setColor(QPalette.WindowText, QColor(DISCORD_COLORS['text'])) + dark_palette.setColor(QPalette.Base, QColor(DISCORD_COLORS['tertiary_bg'])) + dark_palette.setColor(QPalette.AlternateBase, QColor(DISCORD_COLORS['secondary_bg'])) + dark_palette.setColor(QPalette.ToolTipBase, QColor(DISCORD_COLORS['text'])) + dark_palette.setColor(QPalette.ToolTipText, QColor(DISCORD_COLORS['text'])) + dark_palette.setColor(QPalette.Text, QColor(DISCORD_COLORS['text'])) + dark_palette.setColor(QPalette.Button, QColor(DISCORD_COLORS['accent'])) + dark_palette.setColor(QPalette.ButtonText, Qt.white) + dark_palette.setColor(QPalette.BrightText, Qt.red) + dark_palette.setColor(QPalette.Link, QColor(DISCORD_COLORS['accent'])) + dark_palette.setColor(QPalette.Highlight, QColor(DISCORD_COLORS['accent'])) + dark_palette.setColor(QPalette.HighlightedText, Qt.white) + + app.setPalette(dark_palette) \ No newline at end of file From 230723487aec931a35f04db6144efb09a51835ff Mon Sep 17 00:00:00 2001 From: "squidward1.1" <90065237+gru122121@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:06:10 +0000 Subject: [PATCH 2/8] Create README.md --- README.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..13404a2 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ + + +# FridaGUI + +A modern and powerful GUI tool for Frida script management and injection, created by **Oliver Stankiewicz**. + +## ๐Ÿš€ Features +- ๐Ÿ” **Script Injection with Live Preview** +- ๐ŸŒ **CodeShare Browser & Integration** +- โญ **Favorites System** +- ๐Ÿ“ฑ **Android/iOS Device Support** +- ๐Ÿ’ป **Process Management** +- ๐ŸŽจ **Modern Dark Theme UI** +- ๐Ÿ“Š **Real-time Process Monitoring** +- ๐Ÿ“ **Script History Tracking** +- ๐Ÿ”„ **Auto-injection Support** + +--- + +## ๐Ÿ“ฅ Installation + +### Prerequisites +- **Python 3.8+** +- **Frida** +- **ADB** (for Android device support) + +### Steps +1. Clone the repository: + ```bash + git clone https://github.com/oliverstankiewicz/FridaGUI.git + cd FridaGUI + ``` + +2. Install dependencies: + ```bash + pip install -r requirements.txt + ``` + +3. Run the application: + ```bash + python src/main.py + ``` + +--- + +## ๐Ÿ“‚ Project Structure +``` +FridaGUI/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ gui/ +โ”‚ โ”‚ โ”œโ”€โ”€ widgets/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ device_panel.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ data_visualizer.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ history_page.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ injection_panel.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ output_panel.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ process_monitor.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ process_panel.py +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ script_editor.py +โ”‚ โ”‚ โ””โ”€โ”€ main_window.py +โ”‚ โ”œโ”€โ”€ utils/ +โ”‚ โ”‚ โ””โ”€โ”€ themes.py +โ”‚ โ””โ”€โ”€ main.py +โ”œโ”€โ”€ requirements.txt +โ”œโ”€โ”€ requirements-dev.txt +โ”œโ”€โ”€ LICENSE +โ””โ”€โ”€ README.md +``` + +--- + +## ๐Ÿงฉ Core Components + +### **Device Panel** +- USB/Network device support +- Android device detection +- Frida server management +- Process listing + +### **Script Editor** +- Code editing with syntax highlighting +- Script management and injection controls +- Real-time output monitoring + +### **Process Monitor** +- Real-time process list with filtering +- Memory tracking and auto-refresh + +### **Data Visualizer** +- Process data visualization +- Memory usage graphs and performance metrics +- Real-time updates + +### **History Page** +- Script history and injection logs +- Quick re-injection functionality +- Session tracking + +--- + +## ๐Ÿ“œ Dependencies +- Refer to the `requirements.txt` file for a complete list of dependencies. + +--- + +## ๐Ÿ“„ License +This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details. + +--- + +## ๐Ÿ‘ค Author +**Oliver Stankiewicz** + +--- + +## ๐Ÿค Contributing +Pull requests are welcome! For major changes, please open an issue first to discuss your ideas. + +--- + +## ๐Ÿ› ๏ธ Support +If you encounter any issues or have questions, please file an issue on [GitHub](https://github.com/oliverstankiewicz/FridaGUI/issues). From 4d405b14c4a4e578fb180f6fb8a33bad85249777 Mon Sep 17 00:00:00 2001 From: "squidward1.1" <90065237+gru122121@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:49:31 +0000 Subject: [PATCH 3/8] Create LICENSE --- LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From d49f2c71c0f8ca23f058904660528e899c467f9a Mon Sep 17 00:00:00 2001 From: "squidward1.1" <90065237+gru122121@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:50:05 +0000 Subject: [PATCH 4/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13404a2..d13cf48 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ FridaGUI/ --- ## ๐Ÿ“„ License -This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details. +This project is licensed under the ** agplv3 License**. See the [LICENSE](LICENSE) file for details. --- From b324b4590c1e78b84bef3dca3e5d72cad94ca315 Mon Sep 17 00:00:00 2001 From: "squidward1.1" <90065237+gru122121@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:50:31 +0000 Subject: [PATCH 5/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d13cf48..47ba9b8 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ FridaGUI/ --- ## ๐Ÿ“„ License -This project is licensed under the ** agplv3 License**. See the [LICENSE](LICENSE) file for details. +This project is licensed under the **agplv3 License**. See the [LICENSE](LICENSE) file for details. --- From fa1884f39fd10330bd70403efc5120e5bd8df256 Mon Sep 17 00:00:00 2001 From: ors1331 Date: Wed, 4 Dec 2024 11:51:44 +0000 Subject: [PATCH 6/8] fixed mem leak --- .gitignore | 38 +++++++ LICENSE | 21 ++++ README.md | 103 ++++++++++++++++++ .../history_manager.cpython-311.pyc | Bin 3422 -> 4370 bytes src/core/history_manager.py | 33 ++++-- src/core/process_monitor.py | 30 +++-- .../__pycache__/main_window.cpython-311.pyc | Bin 45587 -> 46628 bytes src/gui/main_window.py | 28 ++++- .../device_selector.cpython-311.pyc | Bin 16942 -> 17282 bytes src/gui/widgets/device_selector.py | 8 +- src/utils/__pycache__/themes.cpython-311.pyc | Bin 6478 -> 6478 bytes 11 files changed, 242 insertions(+), 19 deletions(-) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba6805a --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +ENV/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Application specific +.frida_gui/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f4fb59f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Oliver Stankiewicz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..13efbb4 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# FridaGUI + +A modern and powerful GUI tool for Frida script management and injection, created by Oliver Stankiewicz. + +## Features +- ๐Ÿ” Script Injection with Live Preview +- ๐ŸŒ CodeShare Browser & Integration +- โญ Favorites System +- ๐Ÿ“ฑ Android/iOS Device Support +- ๐Ÿ’ป Process Management +- ๐ŸŽจ Modern Dark Theme UI +- ๐Ÿ“Š Real-time Process Monitoring +- ๐Ÿ“ Script History Tracking +- ๐Ÿ”„ Auto-injection Support +- ๐Ÿงต Multi-threaded Performance + +## Installation + +### Prerequisites +- Python 3.8+ +- Frida +- ADB (for Android device support) + +### Setup + +Clone the repository +git clone https://github.com/oliverstankiewicz/FridaGUI.git +cd FridaGUI +Install dependencies +pip install -r requirements.txt +Run the application +python src/main.py +Structure +FridaGUI/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ gui/ +โ”‚ โ”‚ โ”œโ”€โ”€ widgets/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ device_panel.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ data_visualizer.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ history_page.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ injection_panel.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ output_panel.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ process_monitor.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ process_panel.py +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ script_editor.py +โ”‚ โ”‚ โ””โ”€โ”€ main_window.py +โ”‚ โ”œโ”€โ”€ utils/ +โ”‚ โ”‚ โ””โ”€โ”€ themes.py +โ”‚ โ””โ”€โ”€ main.py +โ”œโ”€โ”€ requirements.txt +โ”œโ”€โ”€ requirements-dev.txt +โ”œโ”€โ”€ LICENSE +โ””โ”€โ”€ README.md + + + +## Core Components + +### Device Panel +- USB/Network device support +- Android device detection +- Frida server management +- Process listing + +### Script Editor +- Code editing +- Script management +- Injection controls +- Output monitoring + +### Process Monitor +- Real-time process list +- Process filtering +- Memory tracking +- Auto-refresh + +### Data Visualizer +- Process data visualization +- Memory usage graphs +- Performance metrics +- Real-time updates + +### History Page +- Script history +- Injection logs +- Quick re-injection +- Session tracking + +## Dependencies +- See `requirements.txt` for main dependencies + + +## License +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Author +**Oliver Stankiewicz** + +## Contributing +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +## Support +If you encounter any issues or have questions, please file an issue on GitHub. \ No newline at end of file diff --git a/src/core/__pycache__/history_manager.cpython-311.pyc b/src/core/__pycache__/history_manager.cpython-311.pyc index 104c0d6a0fd4ac8ed2535d69915ab7593dc47db4..0dd5d4148a578294e3b7dcd2d686805dfbd35359 100644 GIT binary patch literal 4370 zcmbVP-ER}w6~FUkkL__BCl1&oPO^X$9F{og77$6cELpZ}>4I3nswEGdjxrP0;MnHQ zgaj*zq7{{Bg;XAp!fu1sPc#TB?c+*)S@IWnqLC7fl)h9*<)tzTD@CZ%_S`%6*b_T_ z=$-MIGxwbPG3TD&Imdquhl2>({P8O{#{CHWlf2X#UuAZ#0JDNrq%v7FgC-F}cs9#S zGW5w#vhd`x+zda-S7pMaz@iIC=4{}uGATGaS zmWsilX`E3N`NnLfD1GTzX6UA#&t?7vn}L(@%_#F(Ete8)Hm}=!K{39x`J4Gn&JJqx z1tq7>>Ke9dL*?mAR>NfLwkTJpv!!faQEP9{7@OC%?6gk4zW?2a=`Ucsp3Y}8ci?F# zxmy`+E;Ch3|B~$M!WX|v>v$?XmB(7TI-fk_oZL|1E-WHvav4LGiwC{OI#d&gu#Em2 zAj@dGr+4+jx1W9U+21-|9NbKeZKTG&|JY1@WTig(;r;TJ-elu$SrYX4u_j#=8&^Sioa|#I1~TRPd^n8eu8lx_Y?2S$r`)@cDw41NR31w|O!SN2)r8mQ(G(C-YNU!N`DADBw)aNb#6_fz3?YJcKjF zW;C5Re?K(b2@GxrQtaO2n~it~gM!`(p0bQ8tUoZa9f>}e`}5MDmL98CZ`zCuTan=k z;=`x6yAqF&T8ECWT{XK#tgev?;-aU&`}|dR-|Cst=U7|L5j(aP zThrF2*QQ_en6Xn<>{K~+s^W(cbQmRX$OLZ*srT*6?e6aGD{}-K?2&GC_s1Y=PgD>S zfIZg8wiGRQq}R>?f!maZEor#i>V}1OD;x}ir#=eC`RdF>8~Xd{b7#4iKMMi-@+?2W z`PVrP_;nvcct7Raf)gjX^?1j`Ft0bZU4p1V@d;c5KK+-kk>pQNC+Bb3%-m~YW|C<#IaSLU#RCp(b>)s1 zwGu_GEzT&nG&O^D2fuz`bIOcH0gOqI!iRtW9%p9?0Ac_)1u;$%Q7>7FqoBooq~kpx zIyv1MVr>Z))@s?R3A&9!VhUZVLf01<-j>=P{Q3#I>i;(KO=OL?j-4^%qgH(Mhihf& zqA6Xpq>I38N4p-X)`5YwPBVJKik_$-Haz}a^i_N8QQ*nZ$HwZSdFX_7=)~IX@1y1R zF|&QlY99lpB7h!MrsNG7_Lh)(-@Y70@BdQW{n1y&ECd@S;1G)0p|z26#}M46l(wXF zxz%06>@dOXI9TPYbK!|j^fEFy;p1MO2?4+E<0pdt^&m(1Fhlq@%6A4QM!EH52f^-1 z0r;s#tPy;X_nm(Ofr6|hX1(Q2>+;O2v%o-BV^+CEt}&06Tr|UCZd>5I_+mT-S>t%G zp2`*>@2cD_8b$B{D9+qG>`o4hcW3qN2$?L!*#S}^Ls>+8*|=NKY`>}*N+zqnrYVd< z$B&7LY%Z6dvjZ7DKb^-jisSWmP$@u4Q?uH%VRKN&;p3oMOIsmjiX=0cOpMcH4gmm& z?|O5X9Z*zNrh;O8PbPCE{V-Zz1hR}?CEi=@`MP(tcQY}vkr*)(A6SVGwqku-2YV}i zJ{p815e?oKDnS$oKL~G%{TpI`sml}xEODSL4uB_0k^9DjMKhGNLdg1>XE)r#ts2lMo@tgb9HZs z-KLnd#AI1a?pKvI8-f^t8e$L_FOtA0t|`1ed==p{pq^qe8Rf*$yXv=d%mOE zKMW&vQYC?BL^>aye{{)=9JV5d%i`hYUAo6bKA9X>37*ZZmGPaOGObPSmt|GU!rz3w zo2>T#9M;fD-k@e~S?JTvUQUI`e-4sxWZ4eN@=RWx%@SUc<=eAL){$V6H)-wXZ09t2 z9Rp&E1q?MNHtymNN#9u@&k&v3sTWCzz=V*hT>m4GW%sJEoX`WsR*eKj0V*t)#QXxp zZI^Teh5ib1Nt`9aTp}W0TZLtWBM|O|BX2l(z4u$E!^aTKX5#FU>@DR)9GYmIq#T;c zXR{iOCc5+Y&p)Rrx3D%%KL($NQT4BUCyqOI`vtxL0^)r79UxHmG0YZ9mc92D3N6!X e3k8#rnO+rsh=J`llQ*RAZ3_Y2*M9*x;iFst delta 1683 zcmaJ>U1(cX96#rtdv9*qq)B7j^dru6{aDg=U0YjbOD3djsEA>uDsBU7$i0i%nlzky zOF!t^B0f|ZXgxwJ>%*ROLy$fRB8cg;%yk4dE>!m7gKsI@LzxKv|2Jt(!<#&GP z>wkXdf6f;te`~G##_O#iP~!5%^e^&yT{DfXpK5)q-9u{tdU4ndN_ZyQhCz1{#~!Ct1!g1{%5!H6X+Wv%T9|)dRbl~w-s^5 zdva)rOsR*%bpoB`#520QSkr%%XY}M$dN#A@`7mQzGkl@{nmLn;y2P;5HH`r4Q_N>V_J!|lZH(49)7HO7Xamy_}XHDWB z8?)mIf=60d6~R&tt7 zZb-}09GxTODblzlmar*b3|`=ThKDikOfDU+ggzFIy1523aW{ai8uPGOlY6it&*Y7q zE#tOr8OPb~3-c)>Z)HGG@;sBX?lSJjuJo!+4WG$?hr?lxp>eOcu7p?ch|R|UtE40; z?!i4@{mr@0mOowI)Qcz1?D__ZzJU^vypg@emd(y$Yww-O-NwOU<6w!<`pEXgUfWAs zqqir%ifzS83=~JM!anwTicr;#v(CeogM)+PdR6}2*DO-%h`6PC&Oi3muer@Ji1wq= zc!d1e5D&3}&jYj&lH*}lAP~@EskJOCUvBFSb+|72}3UG0eHPH{mU+NE#W!E%O zbiE@!k^M}v8bwT7n5cE^wzX{Xx}j&dSvKAUG?&a8j`KVS5%6XJTh?c@d5(wATM^n2 zLI72-MYFrV?gf-2NPfp=@wR(xg?dM}>-YQ(U%EFtxBBkT?e_cr;a&f5(LW3X%=C_c zBNYH2y(8e`ajn|=FIM*Qf4Ux^sll4J-c?D#_hNjM75Zw&fse}ZbFRWU27HH5;5*de zYR&j*w&M?sN7zn80iME4IxI2}tUvi4V1rmxdOwo5mxWM6_3TdBHr^1HFzFsZ4b1z&G_c*uGUr5y6$UTBIIis!Xru@ zK2N|22H@-~24sVzA@nKKodVmS@IpmGQJRQKjdD??9jvg?4~d@+jDX zPYo{T!sQZCO~ia%D*@o#i{MN#i0oy6yM$x6@_kj1A>hx%NLG)&k|Zg;a7_WYXt%+3F-4tUL2PLztIUN=gZHL6U|xFjv{=f#j-Q{?6mPJe+3%Q j1M><1q%x)AXMcm(_WRG4}iP zE@Ej>I}}~2l!~pQl9m1ew@Dai$Uh}u*+``x`RFtpPNpMGw>$Z7>cH!+G3ebyd9!889eAR@$f}+9!s70ou z2<|eJ1Jh|SmL-K8p`t7mXk3&@ULrXv7R79yWW`x}ueJhvMwULXN&>N{dV($;=vrGB}9P z3k-P*uw>bd9liT+HPmJP%S`gNDjXn)K(_HEU%vY>j0v8{-s{ z(PAH)Bmr&>eG~8_yow)M*Q|AGN3a|Pbx3H<`^S|_JD&vYUb@98`#TZtT*J3(Ccfpoyj%=GV5kz1{;GH*Um!E%naK&v$9Xy!Dj1^_gBdjCZ;4Od=p?>5 zjOF7}CQYTu#_M)X{=UwDQEIcsdNw9;23=MU#t#_e=7f#bOd=Z@85n?wp}3KWf#Cx) sBO~JnrpX64f0?|0v(VWFaVX~pH*<=q%p2=ZmD)o#EDU3A?@gONMNMQsTz*HsVoRgZE zr%+m;P?TDnUs{x$TI{FEQe*&BQ)CY!lt6?SkkDi-k^oZ0Aj=erq=4it_GFOe(t;{6 zm(;S%%;m=>|wp z@;Zn7B@X$E9Ew*s6fbZn-jGx3V7bB}eF2P$q=EW1g(th&Wib_XOkQhe%=l*VL%Xv| rd@QWW9~kfvlMmYeW1O}5fP*fhV1Hj6iAZ Date: Wed, 4 Dec 2024 12:00:48 +0000 Subject: [PATCH 7/8] Update README.md --- README.md | 103 ------------------------------------------------------ 1 file changed, 103 deletions(-) diff --git a/README.md b/README.md index 910e24c..47ba9b8 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,3 @@ -<<<<<<< HEAD -# FridaGUI - -A modern and powerful GUI tool for Frida script management and injection, created by Oliver Stankiewicz. - -## Features -- ๐Ÿ” Script Injection with Live Preview -- ๐ŸŒ CodeShare Browser & Integration -- โญ Favorites System -- ๐Ÿ“ฑ Android/iOS Device Support -- ๐Ÿ’ป Process Management -- ๐ŸŽจ Modern Dark Theme UI -- ๐Ÿ“Š Real-time Process Monitoring -- ๐Ÿ“ Script History Tracking -- ๐Ÿ”„ Auto-injection Support -- ๐Ÿงต Multi-threaded Performance - -## Installation - -### Prerequisites -- Python 3.8+ -- Frida -- ADB (for Android device support) - -### Setup - -Clone the repository -git clone https://github.com/oliverstankiewicz/FridaGUI.git -cd FridaGUI -Install dependencies -pip install -r requirements.txt -Run the application -python src/main.py -Structure -FridaGUI/ -โ”œโ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ gui/ -โ”‚ โ”‚ โ”œโ”€โ”€ widgets/ -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ device_panel.py -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ data_visualizer.py -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ history_page.py -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ injection_panel.py -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ output_panel.py -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ process_monitor.py -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ process_panel.py -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ script_editor.py -โ”‚ โ”‚ โ””โ”€โ”€ main_window.py -โ”‚ โ”œโ”€โ”€ utils/ -โ”‚ โ”‚ โ””โ”€โ”€ themes.py -โ”‚ โ””โ”€โ”€ main.py -======= # FridaGUI @@ -112,19 +61,10 @@ FridaGUI/ โ”‚ โ”œโ”€โ”€ utils/ โ”‚ โ”‚ โ””โ”€โ”€ themes.py โ”‚ โ””โ”€โ”€ main.py ->>>>>>> b324b4590c1e78b84bef3dca3e5d72cad94ca315 โ”œโ”€โ”€ requirements.txt โ”œโ”€โ”€ requirements-dev.txt โ”œโ”€โ”€ LICENSE โ””โ”€โ”€ README.md -<<<<<<< HEAD - - - -## Core Components - -### Device Panel -======= ``` --- @@ -132,53 +72,11 @@ FridaGUI/ ## ๐Ÿงฉ Core Components ### **Device Panel** ->>>>>>> b324b4590c1e78b84bef3dca3e5d72cad94ca315 - USB/Network device support - Android device detection - Frida server management - Process listing -<<<<<<< HEAD -### Script Editor -- Code editing -- Script management -- Injection controls -- Output monitoring - -### Process Monitor -- Real-time process list -- Process filtering -- Memory tracking -- Auto-refresh - -### Data Visualizer -- Process data visualization -- Memory usage graphs -- Performance metrics -- Real-time updates - -### History Page -- Script history -- Injection logs -- Quick re-injection -- Session tracking - -## Dependencies -- See `requirements.txt` for main dependencies - - -## License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## Author -**Oliver Stankiewicz** - -## Contributing -Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. - -## Support -If you encounter any issues or have questions, please file an issue on GitHub. -======= ### **Script Editor** - Code editing with syntax highlighting - Script management and injection controls @@ -222,4 +120,3 @@ Pull requests are welcome! For major changes, please open an issue first to disc ## ๐Ÿ› ๏ธ Support If you encounter any issues or have questions, please file an issue on [GitHub](https://github.com/oliverstankiewicz/FridaGUI/issues). ->>>>>>> b324b4590c1e78b84bef3dca3e5d72cad94ca315 From 90437b7f2463e114275f10cf4008ffe606d2642f Mon Sep 17 00:00:00 2001 From: "squidward1.1" <90065237+gru122121@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:02:25 +0000 Subject: [PATCH 8/8] Update LICENSE --- LICENSE | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/LICENSE b/LICENSE index 1eb5424..0ad25db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,26 +1,3 @@ -<<<<<<< HEAD -MIT License - -Copyright (c) 2024 Oliver Stankiewicz - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -======= GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 @@ -682,4 +659,3 @@ specific requirements. if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ->>>>>>> b324b4590c1e78b84bef3dca3e5d72cad94ca315