From cd0d3cda81035f1cefeac5203571f6fb9dd4961f Mon Sep 17 00:00:00 2001 From: Jerry Date: Fri, 7 Oct 2022 22:28:54 +0800 Subject: [PATCH 01/19] Threading and bugfix --- ...03c9cae7-bb8b-4dde-8e02-21559ce9a673.vsidx | Bin 0 -> 7999 bytes ...3f6c12dc-d97b-4c96-a8e9-b8679d5bdf7a.vsidx | Bin 0 -> 9552 bytes ...a542ab1e-96c7-4d8a-a9e3-de87b50797b1.vsidx | Bin 0 -> 127 bytes ...ee1eb762-a914-489a-9f64-332ab6947758.vsidx | Bin 0 -> 43806 bytes .vs/Finger/FileContentIndex/read.lock | 0 .vs/Finger/v17/.suo | Bin 0 -> 14848 bytes .vs/ProjectSettings.json | 3 + .vs/VSWorkspaceState.json | 7 ++ .vs/slnx.sqlite | Bin 0 -> 90112 bytes finger_plugin.py | 60 +++++++-- finger_sdk/__init__.py | 1 + finger_sdk/client.py | 108 ++++++++++++++++ finger_sdk/ida_func.py | 118 ++++++++++++++++++ 13 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 .vs/Finger/FileContentIndex/03c9cae7-bb8b-4dde-8e02-21559ce9a673.vsidx create mode 100644 .vs/Finger/FileContentIndex/3f6c12dc-d97b-4c96-a8e9-b8679d5bdf7a.vsidx create mode 100644 .vs/Finger/FileContentIndex/a542ab1e-96c7-4d8a-a9e3-de87b50797b1.vsidx create mode 100644 .vs/Finger/FileContentIndex/ee1eb762-a914-489a-9f64-332ab6947758.vsidx create mode 100644 .vs/Finger/FileContentIndex/read.lock create mode 100644 .vs/Finger/v17/.suo create mode 100644 .vs/ProjectSettings.json create mode 100644 .vs/VSWorkspaceState.json create mode 100644 .vs/slnx.sqlite create mode 100644 finger_sdk/__init__.py create mode 100644 finger_sdk/client.py create mode 100644 finger_sdk/ida_func.py diff --git a/.vs/Finger/FileContentIndex/03c9cae7-bb8b-4dde-8e02-21559ce9a673.vsidx b/.vs/Finger/FileContentIndex/03c9cae7-bb8b-4dde-8e02-21559ce9a673.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..be69fd4a40c20d0f763a18dd03f291141c816e4e GIT binary patch literal 7999 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 literal 0 HcmV?d00001 diff --git a/.vs/Finger/FileContentIndex/a542ab1e-96c7-4d8a-a9e3-de87b50797b1.vsidx b/.vs/Finger/FileContentIndex/a542ab1e-96c7-4d8a-a9e3-de87b50797b1.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..7c6fa3dd0258e023a151e9f882aa0387162e187d GIT binary patch literal 127 zcmZ>Eac5>=U8UU;V B0viAT literal 0 HcmV?d00001 diff --git a/.vs/Finger/FileContentIndex/ee1eb762-a914-489a-9f64-332ab6947758.vsidx b/.vs/Finger/FileContentIndex/ee1eb762-a914-489a-9f64-332ab6947758.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..03eedfad6d66701c2a757b791e027281feb90803 GIT binary patch literal 43806 zcmai*378~B(f3zw1qGDh5(FBT>4u(~on2U9mc_}g%I?mc>Z;m0rl)rpT0}%t4naUg z4n;-eR8bK{QA7bnK|JsP6i-x85ETVcybuLB6crWuBL4M%yViZa_j&he>X(rj85t3o zm6cVy_(5mmB}|3ifYPDu1G{It*t1vc z8JVT^_R_jrS`U}j)1^UesWnU6#nR9&&1y@t@uk`H_|o?H`1JVr;WZ znJzT5d}1P>m}*Y=Zo&^I{CL97PqfpC&`gA#iO@|%@kC-Ll42sY6IncwcPDbW4ki|t zPAo2)SZsT>hPTY~jPIGyGl^$1&*WaK@A<~_yPoenKlJ?A^HZ-Kd+oyOSg#X#UF&sY zubX==@Ycj$ujBPHFEU=7dP(Rdk(U%+GVs=B-rB-Tt(VC!^0M5^yI$_RLVk%?q~4tM z`oBReGP9HY?qt8etTDZ8+5XFxO)uNuTQT;{@&Dq2Tm>4rxqJii_PWU{>!~&`Lbop z7wap$WW{2AUoY8rvA(j=SlO6f+45Efo{YA?H!lMp&~6{NvU%VEjROy8A9&!(>B-6I z)E?B$4~on|adc3U9yGA4C#F^}Ypjl9Q*W4t{HBdbOsi>Ho@p7=vZm!sD>SXxv@+8w zjPD!Y8oz6NXZ%^?uQh&Z{9M;&;F-XfATWV7fippDg3JVSCK#AtXxi4a+oqkHjxil; zIvvw-rjwdZW?X1o&$!6A)VMjbCN{Iy%sLY`OxQHxgb7!f(3-GqLa7W*7@IIPVP?YI zgnbhZO|NEpvd=mvGvL@=7$eAcI zk<{lVDoivmN!=vYBwdp@lO!g|O)@ZP)1=cTJ=mniq`pZ5lUkE@OzKQJYtqo9sYx@F z<|ZAO%$lrgGH0?`ll4p%n=CU~ZnDDU4U;!zIh%ai)tn!acH#`H5YXqtg%2G$Im8T8B`GsCtSCT1vwIZR!vW&D;& zTPACnycM{X^;_1qY`0~l9JXxKvT4g^Et|LOpyh&=3tKK~xxBT;wnF*2Ryb(IK`Rbh zao&oHRy@~==UYkIN=++utu$_Bww1{>Z{_V)k+q7v)sOso!>?Pvp7{0DZ%q3Wq3`*= z*YUl~H!a_Ul6}dbZ$!*}Q|N;7t?$RaU--7>+lFtOzV&>&N*9`K`8M#a^KH+!k#7^< zroPQ|Cy+JRwSL$6-N^68es`!#()qp%eAo6}=)1^wiSJV1WxmUOH|NjR{n>^;+w^BW zUA(i-pN;(4p&uGQw0`LPF!sa34+nl6_;JUNLqG2MapcE|AE$nt`ElXLbN<@GPpzLi zKaKsY;b%=h^Zabu&ldc==I2d6_x#-Yx%2bT&tpGN{XFyY!q4aZBJ_)%U&MZq`bFj! zxj$_CL+1}8f0+5hTpkxetr^s&WfKqTYlC_oG@7!h1(R`LntF$sR$zLvf&-JtLJUkE zm?AKPzzhT53j!MiE(p@V)&koMtQXknz^>Ap()!wHZD8Ah4FlT?Y!uieu%es=HV^Dv z(6NE@11HyZ;KIN~flC9I1uhR<5xBWvwi(P0gU|+XGl=~lmO?LxlOQgFxF4i8$ZA2> z3(@I~aC@VH6A#J3eje4O^eG^~BcK%412M zNw(g%jfQPBZDZQX)51%vx7JS9^r2@O)--i>=w2f`i|iM2tII7dx24>ExosKiPgq~B zR+fnMo%LsBf~_CRtXRLWLCppY8#Ha;*U|%Ya?qDYg218 zYjfMSw%xXEXWOxDr?xFMh3y#Iv9{xE*Vt}sU1VKiU20u!-JA`ZHZ(T$ZD?)SwxP3O zXv5fsi48Lw4s5S!d&c%!I$F=!USxZ*?IpIC*{E(KqibL0N*syWHtN{O*(kP=wB|M{ zY&2)1fsKYXE^It!6QlknsZBDQ9kFaZUb^lNpNY~ zrX8C)oAzuP*)+CkV$;;7nN4$>ijg^+_H8<_=}?YQHXFBD!)8sJc{ZE0*($j&HA9)R zS!}b!W|_@$n-w-&uyR_-8#Zs++_Sl{xi5*ddDrI7<~^H7HqUHc*t~D^p)IVPGj>j{ z&d$ZQ@9ey>^S+(8c0RH5xgFH(plJuj4y+wGJBaNdvqNKtZ9DAQVb-p<+l}dVv)%Sg z+woSwZF+5!wauJ9udJ_Atr?fp zWNn+v6IExv?b_|Q?M&PGx(Zy_c2V12|c(j>C4Gw&S9m z8eK1`Yo~ELYwNlnGpvr3*fdB@f} zw$ZUoJ@x34?a5M*IqTT0WAjcY?R3LVH}AMs$N3!>bzIhQbDhw3!a*l8oyc|~*NKWw zJl9FWPU`6sIJKRu(aF3{wz`vdJB8^Kt~2a+8_jNGy4#%UHrw6Fq`NxmnxJdKu8~42 zPdIsWb!>;XhZPc}C*Jd)iT|4NymTuB6=*rFEI$alaUEFm^*QH&T zbwk&U;%*#t z&3oNE>*jg4$hw1x?jY$7vhL7yhl$%;PMmTQl@p$EUQ5o7&g(eOIj`ru#CfUnGCln1 zDM!v0avI28%ea=4ho3y*T&r+?)A>doKN^AWRB~6yT9*ag*Ry%MDQn2JozwZ2IhMpYnHblN-K=$?r)Q#2 z3eJV03o{oME*$77sb^i!xnAOWnd=pDz?TEKi#!)i>H$igc(P&3-XrT#CO>j6nsrg; zqTEHIJm;dJi$fPjE>2xsxTNWl)h=nf#JMDPNv=;z+3#iFN}Wr4E{$E9x-@fH&1Fp| zhc$VYX4YkGnL3v_m(98?aarmz(aBw2cX`9*O(!SueA?x!TyAs=%)2gkE)QMab9v9fs{j*lvbxPxgyYZa-afvd&~l z1^Pg6?a*~XHygTM=%UbNlC01T!mt^Jo*sL{mOeCOIfQW-W??wcgRmS};!YSxVVs0< z8pc@|7h&8F<3X6z!lW6d@`Uk1c}J1wOcv{rIk#cn4RaUfS(p#PA_$94SoFdo4T~%+ z@=%^va;zRSWyOVyjc}Ou#ouCahS!^z=uNhJ#_yS+XF9qh&6=K>?U}G=qMk{5 zCY4if&*XA2)dyP3^;%)C70Wwl&o+9t*|VM=%x$30Ive(E)U(nmPn}Gj1ig+dJHO{T zJs0*|)N`4vm|l3Wp5lA9*K@sI-0Ky+$o1l&7e~D~?ZsIyp3?)q+!jgLOX6OV_mZNQ z)p}W@mo?>Gu$N8uvQ96{dqvnQqF#~pf3`)zhe+ zMe^!9;YSlTnrKH8Ni-oZjkU=0<$W^pQh9-lynZy;(xOdR>m$oKRauBXya4wR9fMZPr20jM4Y%_#7qU{c>p^h_oNV>XiID+@6S2QovFXc5->sN3sfv zBr1l{oY51IY;S``G-yTxDa6qrj}{xzVl!IIqoE%S134c>!*(?6M#Ek-jH6)|4dv@Y zB5$g3Yfapeo5{#~NbK9#cd;MK89fec9Jn~h;y}uB&dB0+GuFp{9(P>aiQ-NgcLwr2 zi(L>q*<$Ll%fw+V4jXaUj6*LDr{mDbW)X*0?%OzwW88;vSj6F694^MaM%-)0JumJ} z#yu0`R*8E~R=KR>xF;#A#nE^i)#IoUN6k2zh$AnKCgW&%9Ic9@)p2C>9Yt4+EQO9f zLS=)OEdg^T8J7&k(VPSv$IUqQ;&@h^h~q3yOq|#_adFa%lQd59I4R;}AfLYCbX>j_ z#c4B64~|n4r!9Q}N`pAHaoW+RM>?zfjof@`5~pdLW^tOw=^)PbiL+*$**J4?mc>~f z=gl~uh;yqC^W4SxY@Ek&-j9n~Tr}gt)0azmdyrF!Y+XeZ7inB%agoP!Mh>&_oQwO- zxbMaN>9}v>zKF8JMe%$so_F#5ARd@_VB=vg9wtexk=_JO< z3YGWy#H5MI5|bxpAg@pI?jf%n`o?el#0Gk5wvn8A6PqXPDCstnZYJjmIV&X2Pn=C$ zn7AZysXX(Nu&LjcLX(76c5~T(lTa#)Bpf6?n?xObqQ+4Y$4Q(>j*>V_;yj7_NzzCX znLSx+AS>8wj8Q`zM5Y;r|5xoPnqA4?=P7j%GZ$0Pvx6M7R+Yyjodae+td%Mb~>}G^fQ?CGx;8% zug&eucJ%;)7g~8K&ForDjJ_h~@-ikbS6REr+Jmf9&$kT*fMMoZne#Ij zWUeEpUVW0t)}klQxy-H86XD;Gm~k|vV0-Shw?oy zD_U9M>kDqt&I*?mVJ3%;B9VE9fjArVvO$(Dw6g`59U@OW`99Rl$K`{2Ew7K~^@g68 z>!CgX^(%ay*BA1Jmp7*KW;<`L%_qWqBG>PQ@;%8*)faD=ua?JSE7z~Ca=4z=1D$@p zlx3CYem9pdO175UdTyJ!_4E^?e)0(PZClQLwx`ck{U{)>^b*!wuCLR>TwXrAjlA2` z&*9z4yt`UIOzQiaoZMyGmenfnT}7_1Qu>ahU$*pprk%Tv9wc2)pVhLp;}t?vrRV#( zeAedSK;Q3sp}ykl7dEe-Vguau9Og`Vp=e>L| zo(~%NpecXVkPk#$l@F49!R3p2E|W5puMX{8&LhJ_CN>{RWj`Mdi<(!|d-{NqcVZLD z2jaq{MJp&;Yl>E=dvi-fSq1WGPd{H4epkNn6@is6WreL5wozD5J}2pE#TG@|6zz7= z=@uPVxNf2EZ*s@W!V%x+3O6jmW)Yeq42sa|SAj4r!o27;ie9tmwThlCaHuVMb464y zqGl0!MP!P|FK|pOA}23KMHK4sHj0WUkuz2iqEnlW(k;#gY1tCvqc`g-sR^&x7 zlrPruCKu{)%;a;nsbBuAyidsKO!l*E&JO0BFW&<7yN(Oxye7|f+2rP2Hs|s=H#Zmi zbFq_C(OjJN$GyJS=^NKKQQxF}ll9G@Z=3oZ(b~Seqv+SDxqjF5ySCqTef>-@kWY+# z7xrD$cS+x6x&%T!6ZfO69}W9rHZJ<9>t{v3@Ador`IEy!ucef&zo}R%n!x~5;XwWt zK|ajeKt7Y{IW)BL7BuMDL1gqy8W)4a%6Vdt$n(PuvgRP09%NF;2YGi;MC(kx&eqpi zZ=Ic7XM=UNz0PLq>~Nin7WP?Om{?p`wzx30xUhV2Va4LYzKaX(V!%+V;)Xhuo_Q7z@4*PC6tPh9o#aZ{1asRZ_PMgxd z-IYK4$bZ4~A<6N_zT$|(Uvt7C?W2x4@`&RPc^NP~?o}rqdDJo9aj!di{K~zXKfn7K z{~y((jy~+jBThJE$~*3uBh}(pPW-=Am;aCIZf9Nfe^_1dKdK$~iT^`&-~UmaT)5_c zQd6H zxf{7Vxd&MjY7SmVc`3OUIZoEe2Dvw>i&4jEk`tsyPLj*WDRMcvg4~zfk6cOaPaZ%X zNKTWh$b-n$L!Ky9J10|qE%`d~_2en!8^}}1)5z1wGsriRZz9hm-%P%Rd@Fet z`8M)w^6lg~YGFL^QfKJpUs{p1J8OUVzCA0jU! zKTLjvyqx?fc?J0~^5f){GmHaOGJ@WhHZRG9b56B;qcaV3I zKO*lUe@y;_{3&_2Q16GIQ@)41m;42JANfn6>fcZK*W_==-;%#0e@{L@{(*du{3H1% z@*(nJ^3UWW@lKdz6FY@2yQ{;chr%BzURSOOFox;9=V-R=VyD$JCHk)JCQq+&nI^w zcO_py?ndrT?jh7aN-fsNJ;@i6OUb>+aiRLvpu9J^57}h9M=m3mvwa2S{V1;__hi^;>tmk8CbBgmJt{wVSlLiOiWl#eElA&(`GBVSD(Po6-&hCGox zi9A`TEs((|0eQGq4s|(<+FsUe>UZ}ljo4qrY@<+)l$d8d9C$AK$ovX=DlAmJx zr^#!{>&VZL*9$dIpBJi~8(9BE@=L7$3gsKgo5-8l{x$OJLbdlTwtt)Tx02su{rAb+ z$lJ*u2-S}}DBnr`h`fvZG5Hhnr{vw_&&Z#X_mKCJzaZ}us=xPB{x$g<^0(yg$lsF> zkbfW_B>zbMiF}BBnEW&O2>GZ`<9(d+U&z0bPmq5j|4#mcd{U_W|3&%VywM`9jw3MR}a83w8eYq1+V8Z;3~Fl3Yekk;};y?rU`F`>P>|`BtIk{rjxHjrBhu?_m9%!q8CU;@`uH*~I-N@aA+OI}=Ps&Rv?sAJIZdu24UDArbV88hJW-hERS>&LrQ;`m@Nl zk!K5aymKg@OP)ubPhLP?C{+D-k{6NhVf%Z@i&=jO<@Zy*l>89uFC#xpeuTW7{3v+^ z`7xo6e!OMXtMcD_LQ2J(xn|1#yTkT;SykvEfH6{?+E zD1V*&hERS>zD@Z%c=$YRg_m#K3J%B3>mQAl5Mg>c1cIBA!o^u z?2!=}lL@(&Ov#MQ$%33C`{cY({ap}hT#Mw8?Jp(|WBuWjUrHV!)c!B0e3Ve_yplYc z^~aFMlE(?P-wBjoL!L;UM4l{Ed#|JXdh!(V4dkihY2@kT8RQ$uH<4$OZzkVDzLh+S zd>eVTQ2jWE^10-B)%cJy_7GedesRD0J@{uKFX@>=pbq3T^v`LmQiPx%Yv4dfTeFA25ZS18{| z-bCI^ewF+hc?2LPlUtC_B%dWzy{#xe zn|uzrHMxyY{d*qe?S$HId&)bIJCZw*JCn~RcM+x zeUq|BE@S-^xtv_V_WdZYqbNb+fl%{pDR)T6`ZeS%8InDr+DRy{ zB~vmZbFv`k$UZqw4#;&v&D$ZA7s*4(A^BqRF!Ckj;X>_y1m%~JN0KjR`zt8Fl6)0; zG+i{zKcFOy#( zZzOLbZx(7kZ=w8k@*Ctg$#0S07OMZZlHX(f_sQGH+sPl0KP2xU?-Z(?yU3pi)sLT1 zzDKC{^}XaT$ot4&3bp_JO8HX zJWGaTPpJK4q3Wfq&&ZrC$T_l4&XWUjolxy8Qa+R%k}oC?BVR%uPQH{pf_xcyB>8gk zDDo9T_5W3rk0y^Hk0p;2s^0OGPoR7v<&(&h$=8yv6KcOx$WvK=8hJW-2Kh$vP2`#6 zo5{D3ZxyQDw^2Tu@;Q{xCC?+zCodo`B;P^4lYAF>5&3TNJ>+}Ii^=zqmyqu#KR{ke zevteSc^Ua(@+0Ks<-sdQPp8NuN z1NlYrOF~_jH&VWdyqWwe`8Dzu^6TU`$ZwM066*Nhp?oX(UGjTuzm4+kZ`MCW{)c>;T(UWzPvl1A#^fgCra~QObIMy# zeir2|DLvF66G{3&`Eb-N`-3no#|IA?2my zUgS7gCmZD6LhaY2JVAQoB-^LR736+wUrFxI`U5FXldH&s$kpV*tmym~( zFC~v4Uq&8DzMMRYdN2;PmUGp_^)RD@#G2QYuJ7g<&(+Rvi|jyPoaD&c{=OQ zAm2#7i9C~hvrzpwOQ?RF&HA^K=aA=;=aJ`=7myc{?;zhvzKgtwe78`?c`xOQ$@j7T z{p6*r{~+bdgzD$ztp6zGk5j%>O@4y>BzcWc{rEKHYsu@#&yd%XpCvyh zR6lMIYX2{>{>$W7$Q#L<$eV@Q?-rr<`v&X3N%`B9zeC@lKdz6FY@2yQ{;chr-d5dMq8BYXJettn^E3e zsQy2b+>-TMQGO2Pt;uc3ZOP{fwf}aM$H?u;9mpNYoyeWZ=aajTyOJ*;cO!Qv_aJNJ zp5zP3rQ}}ZI9V5J-1|^&3e|6q@+7&8oFbQ#E69C?+HWQ0{mBE!1IcM}6?qW3nmm|% z5jjH|vPJr2Kw7dbR6n}p8Zu;iPpI)Gl-H6enUOhJkaJ{TsQuRob(}+3zepZR4#^h_ zRqrK2?RNz0Uq&9u_M^yGl1G!r3Uz-wj(j!iPoVr7@f`D5}YC4WKQNB)xh6?s4TYw|bbZ^_>Y)$Rk7|3E%S z{*mnuQGS^GGx-SlDES!qIQbXyujCWt-^jm{{~(_v|4IIf{5ScOQ1ht&BdpHPGsum| zjfEP|rj$1$Hz&6spGiK8+)}9ho=y2Va5^$q0~=?higQf`wSvP(L04LM7OWRHxtzCqsUi~uOwe3)Oe1ed@Ok!`D*fb@&uvk zpGf&6%CDvTI`Z}8DdZc-Q_0hWYUd2fZ=`%CT@#MZS$Zn|!-a*Ts31&nGV+ zFC^a~RK1IYI{tgei`o7@@)Fj6fbyl3KScR5^24mZobpG>E69(LA1ALQuM+C`pP>9n z@*46}b?DB7egApOSZzKO=um-Xqk0zo2}dQ1f#? z>wm-g-;%#${R5Q$Kt4$Rk^B?+kWlSCLithhG4gStj{jH6PY6~2AC#XY|4IH!sQOP) z{tx*yx#U@V{*W7y8PK4ep<<9R}zhbh)CCs&aB z3RQ0<<^6@KKTUZRc@VjpJeYhDIU`g%Ey_L_kd|x zAym74%JY=hk%y3n3N^mNgnGVt33)jAQt}9)+B=f+%gLiy|4PcQB9A7IA&(`GBVSD( zFVyi*qC(j_?$o4bIx3K=Lf(O+n~HRxewVS zCrFQ+B$ts>LbbDk^1kGLtlytJP^fu2i0!M%gUJ^O)s7*3)(4~|+hm9Al8#(M&XOV7 zBO@{<6LPIk$ImF|WFb_)=P3`!b>sqh2)RfeN)Cl;_b~Es*1wcIg7rs|N0F~&`>V*K zS${0$3E#>RT&yd%XpCvyhRQ)edzJcm{8Nb~o9Ja5z0BYug(ZPr-d(LqL>TH}l_@DnRr= zE9xo{L!u5PG*l`mZKFnUflAc_b%C}PdV%tomU4Ah|0{#3Qn#H}BGe6{tOhDGqLmKqZOR1uDDB%L{aWVhh08`ZrB3|fKX+mA) zeXe6*G+2fnN>%<~0G1O~MgzoBMLmGMYWAz80{>thu_2RHsZ0qB=%va5yxPDah|kdc zp#&9-3;$-Me#u(BzzSTO8Hr;Z%GIPI zESERk$nkO~RI%C2pj4B!>?M#>l$K|`fn0tibl)e2@PiSof$y z-!Z+;s50_W4z40bLx5NjXvMe)rrefPZ-e_1!Bn1N$z!xK4jm1xiI$M@ssY|(3fE!k-%J9*x9B5P*XlX_-a1z0kj;pEC%BX|}Ob;56I=BX-$V=6VlvJZt zMyr{&YPn39dH_@F)oCk&LEmcLP%1se5*YP`GOXZfUoIq7fpV1+cm=T>d^FH@D2*72P9+%2q>8|>RI_&bBs#uPpC3t{PQ7S{wA<<(@Ro0t4k=S=M0odxTHXvvy!ybG= zDHz#9CQ(kc^QmLY?h|OpN z_F|MUBpP6)T5F{Tm3ef-MNPqSmCmXJdu8fIxvIuM_@$F-1$i!KRV|~N)rJ|pAZukx zv=s?M9ZFT?iMGOf*u%;mQv)cixB03bl8q#mPO4t64X`|;;@D{3s<6tOYJ*kf8Tqdq z5~V7|Fb|)r9_6dT$jNftkyvFMVZk15y6XOg3R+*#2M{@m55}cgdQ*t$KgRa3#weXhP0PEt9GfJ zgn5hx|L{PA`7$r6TxM5GfnKnbQ7WfV>ktQwV#xKzmB#hlSnt82-O4#v738H7VXsm~ zfVdH18PiiuEtC;Qsi&5)?yKoWH?)>VL>;mmLrcLZEaU*nxWFOYGYA^lg&XJ#u}Th9 z&;t#~1}>;88+5d1=%t#~Y8uh7t+rweG+-*wfMC#2T2?(w0Q}oTMf8O-L|iaX6_HiF zDmHjk-92y|O9kCZQF~x9XqL*Xp&L>Rm2xWdCc%&pvFqU`3A9_KTskzmqYwgoM#<

GVI}khbvSN5%LGeF=a?oHD$;jW(%#*gNPE+4%grd%wyat#V}Rvr=^NI zgoR#uuK`wxE&HkpJVi>zRIEa%W~-_z4X9ojjFw;up<*C-ij|Fq(uxj_85|o;PdToN zFb_R!BZwDuu(yq>RFR__8enQh8q4>iDyJA1X|FO4uaKt7R5<}^0N$6&Ld9}E;F^l) z0XN_kW@bhkkd0DQW&Mx&LrE`iqkM0KJ-t+}C7KsFi8?rhurN?n!a&f&rJ5Ot@V?BN z4q3&45*lVk4VYwjiV`vjJ=~#KFR%xdsv#J8k5=r7Wj3^-at%GI=|Kop>R=uTtL!1! zl>r#VZBR~>W(ajwLqG|+!i2&UE|`6oFFjD@8NEOmw?>&Ytt-bEIbMmF0JvLj|Ef~X zjJi=eqz3R@g}o{fuu?6R8R+R{yU{>nTB;&P2&H+|g8>YV9@Qk5{*4lWi9&{uXI#{9 zX~0yTj!N(fIaqHr^cbmB8Bf#}T*Iov9t&TvkV;=hD&>l;tY`!3Frn3Sm$RU)@E#6f z-bzt@!7&Ow%G*kn%Uo$|m9@$P41}pD*Qxf%RT<{Xh*X3xWjBZ$xC@{4GNu=#sWhO< zC{^csBpdeDGYWenqnNzXfZD^o56f2VA&&BfP;paLsS;MjhBzQ%$Z}1pG7MmFjDf6` zPHJmuSxaN`Um3X>HI!ay!&<#y0$_edMI;fv1ph6f0m^pN00Zy^2H+{?ty)M(#Mme= z<)R~qzwn6|<-Jmb-duuxY;UV1RHj8zARknHMigguzY zR#5p|PN)5C-dca9su&b2HNT*)P!AD~tIhCo>mr<_D)Q$S06{D5ggX+Ou+yPKVM7W>< zZdBc{xUhT>J~EDOW;6yC4Wh(ANH%g>O)c!wNg%nmdfo*OBe&8 zq6cz?(cpNXDlmXa!QjYqX-eZo9aO4pl%}+A8UCnLPKx@s!7YS|3XlU;Mt0#^H8Thh zQ;Wo6NOUVN|D8X~Gz^p%H5#b9Re)$P+5mT}5D+Trs>#EUs6+0{P<6=4Q>48bvb=v) zrK&^FFp3LSHXg#2lL!z_R(lD8L7q`UH@J(0RUT9idZYxS!Iae~v5_GT1dSofh%_&V z2ws)3jg~UTz`l#f;X&ovcB8aoNVJY2XdQBcy$%M-jYnJIIEI8T+h{jjN&_Ry=mr(^ zKr3Q{YiPjW@C9|q6&es=*{VY#YnUiJFJT!zLj}1n!$$~u!H{SzO=;bjn!-R(hC?t4 z6|`185D_e6G^pTGWgIITKI7g(hN=?o!L>T1)eAy^3QF*PgRKCOS2JCWTa~J`L%9mN ziW16b#RcmWMrYKG^>Q^@K9~R`3@VkU<+$1nbz_=D1c+IvlwrOavMg2R)c|f{xNB5I z+P77EWlFSErFcf9^&a^c7e>p{f49O(q!?~sKHx5Tz}+%Aa7gYM}Ra<+8H(w>&!6?{hI)ux4U;Sg5NdOl-}(WP>&bbRCiRxB)I zmxq5wTTzEnbypzwW114^!K)1}>+OWPwjyDXh0tV9IJ8N*H&&5YzyC zo>7%5&+r8zlsB-95YUQ!2rAg0Q9|0w9`H{sBLw6WdPrwkQpb_78P%&iKx^sR$ch%q ze*sq6D|=`Ite{~=MO>;@*h6b|!=e?_Q{9Mg5~9JldKpuD@ENfoO4MzqtyOZ$dDA-h z0#h)L*{bZp0LFk7bb}R`M>niQxC;>jl{csQg4-q0QnhGG|FjOf04$@mJS2>i5i}fv zWvr{xU3DB*;95Dj)A&950Ig6i&1)+h zs(h|ywRBfi$`XvChn7l*v{d%c61=M7Km+zGB%%s+z0$}4912t%(~Cia6w2qq%&1h2 zR+hA(@(*=b7S$NAhb6F~Yoozo0E1&ORAa#L(!36Y0IU3AsX&DJ>J)^*F^y=b?9HeZ z+<6GFGKIl$*2`Sf^7)~sxuO9aoo;`93h}DB1dUP?I9SWB`Cuv`XY|1FP?NWs)r{cF6C$% zewke@!8JsLpfM0kAp|r)EUjpiSOR$F!3`L|8lREI(q5TURW8k=0T&$-5y48LDwQ&l z6QB-rgo}!3fGNxoE---Gxip~WkyA4=utATquj(Ouh~=bc4=5u_)WK-={D6o_#NHO%#?SG98eJfwkqYQQAAH;G< zxT|?KXhp_HZa}XLRa-GGy1^k_FaZJjYB%)2Y*l@c2-KnPHmWy+i*_sh`|p`X2$-X) zR2fAQ(SW+Lq^3~98nm#ggvCV8nVh- zbx)%X%D7-$gn+3dIiW4!*3HdsUv%1_X_6Wt3WnP~o^$v0Ovi1C}8|1&+fgF4f>Lm5+KL zXoOmY1rh#X_R9cuG&qi+%Z#fPcn=jAErU^I_=lA^qhg>Jj0P*)s#x6yXvGEYA`W

B?M)x6P5>7 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 BtV#d? literal 0 HcmV?d00001 diff --git a/finger_plugin.py b/finger_plugin.py index ae5fa12..c4f7afb 100644 --- a/finger_plugin.py +++ b/finger_plugin.py @@ -2,6 +2,8 @@ import idaapi import idautils import traceback +import threading +import queue from finger_sdk import client, ida_func @@ -11,36 +13,68 @@ def __init__(self): self.headers = {'content-type': 'application/json'} self.timeout = 5 self.client = None + self.verify = False - - def recognize_function(self, start_ea): + def recognize_function(self,func_feat): func_symbol = None try: self.client = client.Client(self.url, self.headers, self.timeout) - func_feat = ida_func.get_func_feature(start_ea) + #func_feat = ida_func.get_func_feature(start_ea) if func_feat: func_id, res = self.client.recognize_function(func_feat) if res and res[func_id]: func_symbol = res[func_id] except Exception as e: + print('[x] Exception occured when recognize %s'%func_feat) print(traceback.format_exc()) if func_symbol: - func_symbol = str(func_symbol) # python2 unicode to str + func_symbol = str(func_symbol) return func_symbol + #def _internal_recognize_function(self, funcs, startpos,length, namelist,func_feat, queue): + def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): + #multi-threading version + for ipfn in range(startpos,startpos+length,1): + #func_name = namelist[ipfn] + func_symbol = self.recognize_function(func_feat[ipfn]) + if(func_symbol): + queue.put([funcs[ipfn],func_symbol]) + else: + queue.put([funcs[ipfn],'']) + return def recognize_selected_function(self, funcs): + namelist = [] + func_feat = [] for pfn in funcs: - func_name = idc.get_func_name(pfn.start_ea) - func_symbol = self.recognize_function(pfn.start_ea) - if func_symbol: - idc.set_color(pfn.start_ea, idc.CIC_FUNC, 0x98FF98) - idaapi.set_name(pfn.start_ea, func_symbol, idaapi.SN_FORCE) - idaapi.update_func(pfn) - print("[+]Recognize %s: %s" %(func_name, func_symbol)) + #namelist.append(idc.get_func_name(pfn.start_ea)) + func_feat.append( ida_func.get_func_feature(pfn.start_ea)) + print("[N]Trying to recognize %d functions with 16 threads" %(len(funcs))) + thread_pool = [] + q = [] + results = [] + for i in range(16): + q.append(queue.Queue()) + print("[T]Thread #%d gets %d to %d"%(i, i*len(funcs) /16, i*len(funcs) /16 +len(funcs) /16)) + #t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /16) ,int(len(funcs) /16 ),namelist,func_feat,q[i])) + t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /16) ,int(len(funcs) /16 ),func_feat,q[i])) + thread_pool.append(t) + t.start() + + for thread in thread_pool: + thread.join() + for i in range(16): + for j in range(q[i].qsize()): + results.append(q[i].get()) + print("[+]Successfully fetched %d func_symbol"%len(results)) + for r in results: + if r[1] != '': + idc.set_color(r[0].start_ea, idc.CIC_FUNC, 0x98FF98) + idaapi.set_name(r[0].start_ea, r[1], idaapi.SN_FORCE) + idaapi.update_func(r[0]) + print("[+]Recognize %s: %s" %(r[0],r[1])) else: - print("[-]%s recognize failed" %(func_name)) - + print("[-]%s recognize failed" %(r[0])) def recognize_function_callback(self, menupath): ea = idaapi.get_screen_ea() diff --git a/finger_sdk/__init__.py b/finger_sdk/__init__.py new file mode 100644 index 0000000..7141c42 --- /dev/null +++ b/finger_sdk/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1" \ No newline at end of file diff --git a/finger_sdk/client.py b/finger_sdk/client.py new file mode 100644 index 0000000..e1d4456 --- /dev/null +++ b/finger_sdk/client.py @@ -0,0 +1,108 @@ +import json +import base64 +import hashlib +import platform +import requests + + +class Client(object): + def __init__(self, url, headers, timeout): + self.url = url + self.headers = headers + self.timeout = timeout + + + def str2byte(self, l): + for i in range(len(l)): + l[i] = l[i].encode("utf-8") + return l + + + def byte2str(self, l): + for i in range(len(l)): + l[i] = l[i].decode("utf-8") + return l + + + def my_encode(self, msg_list): + if "str" in str(type(msg_list[0])): + msg_bytes_list = self.str2byte(msg_list) + else: + msg_bytes_list = msg_list + msg_encode = list(map(base64.b64encode, msg_bytes_list)) + msg_str_list = self.byte2str(msg_encode) + return msg_str_list + + + def my_decode(self, msg): + msg_bytes_list = self.str2byte(msg) + msg_decode = list(map(base64.b64decode, msg_bytes_list)) + return msg_decode + + + def gen_msg_py2(self, content): + content_encode = dict() + content_encode["extmsg"] = map(base64.b64encode, content["extmsg"]) + content_encode["ins_bytes"] = map(base64.b64encode, content["ins_bytes"]) + content_encode["ins_str"] = map(base64.b64encode, content["ins_str"]) + content_encode["func_name"] = content["func_name"] + func_id = hashlib.md5(json.dumps(content_encode).encode("utf-8")).hexdigest() + content_encode["md5"] = func_id + msg = json.dumps(content_encode) + return msg, func_id + + + def gen_msg_py3(self, content): + content_encode = dict() + content_encode["extmsg"] = self.my_encode(content["extmsg"]) + content_encode["ins_bytes"] = self.my_encode(content["ins_bytes"]) + content_encode["ins_str"] = self.my_encode(content["ins_str"]) + content_encode["func_name"] = content["func_name"] + func_id = hashlib.md5(json.dumps(content_encode).encode("utf-8")).hexdigest() + content_encode["md5"] = func_id + msg = json.dumps(content_encode) + return msg, func_id + + + def filter_func_symbol(self, func_symbol): + if not func_symbol: + return False + filter_list = ["unknow", "nullsub"] + for item in filter_list: + if item in func_symbol: + return False + return True + + + def recognize_function(self, content): + version = platform.python_version() + res = False + func_id = "" + symbol_dict = dict() + + if version.startswith('3'): + msg, func_id = self.gen_msg_py3(content) + else: + msg, func_id = self.gen_msg_py2(content) + try: + self.session = requests.Session() + res = self.session.post(self.url, data=msg, headers=self.headers, timeout=self.timeout) + if res: + symbol_dict[func_id] = self.get_func_symbol(res.text) + except Exception as e: + raise RuntimeError("upload function failed") + return func_id, symbol_dict + + + def get_func_symbol(self, res): + func_symbol = "" + if len(res) <= 4: + return func_symbol + try: + msg_dict = json.loads(res) + func_symbol = msg_dict["func_symbol"] + if self.filter_func_symbol(func_symbol): + return func_symbol + except Exception as e: + raise RuntimeError("get function symbol failed") + return func_symbol diff --git a/finger_sdk/ida_func.py b/finger_sdk/ida_func.py new file mode 100644 index 0000000..13c9d4e --- /dev/null +++ b/finger_sdk/ida_func.py @@ -0,0 +1,118 @@ +import idc +import idaapi +import idautils +import re + + +class FuncSigFeature: + #using static class variable to avoid repeating loading. + string_pool = idautils.Strings() + def __init__(self): + self.file_path = idc.get_input_file_path() + + self.code_list = ["",".text",".plt",".got","extern",".pdata",".bss"] + + self.control_ins_list = ["call","jc","jnc","jz","jnz","js","jns","jo","jno","jp", + "jpe","jnp","jpo","ja","jnbe","jae","jnb","jb","jnae","jbe", + "jna","je","jne","jg","jnle","jge","jnl","jl","jnge","jle","jng"] + + self.string_list = dict() + for s in self.string_pool: + self.string_list[str(s)] = s.ea + + + def get_file_structure(self): + info = idaapi.get_inf_structure() + arch = info.procName + if info.is_be(): + endian = "MSB" + else: + endian = "LSB" + return arch, endian + + + def get_file_type(self): + file_format = "" + file_type = "" + info = idaapi.get_inf_structure() + if info.is_64bit(): + file_format = "64" + elif info.is_32bit(): + file_format = "32" + if info.filetype == idaapi.f_PE: + file_type = "PE" + elif info.filetype == idaapi.f_ELF: + file_type = "ELF" + return file_format, file_type + + + def get_module_info(self): + module_info = "" + if len(idc.ARGV) == 2: + module_info = idc.ARGV[1] + return module_info + + + def byte2str(self, l): + if "bytes" in str(type(l)): + l = l.decode() + return l + + + def extract_const(self, ins_addr): + const_str = "" + op_str = idc.print_insn_mnem(ins_addr) + if op_str not in self.control_ins_list: + for i in range(2): + operand_type = idc.get_operand_type(ins_addr, i) + if operand_type == idc.o_mem: + const_addr = idc.get_operand_value(ins_addr, i) + if idc.get_segm_name(const_addr) not in self.code_list: + str_const = idc.get_strlit_contents(const_addr) + if str_const: + str_const = self.byte2str(str_const) + if (str_const in self.string_list) and (const_addr == self.string_list[str_const]): + const_str += str_const + break + return const_str + + + def get_ins_feature(self, start_ea): + ins_str_list = list() + ins_bytes_list = list() + ins_list = list(idautils.FuncItems(start_ea)) + for ins_addr in ins_list: + ins_bytes = idc.get_bytes(ins_addr, idc.get_item_size(ins_addr)) + ins_bytes_list.append(ins_bytes) + ins_str = self.extract_const(ins_addr) + ins_str_list.append(ins_str) + return ins_bytes_list, ins_str_list + + + def filter_segment(self, func_addr): + ignore_list = ["extern",".plt",".got",".idata"] + if idc.get_segm_name(func_addr) in ignore_list: + return True + else: + return False + + +def get_func_feature(ea): + content = dict() + pfn = idaapi.get_func(ea) + if pfn: + func_addr = pfn.start_ea + Func = FuncSigFeature() + if Func.filter_segment(func_addr): + return None + arch, endian = Func.get_file_structure() + file_format, file_type = Func.get_file_type() + module_info = Func.get_module_info() + ins_bytes_list, ins_str_list = Func.get_ins_feature(func_addr) + content["extmsg"] = [arch, endian, file_format, file_type, module_info] + content["ins_bytes"] = ins_bytes_list + content["ins_str"] = ins_str_list + content["func_name"] = idaapi.get_func_name(func_addr) + return content + else: + return None From eef133230c8db9b74ebb0cb1bedf4f9921a48fc4 Mon Sep 17 00:00:00 2001 From: Jerry Date: Fri, 7 Oct 2022 22:56:55 +0800 Subject: [PATCH 02/19] Update README.md --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 7ba638e..8420866 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,39 @@ +# Finger Multi-threading + +An improved version of brilliant ida plugin finger, fixed 'always pop up' issue and implemented multi-threading to accelerate the recognition process. + +**IMPORTANT: THIS IS PLUGIN IS STILL UNDER TEST AND HASNT PUBLISHED TO SITE YET!** + +## Installation + +* Download release version of source code. +* Replace the `finger_plugin.py` in `IDA_PATH/plugins` +* **Replace the `ida_func.py` in `%AppData%\Roaming\Python\Python38\site-packages\finger_sdk\client.py`** + +Enjoy! + +## Known issue + +* After running the plugin, your IDA will stuck into no-response status, it takes time depends on your functions count. + +* Sometimes due to remote server SSL problem, you will receive exception: + + ``` + requests.exceptions.SSLError: HTTPSConnectionPool(host='sec-lab.aliyun.com', port=443): Max retries exceeded with url: /finger/recognize/ (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:1131)'))) + ``` + + Most of time this is normal. If it in deed influenced your project , considering using original version. +![2022-10-07 22-19-17 00_00_10-00_01_10~1](https://user-images.githubusercontent.com/20926583/194583856-81b9a536-9918-4eec-bb44-ba6a308ec007.gif) + + + + + + + + + +----- # Finger Finger, a tool for recognizing function symbol. From 1c419c9fa3773252438a6b3fc09e66baab30ef5b Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 9 Oct 2022 00:15:03 +0800 Subject: [PATCH 03/19] Updated UI notification --- .vs/Finger/v17/.suo | Bin 14848 -> 26624 bytes .vs/VSWorkspaceState.json | 5 +++-- .vs/slnx.sqlite | Bin 90112 -> 90112 bytes finger_plugin.py | 41 +++++++++++++++++++++++++++++--------- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.vs/Finger/v17/.suo b/.vs/Finger/v17/.suo index 6e6ac1b2559435f83277578ac8b88da3ca217a45..3c2716de6b752e4fe4d8fa45c7c8ab0315f8bc46 100644 GIT binary patch literal 26624 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 E0ztuKbpQYW delta 1112 zcmb_bO-~b16rDHBv~LF5@-angOAApx8cA$}ga8`*fl{2>f{40tL*l}%-bvvEb& z6-5!7`A${n7*bQ=`s&wv_rH7=orKq-SXbA@3ii|ris>wzjzy`_hDwgq)DSXiFjS@> zK+a}CXmPwWr94B?0U5@v!3%K;&XWHmz@Rls9wYDfuyeF-E^P=`Ei;Xg!O)urug5(W z${~y86IlcfS>Q)7*<*TD`6Q+hL)tn&%p91IZ$U4X-a5eRR(|!Ba7_!{mPEq(7G=hB zv~!*O2KhXB3HyC1beo#KMA26o}2I7a7p8)`CSw?*1}cUK@bxy-g1O5BK8QT48_v`Xh z%|~;zMt6NOW=sA(c#!?l?zD#HSXuH2^H_U$C$?$hX}W}uYDT|B8}c8Wub`A3*H7*| i8na3Cj=aZQUj~hlwi<9otA^;K6RhLogIkTsF6|eHb`Pxp diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json index 29cc4e6..27a4879 100644 --- a/.vs/VSWorkspaceState.json +++ b/.vs/VSWorkspaceState.json @@ -1,7 +1,8 @@ { "ExpandedNodes": [ - "" + "", + "\\finger_sdk" ], - "SelectedNode": "\\D:\\Projects\\Finger", + "SelectedNode": "\\finger_sdk\\ida_func.py", "PreviewInSolutionExplorer": false } \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite index ddce216b991c8da94b108e582887986022ce72b3..a1deafaa301d39004039aa7299ebe7b2fb5ed54f 100644 GIT binary patch delta 1665 zcmaJ=O>7%Q7@hI1H@54Y&knI2$5H$ff|97Xv1zFYq5L>*sZ5&)7jkH-jd3>aw$9qv z>q=EsIj9GEOtcagE>)pQ$pL|kP}7Smijb(dz@e=Yfe@goJyjrjLI`Gdy~&nTTvoI1 z`+48I_s!A`=h6-5as-D~`ZGB6`P|3g6GSJ1`_P6}5)%Iuoh`rbcW?8(vfxCS3q#Yj z$|-#$HL9K5 zgGyPpM>CCDO;$B~QnRSha)W_#RLvnMl2}mGl3bJO^QUKLmHC3S&`^uX>e+aZ zP%Fu-h1w{Wt(aO^lvxjZ9UJew=s6#E@+jk6(5mH@BnVY3Y-HOrpP); zkPG4_thPp9SrK`79>w(bk=`af@Vx;6MX_5bmKCZwl@44RpS(W&PiiUP6i@`Yg*Ogo zvPbh-3+~|s6h&5~KFsP(xR-ZnffClNHHn+A$g^)mRK}p`szB+j%n_Qf?s`T}v(Wc2my?8f7 z8|+P1)e`U8xSGjdTMt|4b5BImPefBY(EYUU+xeMF?R33bD9UtoH96Vi6%ZqAT8!o# zJ-bZUV(g$E6SWvSsM|y>MicD|no>xTqAHps*3ticty1RuZ`@E3dtkKiG! z!>{lo+=1_@1k3QCH99v3hu{u<$3Z8DF$8!HVV-?}eK_`Ua2#?QQ6KwtvZF(!19bCj z{BE9&-pzaTcBTI%vya1NBz3WT!2>f?|1}Q$;DT@9I;Fl0Kae%pLzVvp`p9kaEliN> z@LOxNI!|`P`>5Z3CkRr+V@eNv6mOdV0sXTk)qszq$85OU98UQ8L2UN`eDD$S@?qRD z!qKHqG$Z@y=s9ezFuP0+n~Te?l*8t_GCy+ITvFyp4(kV|g8EEzEM$<_rb&uv=?ZPo*i>Ca%Qo#Q2wKY`_F-eUF^x5KCc#9Z zh^F9!BAeJ<*nHb?V=V|`$rx52s_34!p+1OCQ3PLn@IQh+>|y8Jm?n|VmydgYzwdnC z?>oQmOkJ{0U9z4GQO|VS0QJm`{sO1U{REG&fEn&DYwb2(_Ai^EG-fP>gNU!}>+S1n zkHy#Z#@EGGmmh`wZcTL%LR8EW+`(1M;+Z&h3TjAMPmS<0zj5W0k9%A%h~~OT@EHGK z9zWw76!0ZJLOUG%6~E8t`H%dF>z33DYK)`Z|-&7Nhw| z*ze5ddlCn~6+OROFBo0VWHUuwkEZgI#of8=idAbxT1qqXhX0-sc@r`(GnUkM?a!v( zleaNm>r|VqDeBfJFV*#NupKI%}8f`tBTy*uRoM-wt|rkVrRQD+41x~ zy)d??I*2hp6fVsUY-iptyiP{^qQL5CK0B_8gn`8Q`GEI>3c9KhbyU#4MzpDd#v0HM z?TpCTb|gk}`}P#_$y7Q~OcvA8i9#-G5q)-=jeIsn0%EpJrbdaIu%8JJNE7>faKmR? z2cvt(s)WzVNJtP{HV+IAB?haoR~2Q%sF}UV@pPez8vR4TkxD_$!br17h#oN6?bhZg z4`swBq47+ydcwi45sl9gyuouE#UZgW89v7^;|%U%17f%?)@>NSW4as}KE_K*CtX~I z{A7zu3Dd>3@rgCqxyUxIidS%({%1X!1s8c+rSX(UFz&i7(83S`7R%%(-CXIQHM?O9 z>jC4e-rW^Y;&e$T><%SBE0=FGZhzHfIQ6cG1K$%3#|i$$YrMo0@irggH{8Q5@sMud zPy8X?>RFt`@p5Ez6l<&srD!pu9J*DL<9ReWh*OiI88nl1s|ulzpB9&KBiFXgVmE(m z5@#}4Wl&{MWUwd-X>#y$!juz4hG$RULheuDob-t=uOM-YDrIsG_?qvc3`jF&XE zlh)gU7N}ubGX_%RL@lH7l1=ejYPb3hD~F0!YrVtCQ9X2JokP$}F?ANf4Yh+bnABei k8K+CVJs~wtmv+fwcd7yU)+dY2xNx}JczL+{^wfj@09TBC0{{R3 diff --git a/finger_plugin.py b/finger_plugin.py index c4f7afb..ff91f54 100644 --- a/finger_plugin.py +++ b/finger_plugin.py @@ -44,29 +44,46 @@ def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): return def recognize_selected_function(self, funcs): - namelist = [] + #modify this constant to allocate more threads. + threads_count = 16 + #start + step = 1 + sum_step = 6 + idaapi.show_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Getting function features' %(len(funcs),step,sum_step)) + #namelist = [] func_feat = [] for pfn in funcs: #namelist.append(idc.get_func_name(pfn.start_ea)) func_feat.append( ida_func.get_func_feature(pfn.start_ea)) - print("[N]Trying to recognize %d functions with 16 threads" %(len(funcs))) + print("[N]Trying to recognize %d functions with threads_count threads" %(len(funcs))) + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Setting up threads\nAllocating %d threads' %(len(funcs),step,sum_step,threads_count)) thread_pool = [] q = [] results = [] - for i in range(16): + for i in range(threads_count): q.append(queue.Queue()) - print("[T]Thread #%d gets %d to %d"%(i, i*len(funcs) /16, i*len(funcs) /16 +len(funcs) /16)) - #t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /16) ,int(len(funcs) /16 ),namelist,func_feat,q[i])) - t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /16) ,int(len(funcs) /16 ),func_feat,q[i])) + print("[T]Thread #%d gets %d to %d"%(i, i*len(funcs) /threads_count, i*len(funcs) /threads_count +len(funcs) /threads_count)) + #t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /threads_count) ,int(len(funcs) /threads_count ),namelist,func_feat,q[i])) + t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /threads_count) ,int(len(funcs) /threads_count ),func_feat,q[i])) thread_pool.append(t) t.start() - + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Waiting for threads to end' %(len(funcs),step,sum_step)) for thread in thread_pool: thread.join() - for i in range(16): + + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Getting thread results' %(len(funcs),step,sum_step)) + + for i in range(threads_count): for j in range(q[i].qsize()): results.append(q[i].get()) print("[+]Successfully fetched %d func_symbol"%len(results)) + + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Successfully fetched %d func_symbol\n[%d/%d] Setting up ida details' %(len(results),step,sum_step)) + failed_func_count = 0 for r in results: if r[1] != '': idc.set_color(r[0].start_ea, idc.CIC_FUNC, 0x98FF98) @@ -74,8 +91,14 @@ def recognize_selected_function(self, funcs): idaapi.update_func(r[0]) print("[+]Recognize %s: %s" %(r[0],r[1])) else: + failed_func_count = failed_func_count + 1 print("[-]%s recognize failed" %(r[0])) - + + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Done!\n[%d/%d] Done! ' %(step,sum_step)) + idaapi.hide_wait_box() + print("[N]%d function(s) recognize failed" %(failed_func_count)) + def recognize_function_callback(self, menupath): ea = idaapi.get_screen_ea() pfn = idaapi.get_func(ea) From 71f16c459fc68bdb4279649a8c17abc851204149 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 9 Oct 2022 12:04:45 +0800 Subject: [PATCH 04/19] hotfix #22100901 --- ...923f1c9-f6e5-48f2-8938-5db34c450145.vsidx} | Bin 7999 -> 7732 bytes ...ee1eb762-a914-489a-9f64-332ab6947758.vsidx | Bin 43806 -> 0 bytes ...3220d07-7955-4051-a1e3-a5b46b81a7c2.vsidx} | Bin .vs/Finger/v17/.suo | Bin 26624 -> 33792 bytes .vs/slnx.sqlite | Bin 90112 -> 90112 bytes finger_plugin.py | 2 +- 6 files changed, 1 insertion(+), 1 deletion(-) rename .vs/Finger/FileContentIndex/{03c9cae7-bb8b-4dde-8e02-21559ce9a673.vsidx => 6923f1c9-f6e5-48f2-8938-5db34c450145.vsidx} (92%) delete mode 100644 .vs/Finger/FileContentIndex/ee1eb762-a914-489a-9f64-332ab6947758.vsidx rename .vs/Finger/FileContentIndex/{3f6c12dc-d97b-4c96-a8e9-b8679d5bdf7a.vsidx => f3220d07-7955-4051-a1e3-a5b46b81a7c2.vsidx} (100%) diff --git a/.vs/Finger/FileContentIndex/03c9cae7-bb8b-4dde-8e02-21559ce9a673.vsidx b/.vs/Finger/FileContentIndex/6923f1c9-f6e5-48f2-8938-5db34c450145.vsidx similarity index 92% rename from .vs/Finger/FileContentIndex/03c9cae7-bb8b-4dde-8e02-21559ce9a673.vsidx rename to .vs/Finger/FileContentIndex/6923f1c9-f6e5-48f2-8938-5db34c450145.vsidx index be69fd4a40c20d0f763a18dd03f291141c816e4e..5a967713e51ebccd748cddad580f4f3b5cf55935 100644 GIT binary patch delta 69 zcmdmQx5b9j*~Oiifq{WTZX@S(R-QB<69f!_c=81{xyh$kxhKD6HDvxXfAMA!c4-L# NJ~prdhD{<68UPi+4ZQ#W delta 237 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

B?M)x6P5>7 z8V%+H710f1X<*a?2CC^n33_P2C%rPe=%GHtQ!_Gv3{}a2lVwDz3{#k7xP}ty5D~VA zst)fxrKj4r^hyW9t^& Hw9e7i7GebJ&9wgs_2fnISb_mel-Mm9YOVAr1sMrh26~ z>QP!AWfvYGtTH*H2K2>jp%tz{8NrmE!l+)*uvQBEn67mj+?N;^eW47aSmO{;!jN!$ zy~$HkNEmtqItC^Hrci?8)oekzw4!|jy+9d>g$R}M2Gdr!hQV>cyg?ZeL7Wjani^#Y zt)&~Zg}PEj`yxYF zYpBEOtQ@NJFk~g-7J{d^lVKG8;dxo!)1y^~1{lSTjvg4d3`SkUxVWGJ25_ic?<}u+ z$SE!`1-%(Z6u$4xO-(G^xUItB${f5sU5(Txb(XxE(+bFG_GS{Qrf@t*sf z^Pcyf^PY1Ozs}-k`1u1Y@6tgNuW|~XHJERH$ErAvOEtkPKg-^Nb$*VWgBh7mk%u&o zfNEO}hz<#0w|fP;0Vz1S7hh&!#_m(pp+t2qsaO}R+x-I7i8T})%St3Azr9w8#99EWZY4-gOx}N zTr}k*Dm)5mwi@C#YqG)*t2#5JE8_B3QK5$!ZLgA1L(%+^?AU9tt{oI~TPTQC2BD|L z0O?Bqj)^PH#E-kMdoqKIf`-W>YN$0-LEIRJN}EF1mQ{ma|JCuHN9)g?`{-MjcJSBf z5B9s(bi-41-_IC6o_q1uTGb&au$32n*cTV8%BnxV^70Fx95LOx3N_)quog1FH%8;X z=B9S1+_X6bQ522vc3TNNYs!^dtOlzY^rlfENMF1XseqqHa>3iyq!bO}N$<$SbO_aY zy&lyxk!ehY$0sA9si&h8$0FmCZPA&pk%M8A9-M}FV#gEUTARPbV%~I1uAiixD-rem zx}Q`2&)H3Ay5ua=Xwoo?X>gcP-B1rpb(2hM^4XG|PdGganrB;Y8e+~Me9?#nR2=-< z97@Jq8de*0Ma=JTH_dn6b+|pP=Gikh2JLkh9PatkZ!Y=+znrhWkbKWQAX{=*g1aZ0 zjC$wU(lEc&!!L~q%e|=_FMS=|3SK$K(WEDVQ0nEzxk)aYc)M-ZZu2bu z;8PRVi=)q&J44}+(Pp!b7_A|3pRu#u(rL7gwU1gxMnYp9;ZYfP3ZjESthG{D8`S-y zw^PdNZQrMddio#7W8E%F{Pc37r{{kHBiJ<@h delta 1808 zcmb_dO-vI}5Z<@!+HI|+v=oY#FPXfV*?Di?H{VRFo#M6E z>})Hu-(FM4yMpfX2YTMWV?mDNv`RS6A7|I#D?iK*!a!c9G!NZ83IuvXP|w>SnO7Mv zLx&SZMDe10Pp8xKad)7gI<@N1UVtLy{3W>iaykXkIdgd|e)54<7-R)-tOPOxT|gN{p)?S4_{A$|Ts00mhME2cA+8RA9z8`?!W9(=0hIPG&9diG z?Tp?@bWMllM`NviSiL)v7&1Go7CDCaX2^B04HyVEJYkD{5lHMk=tG*}0|YwOd0Ip(s4Au7}88 zfk{TnYx@;O-v`|@!NwuwQGZgSfcl#x5{(%xxIM4~ZXRub%Y!Y@GoV4F@8O+_F-Em! zE$}t;O2XQvX#}QlY3s0d*(&$l zEn59zU*N%$(Tj`}sz9AaDZ^)Mun5X)7lYO`Y-f~gMh|9Q zRjY=MG>&MhJh)wIy>Wkh#g5&nQT^8P?qdaeKJ~oy-;H15aE!cgB_=|oR@0RTd{kEC zH#FDC%20dnp}xIQycizs^MYG(!e>Q>XN{`Zq|_*l5NrrSth)e46gt|-m{*~F8_M%1jLGEnCxFB z(r-c@64C_^vkuZj>=11085JX`YsWB+%!{g97mbneLf(Y88 zi|!&hB}5m=po_d1$ht@&5&|KRK!S)E6w-x6Uxa#5L7flIG>`)?&-uU4^XGYf&zoN~ z=NHX)8>wf?bBucChKIbgOMc;P5rdtf1Hn+|!P1)7-Rzqu4%-H0DRzN{S&eOj&EOFG z%AVsjo}qv`*2-?NH~3WYADy%DY@WE~52t)Rvp-J%`R(Kd(m+HZF%gZ&GUEFunpzKm zv?%IWA{mcON}17d<3USO1%rHQBXSG0fE1AJSIvMAUoO+i)yox$smbw7 zIyw@IWTV+w;8G@)6y-t56YSQ8u2J}PYjaxFNSn=~MjE4PCGs1S$wjHjZ@~(2V47kU zk1>O3JjF5|VhIaa#5b%d1C~+5FRYjR!(-s_oe1*1RC^oD9Hk~vJ8jxo#dAb?YlK*N zwF@gRucTT#;Ig2gP+Q>PfQTblS4_(|iSyXcezH}#*<1Dsx7h;5OaAmt#)XMpF5b99 zAur{cI>3AB76iB&`Chs+29qd0+t1LoPU$jaiMD-`5@(553h+N3+ IE#yD`17i>{R{#J2 delta 997 zcmZuuT}V@582-MU02;xxcLKeYMYp3$OY0#9NI3KfgN}b({LS< zaF#5Q269W=gh!>&=><*pxfMh|o@tKuHvf$jt7QfGk*esIMpDU~y79zdeW0Lv#*XRf zRB|$&8=WvKw6m|K*XVIt6m*F7gs<+8xv+xVoJ#3=b0AQqphj#{^wE*{*p+l5oV^BS zjXvme7mQ_%nu4ONtA8>xo=oHilKDK7&V|QwnY6`hM+jZ^vZl6hHl32yjmKRBi%Wi! zJ#ir0dLSG5&sz6Z&>!rUYKMZ#6+{}hts%dAP0YEm&20j0fx|V222Dk~IWJp&nkrGgyRqSSM@H4G!`L zqU0yphJNx6wo9S@8A3EEh!Yp}WIE3j-E?c{d_qSuNR7Dc9aVP8kA;xcCq?l=KhRjp z-A3*Xj*KKKIWpYaIWipEIHK8t`;^L%P4Z$f#E}@{NDOf#hWJ5c(Rln)JIxe4$5cD4 zASX;An1hEf1NY!5{D%9m3*TW0zQB8U3$IxMQ?OGC4Ud6bAz@7KW{&tcu<)Z@GC$ZQ z^J87Ioo;0s+AXS4w#pL1iX8@7q;nEDL4_Bv%+}4q8!`vSS(u+8K-S4i=p@VVwG_(U zB$6D&bpFO2db>~;i3xXxL`?WIgkr*>%yWqek1|gt#$0*nR-uWWEY#77LhWqv(?3@- B4t)Rs diff --git a/finger_plugin.py b/finger_plugin.py index ff91f54..e1e339d 100644 --- a/finger_plugin.py +++ b/finger_plugin.py @@ -174,7 +174,7 @@ def register_actions(self): def selected_function_callback(self, ctx): - funcs = map(idaapi.getn_func, ctx.chooser_selection) + funcs = list(idaapi.getn_func, ctx.chooser_selection) if ctx.action == "Finger:RecognizeSelected": self.mgr.recognize_selected_function(funcs) From 1e76afda55ba246792bc1f1ac60e1612cb15d766 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 9 Oct 2022 12:06:30 +0800 Subject: [PATCH 05/19] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8420866..ce712dc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ An improved version of brilliant ida plugin finger, fixed 'always pop up' issue * Download release version of source code. * Replace the `finger_plugin.py` in `IDA_PATH/plugins` -* **Replace the `ida_func.py` in `%AppData%\Roaming\Python\Python38\site-packages\finger_sdk\client.py`** +* **Replace the `ida_func.py` in `%AppData%\Roaming\Python\Python38\site-packages\finger_sdk\client.py`** (differs from users) Enjoy! @@ -23,6 +23,7 @@ Enjoy! ``` Most of time this is normal. If it in deed influenced your project , considering using original version. +* Python 3 supported, any issue of bug please propose the issues. ![2022-10-07 22-19-17 00_00_10-00_01_10~1](https://user-images.githubusercontent.com/20926583/194583856-81b9a536-9918-4eec-bb44-ba6a308ec007.gif) From 20093048028d9a6e0ca93857a8550b370621132a Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 9 Oct 2022 12:11:50 +0800 Subject: [PATCH 06/19] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ce712dc..5b66ebc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# READ INSTALLATION FIRST !!!!! +----- # Finger Multi-threading An improved version of brilliant ida plugin finger, fixed 'always pop up' issue and implemented multi-threading to accelerate the recognition process. From 7a0ebe2df8ff245669736f3872f1a19a891ba2db Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 9 Oct 2022 12:16:55 +0800 Subject: [PATCH 07/19] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 5b66ebc..8bf6b84 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ Enjoy! Most of time this is normal. If it in deed influenced your project , considering using original version. * Python 3 supported, any issue of bug please propose the issues. +* **`TypeError: object of type 'map' has no len()`** Solution manually modify `finger_plugin.py` at line 177: +``` python + # funcs = map(idaapi.getn_func, ctx.chooser_selection) # old + funcs = list(idaapi.getn_func, ctx.chooser_selection) # new +``` ![2022-10-07 22-19-17 00_00_10-00_01_10~1](https://user-images.githubusercontent.com/20926583/194583856-81b9a536-9918-4eec-bb44-ba6a308ec007.gif) From e03589ba5e65b32efe977e5ece455921f073fb85 Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 12 Oct 2022 15:08:45 +0800 Subject: [PATCH 08/19] threads bugfix --- ...ec4bf71-6a38-46df-8164-b9883d499757.vsidx} | Bin ...f7bc787f-6ab7-407a-9224-a4df713fd0b3.vsidx | Bin 0 -> 49890 bytes .vs/Finger/v17/.suo | Bin 33792 -> 26624 bytes .vs/VSWorkspaceState.json | 2 +- .vs/slnx.sqlite | Bin 90112 -> 90112 bytes finger_plugin.py | 4 +++- 6 files changed, 4 insertions(+), 2 deletions(-) rename .vs/Finger/FileContentIndex/{f3220d07-7955-4051-a1e3-a5b46b81a7c2.vsidx => 0ec4bf71-6a38-46df-8164-b9883d499757.vsidx} (100%) create mode 100644 .vs/Finger/FileContentIndex/f7bc787f-6ab7-407a-9224-a4df713fd0b3.vsidx diff --git a/.vs/Finger/FileContentIndex/f3220d07-7955-4051-a1e3-a5b46b81a7c2.vsidx b/.vs/Finger/FileContentIndex/0ec4bf71-6a38-46df-8164-b9883d499757.vsidx similarity index 100% rename from .vs/Finger/FileContentIndex/f3220d07-7955-4051-a1e3-a5b46b81a7c2.vsidx rename to .vs/Finger/FileContentIndex/0ec4bf71-6a38-46df-8164-b9883d499757.vsidx diff --git a/.vs/Finger/FileContentIndex/f7bc787f-6ab7-407a-9224-a4df713fd0b3.vsidx b/.vs/Finger/FileContentIndex/f7bc787f-6ab7-407a-9224-a4df713fd0b3.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..173d02880b7e11dcabf36c60f3140846eab04040 GIT binary patch literal 49890 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 literal 0 HcmV?d00001 diff --git a/.vs/Finger/v17/.suo b/.vs/Finger/v17/.suo index f52e673add123f48f3fa1aa9b3f86ba279809c76..28844ec1cb48dc4cd7be3d4dfdee86a490253eed 100644 GIT binary patch delta 1418 zcmb_bO-vI(6yBL`VO#kr)K(}JTK<$u!?xQB1cFs7dLYsWiHXsKVhd`F8mWm!!A6bI z$N}~RJ(w6JCLX*X%K;OkHNZh*q6ZHqo;-7qXi3mS@jd$k{zVT?^6i`X=IxvBy*CrT z!sD;lh|Kt?r;O=<>6b@)rc{13U)G;LJ@)nuTQA^%GyE(YgGK&Q%E6_csEV)?WS$F2 z-NqgHNG#(hOmEVhMlr6}BEF`U%XK)nA_@=&1kq-+w7(7yH|IIhEbTyit=(OeCR}JY zA?gusL<54HJZbAiTawb>MEXzM`B(k9)rT_rNfYHjfkm{W&4iE;q%jXI?a`nd$Wl&Z zGdZqX37x`L7sIV#Hv z@9H#suPWW1ESq%2nF^;ib&1rd46VP8i;LPPb1t7O;aua6tqf+k2V%}{(YeYI0#Q*< z#|!YKbf;8>6KZfK4#aFBk#6itxojZ_w#X1G8&+%VV6VJq5Nzl`EzbTG-y&*Dl@e2} zF$UTx>oqo6JM&$DmfY}ynIh;+cS;bL(2ZYs+HZrwAp^Yk=1JZGjWVHmtS7Z3{B_8>G1tC265N@4qfdgkcAW#=K^<$jOk$ZnWvVQxYtD`NsC9Zn6 zL671P2ckX?I20QM6uUTq?-*`-9csf)=aGZ=O&`tkcYR;;bl5 zHPZ`o34va+<>dap!^a+W#N~2J#gn1O*fgunT3Zq*CWDE5EphxAtLcHX@(~;9y5-&b9mQf1KUud W=q5@^4QpZoCmXf-AqyMt&G`mrCe(ZY delta 1820 zcmb_dZD?Cn7{2G`roHLXB>hNj)3izY5#ng-y-C`nUt2%o=%}oBYgR|sNN?JtZVVZi z{g|5=QM#!GyZhomaI2J|AR;XHw*P2t2N(HE>?S#@d(>Pgc0S&(wKA&D2oUi!dlYTm3o>VD#;G)X?=Bbp{}3O zy0+vyDwPa_rJf8!C=Vb85n;r02+|K@KY|!Vj3Evo4kN}9ZAFK_Y5ci*+lOEO$4YB^ zj#8!MoSJ}gIoo=a?TGR&SWu>F$&RX{YH1hB_ASK(1miQ8*HrTQwDMZZ<<&(+4ef#O zQeBVfMk$4dw~t?8+S6w((?SicD<5>)q6h|3w4dxPp1ft&R49y&`>~l-5os6Ns+D zbhMS)(jDtsyG^%wbp|{AvXO`+!rt?bcsGMT8;|`XcGfXfu!MAbtm0)nDKK2s@tuhg zwTYRPkeM+--|ZBc=9?CXIhBdDPZ3*C(N(3?^FA}ncI^~G==tvN8FRz-++nRbDiRGn zRko0Bo`oHs4Eg2^`v@-B=LEWoW&h^uICuv^E!)3;*81)XA`fbOQ{OzHGA%F(p9Wmk zK&USyUp}Iqdp&sy7sK{yC%m#?GF`qT6&qT)VKE>IvEsA?-dx&ey6y=dEszN~VKMm2 zGsc+zw-~ObfGqV%#UNG576R#u1Gji>_#2Tf9`T^OFUFfVxB|yVW+3XhQ{hf)waJS( zSc8ksnF6h<8N?>xf%7dk0T((x&>E*qjOhQwd8Z@q^7r@UP7J>7?=#Nff@OVgtX9xQ z*Sj{>KE5%l{PDT#%aO_GLHUiVKg|tyqa2%9m2YwP1P@~L)m$ySkIS6N!7RV`|f%}w0TEiEd?q^SOFq~Qrsv&DK}kxa0s#Z}J~YH*;GQ3aw@-J#_4rxX>~UBz2rC8R&Wx9Bw)MT2Ek+~#A~-O| z^JEH+rCeKYF(>?bb0PfFrc$m3V4ewROnKq8ln;)nS(rR>UOyk*VAgS)4_@X3M+ z%nLrS#m{UdYO>SsnY>bBSsqHJlKyZwq+;Sy&_6T~81jc!1`+{PO|A^45(W-e{H|~I rc$n=zW=ek@firTiVh>ZR(pIdloL$2V>Wa;q`;i3w*CjjKS`q#R`4J40 diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json index 27a4879..f57dd86 100644 --- a/.vs/VSWorkspaceState.json +++ b/.vs/VSWorkspaceState.json @@ -3,6 +3,6 @@ "", "\\finger_sdk" ], - "SelectedNode": "\\finger_sdk\\ida_func.py", + "SelectedNode": "\\finger_plugin.py", "PreviewInSolutionExplorer": false } \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite index 0600ece98456e3737c05d0fc5249371a60cf390c..9df4124f1ed8141c78ad6bd04e31490cf255eb09 100644 GIT binary patch delta 953 zcmY*XT}YEr7(So-cJ^&<=W(?)o#|#vAy+aOfkX}5JOxynic!Rp}FV9*_?sSSl{gWIzUE~k6rFtIW-VM*4;>X;el*)^oGiM?e{u!xt~ z%hs`T0Q;Kt?RaVt**)S=el&U9w<~iO&b%uHB~?VCU2>v3))!569+%#z6{W7(nNgv& zh?3R3$$v7XRaXg=*z;7koYpw@kmxhg4;J2A5$)ljZH_Cz;l@1hWjdiY3wW3iuA*Z9!m0G2CuEj6v zh|94=dSyj<-|SvC=CM!`;!Q1m$(~q8Iuc8#c@8OmPb!%x(p2mQO3DO@?kYs|kJeLC z&du!E-4Je$gtcx$NHA}pW_i3=l9pK2M4wx`YtQZwAj_bIB*SL!p7{xQ>@EA+@ z!2kI?CU74=v%dB?L_8;Y<#N*NgIw|KMj)&AzaCIN2*UjY-VlQr6cy!X1whD34BO416<#5b!Ir8Lv{GiEU=0 ai~R4)t0p0uxm>B5g;>n7Ir}{$&HVua^A2JF delta 1016 zcmZuwUr19?7(d6|d)n@{^SRvov$Uzwh_^4mY#N zXBPR19k_C`vKd!C=(+=xN3a45aF>=@*Q|Wj=3ccJuf+(Dn#XJ9i&dBV$E<=|`8>&? z#D#EA$|JfxebIO%JR}YG4s>=#d%LB+k$8_UIqKQ-|J5_yLy<6Rh>yf#dM(~P7-k%s z`&iqR>}u6(j2q+yS4FY^V13XZ47vkB(H{_l`?D)9r@QVkvRT(LP0&u-L@m~JIt>Tt z7y1O=z$2J}66&Gj^c{T6dJn&}at@v#`A1u|``+xgQ@?+e13|lxk^Iwc{Ge6jrW4Mz zmfDI4Dp4-^E)DC|?U*13gCfzCjya17s>UX=$ph#$C?f>9kcpgbJ<`(F>Kp7WR6>Ze zn+PgXIs?9BJi_Ufa!0w+qrg3}XgHoyR#~8vcZb|0uQL>K<#jr?5mbSVg>nr{sb~Q+OQ=W+!8J5vs*O5dCGKtXLhp&7dX`&+XW3RaH^=YRNIy$MdQ(w zByF}Qz#OvCoB{SiEwsZ1e1>H>2ZPWG#eiWS-JqY~2F<}3OtO>ppS({wtHFikrSrq` zsMOJ@*lSePSy-kDGP6t-A+Ss}V^T7&EC zQGCvGU;`r@f;ELb2gjiU>gjj73{Lu>pU+OC;T5o@o;ocge8Rc=Yw%w(4S0&~7D^Z)<= diff --git a/finger_plugin.py b/finger_plugin.py index e1e339d..85d4be0 100644 --- a/finger_plugin.py +++ b/finger_plugin.py @@ -45,7 +45,9 @@ def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): def recognize_selected_function(self, funcs): #modify this constant to allocate more threads. - threads_count = 16 + threads_count = 128 + if(len(funcs) < threads_count): + threads_count = len(funcs) #start step = 1 sum_step = 6 From cdf760064a5bfc92995f7aead1ea9a24ed80f788 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sat, 15 Oct 2022 01:13:09 +0800 Subject: [PATCH 09/19] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8bf6b84..e90ee22 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Enjoy! ## Known issue -* After running the plugin, your IDA will stuck into no-response status, it takes time depends on your functions count. +* After running the plugin, your IDA will stuck into no-response status, it takes time depends on your functions count. (UI Completed) * Sometimes due to remote server SSL problem, you will receive exception: @@ -25,7 +25,8 @@ Enjoy! ``` Most of time this is normal. If it in deed influenced your project , considering using original version. -* Python 3 supported, any issue of bug please propose the issues. +* Python 3 supported, any issue of bug please propose. + * **`TypeError: object of type 'map' has no len()`** Solution manually modify `finger_plugin.py` at line 177: ``` python # funcs = map(idaapi.getn_func, ctx.chooser_selection) # old From 91188c4dfe55c339564707ed6dc75488af36ef7e Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 19 Oct 2022 21:00:22 +0800 Subject: [PATCH 10/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e90ee22..c243239 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# READ INSTALLATION FIRST !!!!! +# [20221019]IMPORTANT: FATAL EXCEPTION UNDER 7.7, REPAIRING. ----- # Finger Multi-threading From 10a97f0c225300eadb83a70a87b5dbaddaad1cb3 Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 19 Oct 2022 21:00:57 +0800 Subject: [PATCH 11/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c243239..775b844 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [20221019]IMPORTANT: FATAL EXCEPTION UNDER 7.7, REPAIRING. +# [20221019]IMPORTANT: FATAL EXCEPTION UNDER 7.7, REPAIRING. DO NOT USE UNDER PRODUCTIVITY ENV. ----- # Finger Multi-threading From 8d66c9311981dd378af64b1826fe6476f531c92e Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 23 Oct 2022 23:03:38 +0800 Subject: [PATCH 12/19] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 775b844..8d166ee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# [20221019]IMPORTANT: FATAL EXCEPTION UNDER 7.7, REPAIRING. DO NOT USE UNDER PRODUCTIVITY ENV. +# It is recommended to use IDA7.5 to run this plugin. Due to the security update of Hex-ray, this plugin will cause crash when running on a higher version of IDA such as 7.7! +# 建议使用IDA7.5运行此插件,由于Hex-ray的安全更新,此插件在7.7等高版本IDA上运行将导致IDA崩溃! ----- # Finger Multi-threading From 4ab2e58973ea08df97bbf2435d7dfedfaf9cf6c0 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 4 May 2023 23:18:19 +0800 Subject: [PATCH 13/19] #3 Fixing 2028 error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 只执行了单机测试 * 总而言之可能是取空列表错 * issue暂时关闭。 --- finger_plugin.py | 34 ++++++++++++++++++++++++++++++---- finger_sdk/ida_func.py | 6 ++++-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/finger_plugin.py b/finger_plugin.py index 85d4be0..15d2d5e 100644 --- a/finger_plugin.py +++ b/finger_plugin.py @@ -6,9 +6,13 @@ import queue from finger_sdk import client, ida_func - class FingerManager: def __init__(self): + + idaapi.msg("[D]FingerManager:__init__\n") + self.string_pool = '' + #self.string_pool = idautils.Strings() + self.url = "https://sec-lab.aliyun.com/finger/recognize/" self.headers = {'content-type': 'application/json'} self.timeout = 5 @@ -16,6 +20,7 @@ def __init__(self): self.verify = False def recognize_function(self,func_feat): + idaapi.msg("[D]FingerManager:recognize_function\n") func_symbol = None try: self.client = client.Client(self.url, self.headers, self.timeout) @@ -33,6 +38,7 @@ def recognize_function(self,func_feat): #def _internal_recognize_function(self, funcs, startpos,length, namelist,func_feat, queue): def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): + idaapi.msg("[D]FingerManager:_internal_recognize_function\n") #multi-threading version for ipfn in range(startpos,startpos+length,1): #func_name = namelist[ipfn] @@ -44,8 +50,11 @@ def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): return def recognize_selected_function(self, funcs): + self.string_pool = idautils.Strings() + + idaapi.msg("[D]FingerManager:recognize_selected_function\n") #modify this constant to allocate more threads. - threads_count = 128 + threads_count = 256 if(len(funcs) < threads_count): threads_count = len(funcs) #start @@ -56,7 +65,8 @@ def recognize_selected_function(self, funcs): func_feat = [] for pfn in funcs: #namelist.append(idc.get_func_name(pfn.start_ea)) - func_feat.append( ida_func.get_func_feature(pfn.start_ea)) + func_feat.append( ida_func.get_func_feature(pfn.start_ea,self.string_pool)) + #func_feat.append( ida_func.get_func_feature(pfn.start_ea)) print("[N]Trying to recognize %d functions with threads_count threads" %(len(funcs))) step = step + 1 idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Setting up threads\nAllocating %d threads' %(len(funcs),step,sum_step,threads_count)) @@ -102,6 +112,7 @@ def recognize_selected_function(self, funcs): print("[N]%d function(s) recognize failed" %(failed_func_count)) def recognize_function_callback(self, menupath): + idaapi.msg("[D]FingerManager:recognize_function_callback\n") ea = idaapi.get_screen_ea() pfn = idaapi.get_func(ea) if pfn: @@ -119,6 +130,7 @@ def recognize_function_callback(self, menupath): def recognize_functions_callback(self, menupath): + idaapi.msg("[D]FingerManager:recognize_functions_callback\n") funcs = [] for ea in idautils.Functions(): funcs.append(idaapi.get_func(ea)) @@ -128,7 +140,8 @@ def recognize_functions_callback(self, menupath): class FingerUIManager: class UIHooks(idaapi.UI_Hooks): def finish_populating_widget_popup(self, widget, popup): - if idaapi.get_widget_type(widget) == idaapi.BWN_FUNCS: + idaapi.msg("[D]FingerUIManager:UIHooks:finish_populating_widget_popup\n") + if idaapi.get_widget_type(widget) == idaapi.BWN_FUNCS: idaapi.attach_action_to_popup(widget, popup, "Finger:RecognizeSelected", "Finger/") if idaapi.get_widget_type(widget) == idaapi.BWN_DISASM: idaapi.attach_action_to_popup(widget, popup, "Finger:RecognizeFunction", "Finger/") @@ -136,11 +149,13 @@ def finish_populating_widget_popup(self, widget, popup): class ActionHandler(idaapi.action_handler_t): def __init__(self, name, label, shortcut=None, tooltip=None, icon=-1, flags=0): + idaapi.msg("[D]FingerUIManager:ActionHandler:__init__\n") idaapi.action_handler_t.__init__(self) self.name = name self.action_desc = idaapi.action_desc_t(name, label, self, shortcut, tooltip, icon, flags) def register_action(self, callback, menupath=None): + idaapi.msg("[D]FingerUIManager:ActionHandler:register_action\n") self.callback = callback if not idaapi.register_action(self.action_desc): return False @@ -149,18 +164,23 @@ def register_action(self, callback, menupath=None): return True def activate(self, ctx): + idaapi.msg("[D]FingerUIManager:ActionHandler:activate\n") self.callback(ctx) def update(self, ctx): + idaapi.msg("[D]FingerUIManager:ActionHandler:update\n") return idaapi.AST_ENABLE_ALWAYS def __init__(self, name): + idaapi.msg("[D]FingerUIManager:__init__\n") self.name = name self.mgr = FingerManager() self.hooks = FingerUIManager.UIHooks() def register_actions(self): + + idaapi.msg("[D]FingerUIManager:register_actions\n") menupath = self.name idaapi.create_menu(menupath, self.name, "Help") @@ -176,12 +196,15 @@ def register_actions(self): def selected_function_callback(self, ctx): + idaapi.msg("[D]FingerUIManager:selected_function_callback\n") funcs = list(idaapi.getn_func, ctx.chooser_selection) if ctx.action == "Finger:RecognizeSelected": self.mgr.recognize_selected_function(funcs) def check_ida_version(): + idaapi.msg("[D]check_ida_version()\n") + idaapi.msg("[D]idaapi.IDA_SDK_VERSION =%d\n"%idaapi.IDA_SDK_VERSION) if idaapi.IDA_SDK_VERSION < 700: print("[-]Finger support 7.x IDA, please update your IDA version.") return False @@ -198,7 +221,10 @@ def init(self): idaapi.msg("[+]Finger plugin starts\n") manager = FingerUIManager(FingerPlugin.wanted_name) if manager.register_actions(): + idaapi.msg("[D]idaapi.PLUGIN_OK\n") return idaapi.PLUGIN_OK + + idaapi.msg("[D]idaapi.PLUGIN_SKIP\n") return idaapi.PLUGIN_SKIP def run(self, ctx): diff --git a/finger_sdk/ida_func.py b/finger_sdk/ida_func.py index 13c9d4e..1468481 100644 --- a/finger_sdk/ida_func.py +++ b/finger_sdk/ida_func.py @@ -20,7 +20,8 @@ def __init__(self): for s in self.string_pool: self.string_list[str(s)] = s.ea - + def set_string_pool(self,_pool): + self.string_pool = _pool def get_file_structure(self): info = idaapi.get_inf_structure() arch = info.procName @@ -97,12 +98,13 @@ def filter_segment(self, func_addr): return False -def get_func_feature(ea): +def get_func_feature(ea,string_pool): content = dict() pfn = idaapi.get_func(ea) if pfn: func_addr = pfn.start_ea Func = FuncSigFeature() + Func.set_string_pool(string_pool) if Func.filter_segment(func_addr): return None arch, endian = Func.get_file_structure() From e67788adb94b374ead5b23ac3024ff3f40aae16f Mon Sep 17 00:00:00 2001 From: Jerry Date: Fri, 5 May 2023 09:32:57 +0800 Subject: [PATCH 14/19] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 8d166ee..600110a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -# It is recommended to use IDA7.5 to run this plugin. Due to the security update of Hex-ray, this plugin will cause crash when running on a higher version of IDA such as 7.7! -# 建议使用IDA7.5运行此插件,由于Hex-ray的安全更新,此插件在7.7等高版本IDA上运行将导致IDA崩溃! ------ # Finger Multi-threading An improved version of brilliant ida plugin finger, fixed 'always pop up' issue and implemented multi-threading to accelerate the recognition process. From f47908aceff74cebef92c76dd68203baa32da40d Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 7 May 2023 14:47:51 +0800 Subject: [PATCH 15/19] synced correct version Committed wrong version last time. Upload the version Im using now. --- finger_sdk/ida_func.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/finger_sdk/ida_func.py b/finger_sdk/ida_func.py index 1468481..48bd3c0 100644 --- a/finger_sdk/ida_func.py +++ b/finger_sdk/ida_func.py @@ -6,8 +6,10 @@ class FuncSigFeature: #using static class variable to avoid repeating loading. - string_pool = idautils.Strings() - def __init__(self): + #this code may not secured under 7.7, removed + #20230507 + #string_pool = idautils.Strings() + def __init__(self,string_pool): self.file_path = idc.get_input_file_path() self.code_list = ["",".text",".plt",".got","extern",".pdata",".bss"] @@ -15,13 +17,12 @@ def __init__(self): self.control_ins_list = ["call","jc","jnc","jz","jnz","js","jns","jo","jno","jp", "jpe","jnp","jpo","ja","jnbe","jae","jnb","jb","jnae","jbe", "jna","je","jne","jg","jnle","jge","jnl","jl","jnge","jle","jng"] - + #may not compatible for python 2 self.string_list = dict() - for s in self.string_pool: + for s in string_pool: self.string_list[str(s)] = s.ea - def set_string_pool(self,_pool): - self.string_pool = _pool + def get_file_structure(self): info = idaapi.get_inf_structure() arch = info.procName @@ -103,8 +104,7 @@ def get_func_feature(ea,string_pool): pfn = idaapi.get_func(ea) if pfn: func_addr = pfn.start_ea - Func = FuncSigFeature() - Func.set_string_pool(string_pool) + Func = FuncSigFeature(string_pool) if Func.filter_segment(func_addr): return None arch, endian = Func.get_file_structure() From de22e2884598bc6d46544ab47ad71cf8eef72be2 Mon Sep 17 00:00:00 2001 From: Jerry Date: Mon, 3 Jul 2023 11:22:23 +0800 Subject: [PATCH 16/19] Optimize files * optimized notification output * introduced new structure for replacing python site-packages. --- .../site-packages/finger_sdk}/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 193 bytes .../__pycache__/client.cpython-38.pyc | Bin 0 -> 3307 bytes .../__pycache__/ida_func.cpython-38.pyc | Bin 0 -> 3588 bytes .../site-packages/finger_sdk}/client.py | 0 .../site-packages/finger_sdk}/ida_func.py | 0 finger_plugin.py | 58 +++++++++--------- 7 files changed, 29 insertions(+), 29 deletions(-) rename {finger_sdk => Lib/site-packages/finger_sdk}/__init__.py (100%) create mode 100644 Lib/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc create mode 100644 Lib/site-packages/finger_sdk/__pycache__/client.cpython-38.pyc create mode 100644 Lib/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc rename {finger_sdk => Lib/site-packages/finger_sdk}/client.py (100%) rename {finger_sdk => Lib/site-packages/finger_sdk}/ida_func.py (100%) diff --git a/finger_sdk/__init__.py b/Lib/site-packages/finger_sdk/__init__.py similarity index 100% rename from finger_sdk/__init__.py rename to Lib/site-packages/finger_sdk/__init__.py diff --git a/Lib/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc b/Lib/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38879ac60ad2b922c89ff54fb9100c7395f5b941 GIT binary patch literal 193 zcmWIL<>g`kf-jGLBnboQ#~=ET=^g5DlWe82lWw7xpSa$;`e&Svg8e%n5lXFIo+?{@Auy8k2W`(49~y* z@$aL*Y%=ywYOFp2jVJhu0|?0^&sbEIJmR+PM2;=p$hD=2#IKojr29FOZsJ|IkuUin z>xeI~0_zS)u09SLPw*8zh=kFqSdmNTbA}aNDNqXONgt&r0~w<9WkWVm2698TP=<0- zZlP?*ZMlQ8DPNJhC^zJ*vW>DOUxNmF#($cnNv;QUs_Td9q{CBu#U4b-OBS=zf#jtN ziSr$@E8gw6#+|Cn1gA+~CaN%jPDjajsyo7nBFT;wt))B?KExGMA&M`5|Mcg*j|%+I z`%R+MdGFoHYGsb zl_2d*SZMX&=v*h;Xw|yz@b->mUDXD*>9`7PW@?KR@MS_usvYVfL{^m}#U4O| z*UsECP1=<`XUUbWRMEwph*{fY69`bF~gBhxr~`%z_ot9{3_ zAVlG)IEn4-EvnH03fqpF)UN)BT2#k=cI38I8l&@i-q*2r(Sl$=tmO#4!<&4zQ?qP2 zZOuAFu_evEg$7OvuOdrmu3?9vSlYlnWQu4B<>{E}Yb5TF*oUx3tkCO-iefJGR^Px_ zb($L5Td0!$)yVfTvxc<^-B&fQCah_`hU;heRkI6LX+3mHE;-P3O1Q@X97j90j}WIm zILornt^3>qoLh=Jh$d2CTr52819j(;FGA^E!X}HxB?A{+%c3%Ze? zywEsy)JXH9!ZQk}3iwRpc$yF5ygy15!32iLw>nKDWIE6{hE#+j0$0B{&C(F z>wXaV)BG$Se-Z`xG|P(V(I2d;*G{%!(JjtLN8>EoI8HN-3}i!!KyDh{KGtqMW92Wa z8AV^l1>UfJXLoAIU;bn5fMhqinAm0hj*156zz1m0UFpn)cHo%6Z-FbqC0B0(Zv@#( z-gD;uT;yA5dpGUb0XfzsSP_RM8-~LMWV7@kH}JKj_^TjesTcTfvvh}BV2xM8WuLM9 z4Z-yGSyTOuvw6VTtfhCPcfmj7&)8eI3eAI0gOAxCS?+wy;Itk&ZO89^V2}KNukKKqm7Csv5hwr3=B_R;)g;4KFQNrVmGEu zev6p}E^v`N*C`oTOT-2^3R@ubf#Cn($1kj% z@1r)`hly|StqgPxvv2PtHewi$h)}3E@sIi?iFY9CG_Iad2Q616bZg&tsc$`|s9&J3 zkI&v+$=h1^$chwVDd)k8d&@w(27R})NjFJK9&v1%aXcE!X-4%{96z7-vuZ?VZ`u1%?w(vH?ww=)ls%T2-zlyMr js7BG!_~WBtg0w~}ebp8wJg83a4qc)OQu{jagzkR;mqzC2 literal 0 HcmV?d00001 diff --git a/Lib/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc b/Lib/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e1d9f3814bd3f64299f3e558e63e27c1a6b52c6 GIT binary patch literal 3588 zcmZ`+TW=h<6(%{CoxSK{*@^8`mX$P@Zh)$VZKOdU1WsbhN$M^PE6KxPlfh^?yB=wG zW=xKXYBBqyJoLE;(4tS;$NnY0_Q}7XKpu*IhqF?3akQ92@{l|v&-uf5EI7Q^3v z+-HNouQ2v+YMlRb(0GVXe*z(yWX1%A4|*ye;37zmn_nj@*!U<)(aFz9WAv-<9vl z_eXbO!!5mIvhP>?5&QneOjKeYw)g!qsKhbfW-l|zu~Ql*{1EtV(4sVg35?G$J88I9j!ekoo(8VY}9QUCzS(peUy|@niYp- zG)%`yRK(>A(>zxldxAkRvbk1<@=8ds)+(zJD{(ri?#L=qbZimqc^os5TP zy+xjn4cGr->J5bQ=iM(p>3^#erTbqeN*(n-D~d;OS^s$+PhhP7?5KQ^XN%(g$9 zy8#dQ^yWoiZk<{laI(7ib5RmvEkb{TN{!GNulT+IdiaSmXKG#YnUlFv%$!P8&QJhB zPA_!+wEKDJJl*}g>lz=Yh>O%*tD`ZD;K9QIWrPU|l@EIHBr#s9qrIf-sx_=++%y~J z#*NkB1#p+iG|sGNYw;!xStDyUlxODNf?kt%7!K*vCtN;#`_f^r&imzKe*;@MAjc;b z$9F2{MBv~XI5N*VXptK}lnxx@?7Jn~_YQ>GmhMbss|$?}%~R=-(<)(I(>c2G{&f60$D{elY#HD}1# zM1x#dvJ$vC2D8E$>r|J{ZANGUV%*vtuV~gQ0wFTY87G-)qHf&Job$b-@zqT({UH}NQd+A z***RPl~bfve2&Dd91y%XXsDk`N5c7!*!OA%jGnkaqB|Eeuksd%-GmlbgVp+|H!6S5 zF+0F)>T6Pa)~L{`*h#Bu)Gdr?!xx*CPqnIXn<2cLpn}F9*i3Nt@~4+*SyHfwS07@N z-A*0Yl>*#K46UHb=X4?3 zLD;It)Z;ON45PT5S-2@xJh02^@*!5!8xRD-7JeQ4NUlPX=9bO1EBST7$d#ZsV9y4< zk4njo;cP1%%Ci#?%^V<|xs{8MKbU!?gOHO%z~>x+_d#4fg_Pq>fM7$5;0ovvScGi@ z(5b14_qu^`?Ijt2$q&nPtWA^h=&LfB=sJ>33YLX`9SdA&lSrr2#Q1ShfWg(L*yowk zbs4KN!fo0i_md;7{bg!dE4Cfl;82g+#%$9(>o$$Wyvwe)3_yLu`pxwm^E6#43^eA0 z0|LGVmy`Qn%LV7AUT`^)^%)nuj|M#v>5(=9&m%iPW5^bPOxTd#E|We!dQ3HXic(t9 zTt$hB=M$V*#^3CK5{TVw-h(sjz1JMVtu-9m=_t!p{W`gP%4N_z-eM6U9p4?E{^YY+w_{CREZ+ficTprGMy-Tc^HrD9(9wpM~_t1CGk5FHi0OX>T448 zYCg9+n+SA0D;>)uv;ky**`0Isd-60|{+#szY{1w%Y1n!&YoZ>2qgyDOG#Vq?vkus*Q3X}AYK^=~ zNIZV4HcElAgVNciBx=!kv|MPxV6=*jsn@e_;fWrpFR@eg6$CC@iX1tv+g8h@rpwd> z_L4=HdQ3X!RG~{TR(EI!v5WeE1ijPL z7KtY$c1T=eC(EmLl|_yWF-X4yK{-N{L>E7mL{tF)wFXJ0E7W%w)%9!O+K0+RD2z}^ eeKhJmx84~XOt4+wvY*oBuIXPsGKrtF9Q_Z9W^)n% literal 0 HcmV?d00001 diff --git a/finger_sdk/client.py b/Lib/site-packages/finger_sdk/client.py similarity index 100% rename from finger_sdk/client.py rename to Lib/site-packages/finger_sdk/client.py diff --git a/finger_sdk/ida_func.py b/Lib/site-packages/finger_sdk/ida_func.py similarity index 100% rename from finger_sdk/ida_func.py rename to Lib/site-packages/finger_sdk/ida_func.py diff --git a/finger_plugin.py b/finger_plugin.py index 15d2d5e..866a19d 100644 --- a/finger_plugin.py +++ b/finger_plugin.py @@ -9,7 +9,7 @@ class FingerManager: def __init__(self): - idaapi.msg("[D]FingerManager:__init__\n") + #idaapi.msg("[D]FingerManager:__init__\n") self.string_pool = '' #self.string_pool = idautils.Strings() @@ -20,7 +20,7 @@ def __init__(self): self.verify = False def recognize_function(self,func_feat): - idaapi.msg("[D]FingerManager:recognize_function\n") + #idaapi.msg("[D]FingerManager:recognize_function\n") func_symbol = None try: self.client = client.Client(self.url, self.headers, self.timeout) @@ -30,15 +30,15 @@ def recognize_function(self,func_feat): if res and res[func_id]: func_symbol = res[func_id] except Exception as e: - print('[x] Exception occured when recognize %s'%func_feat) - print(traceback.format_exc()) + idaapi.msg('[x] Exception occured when recognize %s'%func_feat) + idaapi.msg(traceback.format_exc()) if func_symbol: func_symbol = str(func_symbol) return func_symbol #def _internal_recognize_function(self, funcs, startpos,length, namelist,func_feat, queue): def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): - idaapi.msg("[D]FingerManager:_internal_recognize_function\n") + #idaapi.msg("[D]FingerManager:_internal_recognize_function\n") #multi-threading version for ipfn in range(startpos,startpos+length,1): #func_name = namelist[ipfn] @@ -52,9 +52,9 @@ def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): def recognize_selected_function(self, funcs): self.string_pool = idautils.Strings() - idaapi.msg("[D]FingerManager:recognize_selected_function\n") + #idaapi.msg("[D]FingerManager:recognize_selected_function\n") #modify this constant to allocate more threads. - threads_count = 256 + threads_count = 16 if(len(funcs) < threads_count): threads_count = len(funcs) #start @@ -67,7 +67,7 @@ def recognize_selected_function(self, funcs): #namelist.append(idc.get_func_name(pfn.start_ea)) func_feat.append( ida_func.get_func_feature(pfn.start_ea,self.string_pool)) #func_feat.append( ida_func.get_func_feature(pfn.start_ea)) - print("[N]Trying to recognize %d functions with threads_count threads" %(len(funcs))) + idaapi.msg("[N]Trying to recognize %d functions with threads_count threads" %(len(funcs))) step = step + 1 idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Setting up threads\nAllocating %d threads' %(len(funcs),step,sum_step,threads_count)) thread_pool = [] @@ -75,7 +75,7 @@ def recognize_selected_function(self, funcs): results = [] for i in range(threads_count): q.append(queue.Queue()) - print("[T]Thread #%d gets %d to %d"%(i, i*len(funcs) /threads_count, i*len(funcs) /threads_count +len(funcs) /threads_count)) + #print("[T]Thread #%d gets %d to %d"%(i, i*len(funcs) /threads_count, i*len(funcs) /threads_count +len(funcs) /threads_count)) #t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /threads_count) ,int(len(funcs) /threads_count ),namelist,func_feat,q[i])) t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /threads_count) ,int(len(funcs) /threads_count ),func_feat,q[i])) thread_pool.append(t) @@ -101,10 +101,10 @@ def recognize_selected_function(self, funcs): idc.set_color(r[0].start_ea, idc.CIC_FUNC, 0x98FF98) idaapi.set_name(r[0].start_ea, r[1], idaapi.SN_FORCE) idaapi.update_func(r[0]) - print("[+]Recognize %s: %s" %(r[0],r[1])) + #print("[+]Recognize %s: %s" %(r[0],r[1])) else: failed_func_count = failed_func_count + 1 - print("[-]%s recognize failed" %(r[0])) + #print("[-]%s recognize failed" %(r[0])) step = step + 1 idaapi.replace_wait_box('HIDECANCEL\nFinger:Done!\n[%d/%d] Done! ' %(step,sum_step)) @@ -112,7 +112,7 @@ def recognize_selected_function(self, funcs): print("[N]%d function(s) recognize failed" %(failed_func_count)) def recognize_function_callback(self, menupath): - idaapi.msg("[D]FingerManager:recognize_function_callback\n") + #idaapi.msg("[D]FingerManager:recognize_function_callback\n") ea = idaapi.get_screen_ea() pfn = idaapi.get_func(ea) if pfn: @@ -122,15 +122,15 @@ def recognize_function_callback(self, menupath): idc.set_color(pfn.start_ea, idc.CIC_FUNC, 0x98FF98) idaapi.set_name(pfn.start_ea, func_symbol, idaapi.SN_FORCE) idaapi.update_func(pfn) - print("[+]Recognize %s: %s" %(func_name, func_symbol)) - else: - print("[-]%s recognize failed" %(func_name)) + #print("[+]Recognize %s: %s" %(func_name, func_symbol)) + #else: + # print("[-]%s recognize failed" %(func_name)) else: print("[-]0x%x is not a function" %ea) def recognize_functions_callback(self, menupath): - idaapi.msg("[D]FingerManager:recognize_functions_callback\n") + #idaapi.msg("[D]FingerManager:recognize_functions_callback\n") funcs = [] for ea in idautils.Functions(): funcs.append(idaapi.get_func(ea)) @@ -140,7 +140,7 @@ def recognize_functions_callback(self, menupath): class FingerUIManager: class UIHooks(idaapi.UI_Hooks): def finish_populating_widget_popup(self, widget, popup): - idaapi.msg("[D]FingerUIManager:UIHooks:finish_populating_widget_popup\n") + #idaapi.msg("[D]FingerUIManager:UIHooks:finish_populating_widget_popup\n") if idaapi.get_widget_type(widget) == idaapi.BWN_FUNCS: idaapi.attach_action_to_popup(widget, popup, "Finger:RecognizeSelected", "Finger/") if idaapi.get_widget_type(widget) == idaapi.BWN_DISASM: @@ -149,13 +149,13 @@ def finish_populating_widget_popup(self, widget, popup): class ActionHandler(idaapi.action_handler_t): def __init__(self, name, label, shortcut=None, tooltip=None, icon=-1, flags=0): - idaapi.msg("[D]FingerUIManager:ActionHandler:__init__\n") + #idaapi.msg("[D]FingerUIManager:ActionHandler:__init__\n") idaapi.action_handler_t.__init__(self) self.name = name self.action_desc = idaapi.action_desc_t(name, label, self, shortcut, tooltip, icon, flags) def register_action(self, callback, menupath=None): - idaapi.msg("[D]FingerUIManager:ActionHandler:register_action\n") + #idaapi.msg("[D]FingerUIManager:ActionHandler:register_action\n") self.callback = callback if not idaapi.register_action(self.action_desc): return False @@ -164,23 +164,23 @@ def register_action(self, callback, menupath=None): return True def activate(self, ctx): - idaapi.msg("[D]FingerUIManager:ActionHandler:activate\n") + #idaapi.msg("[D]FingerUIManager:ActionHandler:activate\n") self.callback(ctx) def update(self, ctx): - idaapi.msg("[D]FingerUIManager:ActionHandler:update\n") + #idaapi.msg("[D]FingerUIManager:ActionHandler:update\n") return idaapi.AST_ENABLE_ALWAYS def __init__(self, name): - idaapi.msg("[D]FingerUIManager:__init__\n") + #idaapi.msg("[D]FingerUIManager:__init__\n") self.name = name self.mgr = FingerManager() self.hooks = FingerUIManager.UIHooks() def register_actions(self): - idaapi.msg("[D]FingerUIManager:register_actions\n") + #idaapi.msg("[D]FingerUIManager:register_actions\n") menupath = self.name idaapi.create_menu(menupath, self.name, "Help") @@ -196,15 +196,15 @@ def register_actions(self): def selected_function_callback(self, ctx): - idaapi.msg("[D]FingerUIManager:selected_function_callback\n") + #idaapi.msg("[D]FingerUIManager:selected_function_callback\n") funcs = list(idaapi.getn_func, ctx.chooser_selection) if ctx.action == "Finger:RecognizeSelected": self.mgr.recognize_selected_function(funcs) def check_ida_version(): - idaapi.msg("[D]check_ida_version()\n") - idaapi.msg("[D]idaapi.IDA_SDK_VERSION =%d\n"%idaapi.IDA_SDK_VERSION) + #idaapi.msg("[D]check_ida_version()\n") + #idaapi.msg("[D]idaapi.IDA_SDK_VERSION =%d\n"%idaapi.IDA_SDK_VERSION) if idaapi.IDA_SDK_VERSION < 700: print("[-]Finger support 7.x IDA, please update your IDA version.") return False @@ -218,13 +218,13 @@ class FingerPlugin(idaapi.plugin_t): def init(self): if check_ida_version(): - idaapi.msg("[+]Finger plugin starts\n") + #idaapi.msg("[+]Finger plugin starts\n") manager = FingerUIManager(FingerPlugin.wanted_name) if manager.register_actions(): - idaapi.msg("[D]idaapi.PLUGIN_OK\n") + #idaapi.msg("[D]idaapi.PLUGIN_OK\n") return idaapi.PLUGIN_OK - idaapi.msg("[D]idaapi.PLUGIN_SKIP\n") + #idaapi.msg("[D]idaapi.PLUGIN_SKIP\n") return idaapi.PLUGIN_SKIP def run(self, ctx): From b3cf74e0799b6aec42497e0eea650f44d80e3a36 Mon Sep 17 00:00:00 2001 From: Jerry Date: Mon, 3 Jul 2023 16:51:03 +0800 Subject: [PATCH 17/19] Error 1827 fixed --- finger_plugin_complete.py | 466 ++++++++++++++++++ .../Lib}/site-packages/finger_sdk/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin .../__pycache__/client.cpython-38.pyc | Bin .../__pycache__/ida_func.cpython-38.pyc | Bin .../Lib}/site-packages/finger_sdk/client.py | 0 .../Lib}/site-packages/finger_sdk/ida_func.py | 0 finger_plugin.py => separate/finger_plugin.py | 0 8 files changed, 466 insertions(+) create mode 100644 finger_plugin_complete.py rename {Lib => separate/Lib}/site-packages/finger_sdk/__init__.py (100%) rename {Lib => separate/Lib}/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc (100%) rename {Lib => separate/Lib}/site-packages/finger_sdk/__pycache__/client.cpython-38.pyc (100%) rename {Lib => separate/Lib}/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc (100%) rename {Lib => separate/Lib}/site-packages/finger_sdk/client.py (100%) rename {Lib => separate/Lib}/site-packages/finger_sdk/ida_func.py (100%) rename finger_plugin.py => separate/finger_plugin.py (100%) diff --git a/finger_plugin_complete.py b/finger_plugin_complete.py new file mode 100644 index 0000000..fba8107 --- /dev/null +++ b/finger_plugin_complete.py @@ -0,0 +1,466 @@ +import idc +import idaapi +import idautils +import traceback +import threading +import queue +import json +import base64 +import hashlib +import platform +import requests + +class Client(object): + def __init__(self, url, headers, timeout): + self.url = url + self.headers = headers + self.timeout = timeout + + + def str2byte(self, l): + for i in range(len(l)): + l[i] = l[i].encode("utf-8") + return l + + + def byte2str(self, l): + for i in range(len(l)): + l[i] = l[i].decode("utf-8") + return l + + + def my_encode(self, msg_list): + if "str" in str(type(msg_list[0])): + msg_bytes_list = self.str2byte(msg_list) + else: + msg_bytes_list = msg_list + msg_encode = list(map(base64.b64encode, msg_bytes_list)) + msg_str_list = self.byte2str(msg_encode) + return msg_str_list + + + def my_decode(self, msg): + msg_bytes_list = self.str2byte(msg) + msg_decode = list(map(base64.b64decode, msg_bytes_list)) + return msg_decode + + + def gen_msg_py2(self, content): + content_encode = dict() + content_encode["extmsg"] = map(base64.b64encode, content["extmsg"]) + content_encode["ins_bytes"] = map(base64.b64encode, content["ins_bytes"]) + content_encode["ins_str"] = map(base64.b64encode, content["ins_str"]) + content_encode["func_name"] = content["func_name"] + func_id = hashlib.md5(json.dumps(content_encode).encode("utf-8")).hexdigest() + content_encode["md5"] = func_id + msg = json.dumps(content_encode) + return msg, func_id + + + def gen_msg_py3(self, content): + content_encode = dict() + content_encode["extmsg"] = self.my_encode(content["extmsg"]) + content_encode["ins_bytes"] = self.my_encode(content["ins_bytes"]) + content_encode["ins_str"] = self.my_encode(content["ins_str"]) + content_encode["func_name"] = content["func_name"] + func_id = hashlib.md5(json.dumps(content_encode).encode("utf-8")).hexdigest() + content_encode["md5"] = func_id + msg = json.dumps(content_encode) + return msg, func_id + + + def filter_func_symbol(self, func_symbol): + if not func_symbol: + return False + filter_list = ["unknow", "nullsub"] + for item in filter_list: + if item in func_symbol: + return False + return True + + + def recognize_function(self, content): + version = platform.python_version() + res = False + func_id = "" + symbol_dict = dict() + + if version.startswith('3'): + msg, func_id = self.gen_msg_py3(content) + else: + msg, func_id = self.gen_msg_py2(content) + try: + self.session = requests.Session() + res = self.session.post(self.url, data=msg, headers=self.headers, timeout=self.timeout) + if res: + symbol_dict[func_id] = self.get_func_symbol(res.text) + except Exception as e: + raise RuntimeError("upload function failed") + return func_id, symbol_dict + + + def get_func_symbol(self, res): + func_symbol = "" + if len(res) <= 4: + return func_symbol + try: + msg_dict = json.loads(res) + func_symbol = msg_dict["func_symbol"] + if self.filter_func_symbol(func_symbol): + return func_symbol + except Exception as e: + raise RuntimeError("get function symbol failed") + return func_symbol + +class FuncSigFeature: + #using static class variable to avoid repeating loading. + #this code may not secured under 7.7, removed + #20230507 + #string_pool = idautils.Strings() + def __init__(self,string_pool): + self.file_path = idc.get_input_file_path() + + self.code_list = ["",".text",".plt",".got","extern",".pdata",".bss"] + + self.control_ins_list = ["call","jc","jnc","jz","jnz","js","jns","jo","jno","jp", + "jpe","jnp","jpo","ja","jnbe","jae","jnb","jb","jnae","jbe", + "jna","je","jne","jg","jnle","jge","jnl","jl","jnge","jle","jng"] + #may not compatible for python 2 + self.string_list = dict() + for s in string_pool: + self.string_list[str(s)] = s.ea + + + def get_file_structure(self): + info = idaapi.get_inf_structure() + arch = info.procName + if info.is_be(): + endian = "MSB" + else: + endian = "LSB" + return arch, endian + + + def get_file_type(self): + file_format = "" + file_type = "" + info = idaapi.get_inf_structure() + if info.is_64bit(): + file_format = "64" + elif info.is_32bit(): + file_format = "32" + if info.filetype == idaapi.f_PE: + file_type = "PE" + elif info.filetype == idaapi.f_ELF: + file_type = "ELF" + return file_format, file_type + + + def get_module_info(self): + module_info = "" + if len(idc.ARGV) == 2: + module_info = idc.ARGV[1] + return module_info + + + def byte2str(self, l): + if "bytes" in str(type(l)): + l = l.decode() + return l + + + def extract_const(self, ins_addr): + const_str = "" + op_str = idc.print_insn_mnem(ins_addr) + if op_str not in self.control_ins_list: + for i in range(2): + operand_type = idc.get_operand_type(ins_addr, i) + if operand_type == idc.o_mem: + const_addr = idc.get_operand_value(ins_addr, i) + if idc.get_segm_name(const_addr) not in self.code_list: + str_const = idc.get_strlit_contents(const_addr) + if str_const: + str_const = self.byte2str(str_const) + if (str_const in self.string_list) and (const_addr == self.string_list[str_const]): + const_str += str_const + break + return const_str + + + def get_ins_feature(self, start_ea): + ins_str_list = list() + ins_bytes_list = list() + ins_list = list(idautils.FuncItems(start_ea)) + for ins_addr in ins_list: + ins_bytes = idc.get_bytes(ins_addr, idc.get_item_size(ins_addr)) + ins_bytes_list.append(ins_bytes) + ins_str = self.extract_const(ins_addr) + ins_str_list.append(ins_str) + return ins_bytes_list, ins_str_list + + + def filter_segment(self, func_addr): + ignore_list = ["extern",".plt",".got",".idata"] + if idc.get_segm_name(func_addr) in ignore_list: + return True + else: + return False + + +def get_func_feature(ea,string_pool): + content = dict() + pfn = idaapi.get_func(ea) + if pfn: + func_addr = pfn.start_ea + Func = FuncSigFeature(string_pool) + if Func.filter_segment(func_addr): + return None + arch, endian = Func.get_file_structure() + file_format, file_type = Func.get_file_type() + module_info = Func.get_module_info() + ins_bytes_list, ins_str_list = Func.get_ins_feature(func_addr) + content["extmsg"] = [arch, endian, file_format, file_type, module_info] + content["ins_bytes"] = ins_bytes_list + content["ins_str"] = ins_str_list + content["func_name"] = idaapi.get_func_name(func_addr) + return content + else: + return None + +class FingerManager: + def __init__(self): + + #idaapi.msg("[D]FingerManager:__init__\n") + self.string_pool = '' + #self.string_pool = idautils.Strings() + + self.url = "https://sec-lab.aliyun.com/finger/recognize/" + self.headers = {'content-type': 'application/json'} + self.timeout = 5 + self.client = None + self.verify = False + + def recognize_function(self,func_feat): + #idaapi.msg("[D]FingerManager:recognize_function\n") + func_symbol = None + try: + self.client = Client(self.url, self.headers, self.timeout) + #func_feat = ida_func.get_func_feature(start_ea) + if func_feat: + func_id, res = self.client.recognize_function(func_feat) + if res and res[func_id]: + func_symbol = res[func_id] + except Exception as e: + idaapi.msg('[x] Exception occurred when recognize %s'%func_feat) + idaapi.msg(traceback.format_exc()) + if func_symbol: + func_symbol = str(func_symbol) + return func_symbol + + #def _internal_recognize_function(self, funcs, startpos,length, namelist,func_feat, queue): + def _internal_recognize_function(self, funcs, startpos,length,func_feat, queue): + #idaapi.msg("[D]FingerManager:_internal_recognize_function\n") + #multi-threading version + try: + for ipfn in range(startpos,startpos+length,1): + #func_name = namelist[ipfn] + func_symbol = self.recognize_function(func_feat[ipfn]) + if(func_symbol): + queue.put([funcs[ipfn],func_symbol]) + else: + queue.put([funcs[ipfn],'']) + return + except Exception as ea: + idaapi.msg('[x] Exception occurred when recognize %s'%func_feat) + idaapi.msg(traceback.format_exc()) + + def recognize_selected_function(self, funcs): + try: + self.string_pool = idautils.Strings() + + #idaapi.msg("[D]FingerManager:recognize_selected_function\n") + #modify this constant to allocate more threads. + threads_count = 16 + if(len(funcs) < threads_count): + threads_count = len(funcs) + #start + step = 1 + sum_step = 6 + idaapi.show_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Getting function features' %(len(funcs),step,sum_step)) + #namelist = [] + func_feat = [] + for pfn in funcs: + #namelist.append(idc.get_func_name(pfn.start_ea)) + func_feat.append( get_func_feature(pfn.start_ea,self.string_pool)) + #func_feat.append( ida_func.get_func_feature(pfn.start_ea)) + idaapi.msg(f"[N]Trying to recognize {len(funcs)} functions with {threads_count} threads") + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Setting up threads\nAllocating %d threads' %(len(funcs),step,sum_step,threads_count)) + thread_pool = [] + q = [] + results = [] + for i in range(threads_count): + q.append(queue.Queue()) + #print("[T]Thread #%d gets %d to %d"%(i, i*len(funcs) /threads_count, i*len(funcs) /threads_count +len(funcs) /threads_count)) + #t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /threads_count) ,int(len(funcs) /threads_count ),namelist,func_feat,q[i])) + t = threading.Thread(target = self._internal_recognize_function, args = ( funcs, int(i*len(funcs) /threads_count) ,int(len(funcs) /threads_count ),func_feat,q[i])) + thread_pool.append(t) + t.start() + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Waiting for threads to end' %(len(funcs),step,sum_step)) + for thread in thread_pool: + thread.join() + + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Recognizing %d functions...\n[%d/%d] Getting thread results' %(len(funcs),step,sum_step)) + + for i in range(threads_count): + for j in range(q[i].qsize()): + results.append(q[i].get()) + print("[+]Successfully fetched %d func_symbol"%len(results)) + + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Successfully fetched %d func_symbol\n[%d/%d] Setting up ida details' %(len(results),step,sum_step)) + failed_func_count = 0 + for r in results: + if r[1] != '': + idc.set_color(r[0].start_ea, idc.CIC_FUNC, 0x98FF98) + idaapi.set_name(r[0].start_ea, r[1], idaapi.SN_FORCE) + idaapi.update_func(r[0]) + #print("[+]Recognize %s: %s" %(r[0],r[1])) + else: + failed_func_count = failed_func_count + 1 + #print("[-]%s recognize failed" %(r[0])) + + step = step + 1 + idaapi.replace_wait_box('HIDECANCEL\nFinger:Done!\n[%d/%d] Done! ' %(step,sum_step)) + idaapi.hide_wait_box() + print("[N]%d function(s) recognize failed" %(failed_func_count)) + except Exception as ea: + idaapi.msg('[x] Exception occurred when recognize %s'%func_feat) + idaapi.msg(traceback.format_exc()) + def recognize_function_callback(self,menupath): + #idaapi.msg("[D]FingerManager:recognize_function_callback\n") + ea = idaapi.get_screen_ea() + pfn = idaapi.get_func(ea) + if pfn: + func_name = idc.get_func_name(pfn.start_ea) + func_symbol = self.recognize_function(pfn.start_ea) + if func_symbol: + idc.set_color(pfn.start_ea, idc.CIC_FUNC, 0x98FF98) + idaapi.set_name(pfn.start_ea, func_symbol, idaapi.SN_FORCE) + idaapi.update_func(pfn) + #print("[+]Recognize %s: %s" %(func_name, func_symbol)) + #else: + # print("[-]%s recognize failed" %(func_name)) + else: + print("[-]0x%x is not a function" %ea) + + + def recognize_functions_callback(self,menupath): + #idaapi.msg("[D]FingerManager:recognize_functions_callback\n") + funcs = [] + for ea in idautils.Functions(): + funcs.append(idaapi.get_func(ea)) + self.recognize_selected_function(funcs) + + +class FingerUIManager: + class UIHooks(idaapi.UI_Hooks): + def finish_populating_widget_popup(self, widget, popup): + #idaapi.msg("[D]FingerUIManager:UIHooks:finish_populating_widget_popup\n") + if idaapi.get_widget_type(widget) == idaapi.BWN_FUNCS: + idaapi.attach_action_to_popup(widget, popup, "Finger:RecognizeSelected", "Finger/") + if idaapi.get_widget_type(widget) == idaapi.BWN_DISASM: + idaapi.attach_action_to_popup(widget, popup, "Finger:RecognizeFunction", "Finger/") + + + class ActionHandler(idaapi.action_handler_t): + def __init__(self, name, label, shortcut=None, tooltip=None, icon=-1, flags=0): + #idaapi.msg("[D]FingerUIManager:ActionHandler:__init__\n") + idaapi.action_handler_t.__init__(self) + self.name = name + self.action_desc = idaapi.action_desc_t(name, label, self, shortcut, tooltip, icon, flags) + + def register_action(self, callback, menupath=None): + #idaapi.msg("[D]FingerUIManager:ActionHandler:register_action\n") + self.callback = callback + if not idaapi.register_action(self.action_desc): + return False + if menupath and not idaapi.attach_action_to_menu(menupath, self.name, idaapi.SETMENU_APP): + return False + return True + + def activate(self, ctx): + #idaapi.msg("[D]FingerUIManager:ActionHandler:activate\n") + self.callback(ctx) + + def update(self, ctx): + #idaapi.msg("[D]FingerUIManager:ActionHandler:update\n") + return idaapi.AST_ENABLE_ALWAYS + + + def __init__(self, name): + #idaapi.msg("[D]FingerUIManager:__init__\n") + self.name = name + self.mgr = FingerManager() + self.hooks = FingerUIManager.UIHooks() + + def register_actions(self): + + #idaapi.msg("[D]FingerUIManager:register_actions\n") + menupath = self.name + idaapi.create_menu(menupath, self.name, "Help") + + action = FingerUIManager.ActionHandler("Finger:RecognizeFunctions", "Recognize all functions", "") + action.register_action(self.mgr.recognize_functions_callback, menupath) + action = FingerUIManager.ActionHandler("Finger:RecognizeFunction", "Recognize function", "") + action.register_action(self.mgr.recognize_function_callback, menupath) + recognize_action = FingerUIManager.ActionHandler("Finger:RecognizeSelected", "Recognize function") + if recognize_action.register_action(self.selected_function_callback): + self.hooks.hook() + return True + return False + + + def selected_function_callback(self, ctx): + #idaapi.msg("[D]FingerUIManager:selected_function_callback\n") + funcs = list(idaapi.getn_func, ctx.chooser_selection) + if ctx.action == "Finger:RecognizeSelected": + self.mgr.recognize_selected_function(funcs) + + +def check_ida_version(): + #idaapi.msg("[D]check_ida_version()\n") + #idaapi.msg("[D]idaapi.IDA_SDK_VERSION =%d\n"%idaapi.IDA_SDK_VERSION) + if idaapi.IDA_SDK_VERSION < 700: + print("[-]Finger support 7.x IDA, please update your IDA version.") + return False + return True + + +class FingerPlugin(idaapi.plugin_t): + wanted_name = "Finger" + comment, help, wanted_hotkey = "", "", "" + flags = idaapi.PLUGIN_KEEP + + def init(self): + if check_ida_version(): + #idaapi.msg("[+]Finger plugin starts\n") + manager = FingerUIManager(FingerPlugin.wanted_name) + if manager.register_actions(): + #idaapi.msg("[D]idaapi.PLUGIN_OK\n") + return idaapi.PLUGIN_OK + + #idaapi.msg("[D]idaapi.PLUGIN_SKIP\n") + return idaapi.PLUGIN_SKIP + + def run(self, ctx): + return + + def term(self): + return + + +def PLUGIN_ENTRY(): + return FingerPlugin() diff --git a/Lib/site-packages/finger_sdk/__init__.py b/separate/Lib/site-packages/finger_sdk/__init__.py similarity index 100% rename from Lib/site-packages/finger_sdk/__init__.py rename to separate/Lib/site-packages/finger_sdk/__init__.py diff --git a/Lib/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc b/separate/Lib/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc similarity index 100% rename from Lib/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc rename to separate/Lib/site-packages/finger_sdk/__pycache__/__init__.cpython-38.pyc diff --git a/Lib/site-packages/finger_sdk/__pycache__/client.cpython-38.pyc b/separate/Lib/site-packages/finger_sdk/__pycache__/client.cpython-38.pyc similarity index 100% rename from Lib/site-packages/finger_sdk/__pycache__/client.cpython-38.pyc rename to separate/Lib/site-packages/finger_sdk/__pycache__/client.cpython-38.pyc diff --git a/Lib/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc b/separate/Lib/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc similarity index 100% rename from Lib/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc rename to separate/Lib/site-packages/finger_sdk/__pycache__/ida_func.cpython-38.pyc diff --git a/Lib/site-packages/finger_sdk/client.py b/separate/Lib/site-packages/finger_sdk/client.py similarity index 100% rename from Lib/site-packages/finger_sdk/client.py rename to separate/Lib/site-packages/finger_sdk/client.py diff --git a/Lib/site-packages/finger_sdk/ida_func.py b/separate/Lib/site-packages/finger_sdk/ida_func.py similarity index 100% rename from Lib/site-packages/finger_sdk/ida_func.py rename to separate/Lib/site-packages/finger_sdk/ida_func.py diff --git a/finger_plugin.py b/separate/finger_plugin.py similarity index 100% rename from finger_plugin.py rename to separate/finger_plugin.py From a547b7b616c837086b51c839362a75ee9c93128e Mon Sep 17 00:00:00 2001 From: Jerry Date: Mon, 3 Jul 2023 17:04:15 +0800 Subject: [PATCH 18/19] update readme --- README.md | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 600110a..1197672 100644 --- a/README.md +++ b/README.md @@ -2,44 +2,50 @@ An improved version of brilliant ida plugin finger, fixed 'always pop up' issue and implemented multi-threading to accelerate the recognition process. -**IMPORTANT: THIS IS PLUGIN IS STILL UNDER TEST AND HASNT PUBLISHED TO SITE YET!** +**IMPORTANT: THIS PLUGIN IS STILL UNDER TEST!** ## Installation -* Download release version of source code. -* Replace the `finger_plugin.py` in `IDA_PATH/plugins` -* **Replace the `ida_func.py` in `%AppData%\Roaming\Python\Python38\site-packages\finger_sdk\client.py`** (differs from users) - -Enjoy! +* Always pull this git or download `finger_plugin_complete.py`, release may be out-of-date. +* Paste `finger_plugin_complete` under `\plugins\`, no further actions needed. +* **This plugin shall not been co-exist with old finger plugin ** ## Known issue * After running the plugin, your IDA will stuck into no-response status, it takes time depends on your functions count. (UI Completed) - + You can change thread count in source file: + + ```python + #finger_plugin_complete.py: 277 + #------------------------------- + def recognize_selected_function(self, funcs): + try: + self.string_pool = idautils.Strings() + + #idaapi.msg("[D]FingerManager:recognize_selected_function\n") + #modify this constant to allocate more threads. + threads_count = 16 + ``` + * Sometimes due to remote server SSL problem, you will receive exception: ``` requests.exceptions.SSLError: HTTPSConnectionPool(host='sec-lab.aliyun.com', port=443): Max retries exceeded with url: /finger/recognize/ (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:1131)'))) ``` - Most of time this is normal. If it in deed influenced your project , considering using original version. -* Python 3 supported, any issue of bug please propose. + Most of time this is normal. If it in deed influenced your project , try using fewer threads. This may due to remote denial of services problem, when may be issued by too many requests. + +* **`TypeError: object of type 'map' has no len()`** -* **`TypeError: object of type 'map' has no len()`** Solution manually modify `finger_plugin.py` at line 177: + Manually modify `finger_plugin_complete.py` ``` python # funcs = map(idaapi.getn_func, ctx.chooser_selection) # old funcs = list(idaapi.getn_func, ctx.chooser_selection) # new -``` +``` ![2022-10-07 22-19-17 00_00_10-00_01_10~1](https://user-images.githubusercontent.com/20926583/194583856-81b9a536-9918-4eec-bb44-ba6a308ec007.gif) - - - - - - ----- # Finger Finger, a tool for recognizing function symbol. @@ -58,6 +64,8 @@ pip install finger_sdk ~~~ After installing finger python SDK, you can check out the finger/exampls/recognize.py for more information. +*In fact, if you are using my fork, this sdk is not needed anymore.* + ## Finger IDA Plugin Copy plugin/finger_plugin.py to your IDA_PATH/plugins path. ### upload function From 2b3cede4f6f9970db6b8b7c6cb2cad79010b08e5 Mon Sep 17 00:00:00 2001 From: JANlittle Date: Mon, 8 Apr 2024 10:43:17 +0800 Subject: [PATCH 19/19] fix certificate expiration & single function recognize --- finger_plugin_complete.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/finger_plugin_complete.py b/finger_plugin_complete.py index fba8107..2def5e1 100644 --- a/finger_plugin_complete.py +++ b/finger_plugin_complete.py @@ -91,6 +91,7 @@ def recognize_function(self, content): msg, func_id = self.gen_msg_py2(content) try: self.session = requests.Session() + self.session.verify = False res = self.session.post(self.url, data=msg, headers=self.headers, timeout=self.timeout) if res: symbol_dict[func_id] = self.get_func_symbol(res.text) @@ -133,7 +134,7 @@ def __init__(self,string_pool): def get_file_structure(self): info = idaapi.get_inf_structure() - arch = info.procName + arch = info.procname if info.is_be(): endian = "MSB" else: @@ -345,7 +346,7 @@ def recognize_function_callback(self,menupath): pfn = idaapi.get_func(ea) if pfn: func_name = idc.get_func_name(pfn.start_ea) - func_symbol = self.recognize_function(pfn.start_ea) + func_symbol = self.recognize_selected_function([pfn]) if func_symbol: idc.set_color(pfn.start_ea, idc.CIC_FUNC, 0x98FF98) idaapi.set_name(pfn.start_ea, func_symbol, idaapi.SN_FORCE)