From 89106b78fbff5b23165a599cff705cdbf091fe78 Mon Sep 17 00:00:00 2001 From: Ulf Frisk Date: Thu, 28 Feb 2019 11:23:04 +0100 Subject: [PATCH] Version 2.1 --- README.md | 6 + files/leechcore.dll | Bin 75776 -> 76288 bytes files/leechcore.h | 7 +- files/leechsvc.exe | Bin 27648 -> 29184 bytes files/vmm.dll | Bin 131072 -> 136192 bytes files/vmm.lib | Bin 8938 -> 10500 bytes files/vmmdll.h | 103 ++++++++++++++++- files/vmmpy.py | 56 ++++++++- files/vmmpyc.pyd | Bin 32768 -> 35840 bytes vmm/leechcore.h | 25 ++-- vmm/m_ldrmodules.c | 10 +- vmm/mm_x64_winpaged.c | 54 +++++++++ vmm/mm_x64_winpaged.h | 26 +++++ vmm/pe.c | 121 ++++++++++++++++++-- vmm/pe.h | 42 +++++++ vmm/statistics.c | 126 ++++++++++----------- vmm/statistics.h | 82 ++++++++------ vmm/version.h | 2 +- vmm/vmm.c | 21 +++- vmm/vmm.h | 19 +++- vmm/vmm.vcxproj | 2 + vmm/vmm.vcxproj.filters | 6 + vmm/vmmdll.c | 172 +++++++++++++++++++++++++--- vmm/vmmdll.def | 7 ++ vmm/vmmdll.h | 103 ++++++++++++++++- vmm/vmmproc.c | 12 +- vmm/vmmwin.c | 62 +++++----- vmm/vmmwin.h | 12 +- vmm/vmmwininit.c | 10 +- vmm_example/leechcore.h | 25 ++-- vmm_example/vmmdll.h | 103 ++++++++++++++++- vmm_example/vmmdll_example.c | 90 +++++++++++++-- vmmpyc/leechcore.h | 25 ++-- vmmpyc/version.h | 2 +- vmmpyc/vmmdll.h | 103 ++++++++++++++++- vmmpyc/vmmpyc.c | 214 ++++++++++++++++++++++++++++------- 36 files changed, 1366 insertions(+), 282 deletions(-) create mode 100644 vmm/mm_x64_winpaged.c create mode 100644 vmm/mm_x64_winpaged.h diff --git a/README.md b/README.md index 2540017..3695088 100644 --- a/README.md +++ b/README.md @@ -137,3 +137,9 @@ v2.0 * Support for Microsoft Crash Dumps - such as created by default by [Comae DumpIt](https://www.comae.com). * Hyper-V save files. * Remote capture via remotely installed LeechService. + +v2.1 +* New APIs: + * IAT/EAT hook functionality. + * Limited Windows 10 MemCompression support. +* Bug fixes. diff --git a/files/leechcore.dll b/files/leechcore.dll index c269e892ca2cdb1193e7829a9660ed6ce39f77dd..16519ec86a8850ea355bcc15ed33c7f3388bd7f4 100644 GIT binary patch delta 28845 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 Qt@5|FytSu`S$swOKMhF@TL1t6 diff --git a/files/leechcore.h b/files/leechcore.h index d49a089..46af16a 100644 --- a/files/leechcore.h +++ b/files/leechcore.h @@ -111,7 +111,7 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // -// Header Version: 1.0.2 +// Header Version: 1.1.0 // #ifndef __LEECHCORE_H__ #define __LEECHCORE_H__ @@ -229,7 +229,10 @@ typedef struct tdLEECHCORE_PAGESTAT_MINIMAL { } LEECHCORE_PAGESTAT_MINIMAL, *PLEECHCORE_PAGESTAT_MINIMAL; /* -* Open a connection to the target device. +* Open a connection to the target device. The LeechCore initialization may fail +* if the underlying device cannot be opened or if the LeechCore is already +* initialized. If already initialized please connect with device EXISTING or +* call LeechCore_Close() before opening a new device. * -- pInformation * -- result */ diff --git a/files/leechsvc.exe b/files/leechsvc.exe index cb2c4233d9a67044497461e5ab0737bb5fe5d59f..c55e7d134dbb8bf4eedad37841e666dc8bceb927 100644 GIT binary patch delta 13377 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 delta 11738 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 delta 50315 zcma%Ed0bV++rQ_)RZ#I_aAn`G2!e=$3*y2R(1TtS_g!(<+(;qK4G>W2c-5NfXqj4C zYN=70qPF0^pyHCLnWklWh%{|+EzkRX=3GGQ_s8p}%Q-XiZ1c=B&&)hCbIRM~l9%VQ zA}@E1SZDEfV937?xm0fwPk%RN z(l<+yFV)LDf1E!jp1;hW$~W}>exFHWCW5E%&1)gv%`#FAhOvw47#_XU zcbLKP@2(*@l!~u~__AQprA@(D6uF~}*b=#C z{skKhnK3iQj>;ZoF!Y&;0$>`l@qCeu5QAD8L<9Y2xQg6o{v{a9i$%q2P-LN#<(fd#~8Ty22_Jne=2O(M57?y7g!zV*1}7ygL5GM4s{-i zzq_1!h<^#p**M%hLoi5ow<0{$bx$=-lHHE|DX^D~BjWxfse=l3`xD3P#_94A$!`AX zB@Q1=@LqtAJ8LZ^J*3Ge)b*LHZk^6T7VB0gR{VS3(mg?dm*fhy$6uP3UZiDv zrOOpIyU}|RYh~IXjAa*0G2(^2tgibMaZ*o~a?6UVdM7rc?b@`V^-96Rzt z@C)MvLyFVg;?o3-$bQ{>|SG;(MYcM?Bh( zb!|LO9Mz9)ZQRu4A`!7{OKh5aN`2Ce{nR)__=y?4@`Pb*kyp5w*_Une8ti|lH-R0M zL8g*t@ywP-@|mYDic1rS~`?lZE*#5l8iA zTYY+o*HYM1pYH*F*|(o?k;VEYw-`i5csV=Nmask#waxa?TWmk&%LSArCk0nBZhOWz zuCn+2T1U3-Me->|boB+EGWL^Qy#3${yCkNOBVK7?}=!d^s z0zVS^v4KG`;=nCzanL03*G}B##%+OU+~%VC!RETNslf$8Uv@7zGo-3Rb#FHnc0zqp z<8CBOi#9Df1pY9j4g=YYkT}TsVMvhY0N9`@_4NIH&bH!ymL$n;JU@V43TfqeY_MP` zo@WVG2mPpNPAc%04`4x|!NV@p8dUcb`49|nGC*NhZ-AEi$#pWo3UwA_sOk&@JoOB` z!vKHkPw?h=psBe8Z(jhzt5B0Wuy;cJ>W2dr{ZBL7d$WC^uLuKJVpxb+(v6J>ixT@L zGCAxsv7#&MYzc>A##^Gr?(Nwdmd;{AS9adAQcSe6=}iV=*g4YVrOxNTDgRKW)!9(h zP?DS9*3%`_$VInvq9Nap$Z~WjQ=v!&cJl&s4OFP@ST-wsn2^rgCVR1}@X7UUf}zSq zlFQZkZP}=%ulamutyXlkTfNwsWIxTcI*}|sqN$kQh7FIHD0;PFMxe~V|oMMeqTnP0QS8#IyFgqbs>mvR6}KIbqMrkoFkR z_ce`3xXnAk_BZb;I(KCCVv@z>v8-22tT;TD<;OG?QrYI1Iq@a!sI$RLV^c{tyBS@P2=v_%HjOk-|E}w>GbbE{n5a-@jLA8XPU?M5M4q@e#R)Y78RV;bA+F z-tBHpKu=8qI1RHd5Q=-)k|D8cbkwIINbA3P1uumZG}B7E+Nw8S{DIr{80xsHlbPWh%kep zGSf&6W!}RJrgRVtm{hy~Gb}EH9V~%YG89~QuKH4v;q&a~?Hnvs2lE8j=o+vGX9}(h zRc|-|-{*k-Iv|bpRshBwlMLzISS)sG-gLRbg;pRA<1^X)m&OJ6zopCHh4!2m6)1>MU*?d@59Vpw7| zByU!kAUoDVG7g#Ap44tC`W3`%vfButV1}~X%6>>J7bBzCmJYs6zT$!=CFqcg0J&$H ztHE$2#gawOxMP}ma$8o_A;^DCB%v?0;R&XdZG^t?-<(TMgUDMPstZGvuj(l z@`1`#=!TAJw&i@@YsVX8`xlM|p&5$WA5*DHjBnMTZX9%^4*F6xs5gOn5U9Bp^fIq! z0D%THsfP3?$TNX}JO@!7{6FQ8m)mmwxA!{u2NURW0)4F(bTJ2A)t2q;=w`AVuSw{H;`+VhNVyVz@v|&k5dGx)SRINnUDMN4+8Lgqq;P zjGcSC-0VPYUebn*=-kPrlqX(n!!~qoitcoxbAa#m#n60NiCN*vHl_$sOU5gRVi(hWddtMy8H`~qDCp8J%0m&*|YvCGjxELMI z4{$wpv#O*3k3SPh#;5TdpYr{7Ge2v9$M-z{l%5}m{Hjvc!y4>yjOV{on_pVW79js= zN1`)P&+hz&u}5Qp@th3r6(mgdy**5W|5laBFTEk+^7I2^}i zOdwfy5k@nA2gdDN#G|UOBy$EmIr!{a@OO8y_qs%jYj3fNF4J?{pc7~wXk+&jdp98! zUd3*1|3C7&KAY#F!*%~3c?14O-jM&1H$u1#WX1b#X zd>9H>7Ih6S+VTnB;top1g_aDI*w9asZ1Q89e7$->D%ow840T-yT=``zbz`bi9bk zk7@B2u&A`e2d2X9cqs+FyfmjHQsQ0H;`fmupN~71BLAhC2gQ*8rj>xHuowkYgIEd9 zGy;6!Cr;{B!0KH7mMV~k*;oQ9Y$_ZI=oEQ^cWTiMQ(;dbmr}igQ;Uw73gK;#7-;Eb zcQ(lb{nMQ;q&Z!%B_1{{Y>JZZcIOX3+mI?>wMAXDIbBUpJYZVvh7w!ix2A>8I>qLWFKLPAOp7lQfQ-p~dePaO2Dx%CizFXNk-Pa!cgB20ysD8;4Q3i zxzo5B7Wq@Vl(=QgW9o08DEIa8nBe! zkqtWgRYQHbk-gH}A}0E>wY>+4Keu6zdT$gvv|;b}2^BZhV~6|1HrVM?4IKX=6Z>`+ z6>pZ@x1X5Wnyu>ljW|7?P45>ahPGp?`!yHWyRh9zFO+&XTlbMC6hu%C-We zdpOeHO4bDABdP=;pyOn35YGm_O-@Fg%rD_pDxP9NU0Un!DV9tG z9PI>>i-JLEP%ll*5va&Ww>JO{Cz~nx3=KpQMiOb?FZEz0WOGRlP>OQ2OEYyrS)~j4 zvRFy(8f_`e)8PE!7t4?w zdLXGUBDTQ=ufzl7QcfGvXl$G|e!MBD5akBL?y>lznTNw^C(Mn_t}o@v<;8Pj)uJbw zmM}Ly`tUc@RCTF9W50d}J5D`Ijn+{Z| z;9%@f-OH1wmx_*M+iZf>-6o`AeQC{q=UgRBX*m5mNGJ zrvm}=A8Y0&A*Z&$mOqyPoAKf&vIc=+^r9yan$8SQh zbi8Pl?^^8>y~fv?76|&RXU3uZ8u_*4l;rZLb7@Ye)Eisai$mLsD+AewLu15EA?(|s zLpvpdN1A+2_1J>2KJ5Du1>)ESY}CkRohPk??7H%k3cj#leMi=5L^vLmdzdC)lH}h} zMM~S`W2jx(o?s4wCQ#ql#Ey@Q7Z25cJ=4p{{I`MmW`=a$t^7}lbDBR-aR$;U|Dkwe zBPg~OK+s!!VqnKJgT(p@tICXbaziq7R5Ni_J=SxSm*0`cyqaX`Nqzfc^JM_qIte;` z`7M?=s#&9EG)p2o*@H6r*^ zjGiuGPsfiCFS@f46ZVQ>?#yrE0P%o{&7Amk;Nv$SM*`mh$@ajK<#!8J(CrBx>b5m3 zV^X+yxFMT8DWYp3Af@~R)zRO8Y&~6q-iV!m9O|r-kX7x{JAl|`o!EYQ6dVj5TIs!p z-JBFC9&O0#yx6_hH|%+759(>1;_aN4ptu`ZHRD=aVr4h?*Qqw72O@nEd8Uz-Bj06f zUz{RFx*@*2PF&uAeKL7iywB^;%j~MB9Wt8=vGHd}m>gj$#C{owm22j;4Or(X{o_}@ zRx`;#6p}Uxd!oNVF$3{XyT7ApyX(OKun|_DqyS>$2JGmR7GipR_Grr6^%IbNS4&3! zIl>yP@nm=A)ni{wjrQ)af&{7!p-1cKlGmpV7B9P^ePWqB%_?TOvQyI%#i}K&{`63< z2g^WA(()ft52KlruP=LdQ6$#yiukOlBds@jJ=n!^V8dl=4Gr=mai!M%9>}z zdmperuZ-<_x`e%wW%JSMVvT!5%0K)NuDC#JGy^>pwKBM{t67$~?@)@xTF%diDZ{eM z^<;J^2DB7v6X}||@t&sL)p}TTh|P6b>z6uvmn?mr3tq!~JQjGUCsx=n3-Vz0vt9|dKA{HE}x?{P;kOX%gfz#d0EyBYdDtiDe?gZ@3`rjh5Iisf`7G=2e32qeO{lr z-tAR5+j|fzLNmYR%t~g4nVpgIEv7?iFs5TPEs`&)e=&7tY-7Mma;0SN3E*IWN%Dw{ zIE}U3;9ZyJ3C=7%JI%H09l|SDJF&IdV_d(xt*6ei`Z+HRrU6Ip5upy!bEPB65vUuP z8!th_w4ou{J~D$g;jr<7oHweG!v2l4uo_8lV&`*WCtUcIc*#pZQ7zZsX%?e?P4D)- zWq7A(1;}FC=mm}pSM(l-tdNd$bI4-gB>>5#7uuAQu|%hiK2p+}yeP6MFNY-vR1;vQ z{u_G(F9CKKuuZ$>!hbA%5}QY?k7Wm`W_I-D{^s8vkfaxRoZtqDB^#X=vsSZO`oB_z zfw1Vlsqj6X9k6c^mjNn2Vqm$mEFS0y*p$j1FWK#}c_LJ|!oYUUiWCg&+N|(kD&_Hq zoEFsnM}8qbC)Ka{vqIg)!e>u!%z-XQ#k&T9fP;=2%08YQ=b^%1?1{x6$3$ejW?=Vb zj~Ps(q+Dnr)k^kdWq9o2D;9TyjFnn(R~wb)AzLc$ItZ_%_u{Turbb;eQ;Nq+w&H0S znDc>(RX!mdS)`(v{;Aj4r?2#H(d8*8%Ny&Is%d+UqRb&d8!E%ZEB%P!>C=X^u!v@@ z=L}Ea`40QxvPkk8K4-P}04s08I+8q_n80FT|I7ZN7{*DpF)N?rYiwf*N;0sUb9%XM zyhSRiP}j0fb5k2Etk!lZhUevTn*{t#+%;3-a!zPDshk!Yu^0vcab|905Qn&&orI0U z792etjn$fR<@5~oAoH0Q=+0B+>YAH6ueVsYdBel4!Y13og7vgeug6pEa!%jcTYG z!-=ai*yQ=a;y(svpC1+)2B@mKac9-vuu(wZlVQ_1Gf?d;rd(xb=f``z0@dite?H8} z#(+HLvtX9cfxWgMx=~|9l*rP&Zcr7C6LZ`;&Y#UzE%fuAc%Spd!1))wxMguD5IM21sf(VKIF~(H=$p$E z@M&NFKy@jXisz#JLwPQ|*Lynb<2l5{%GrwAX}yO3K+pQQg+I6#ar=g#?5pxfM)*H; zVNj^}lLP>R5zjK%$8%8}k)(521zgP>rze*3#5N8?wJ+{CXAb+KpsA3}ZWN3a|1_}f zg|hGp`?jzZ{<;>m6lSx;B46<@0~=Tr+^|2k+iR>)Vns!9Y0w*nYHqA4Sa(BVSRM6n z(aZn~i(5c`oL( zrJ2F-Dc7%oC=bz%oCV;)da=LujhFNrybuV0Pzp05X&o_g1iPN&%~r{sg&FLU+%6XUIxV-$y)~!v^128 zr9sI3L`H70l!@1acK(Wphuh|b{51=&Yl`^mY`l6brq?BTgpC7ZOL$6m-fk0ec}lOa zTiQIHqJ@l9kb5xVfFy$(hALO>4Eiy{OcE6OK_Xosiepy`w1rT)K=GgOR}$#adj3iR zZTpbFl0eCu=(Sp)&o}dwL!eu0c?trB5U&LAs(R&lG2C=9PT5(k-P5*;lO;vm!Gske z5Z^x-eeWpfUh0B+2zLkv;|}&HRpDIP&1ciu$IERv-&Zz-z z&Vg_J!i6MB8axZ^PQa4rS54ZTL+`1D{$Uy$xjam~X<&uRhgvEL?r6bb1f-0;r@FsKmq^1t(GQ=?3PsVpzjx0nbcjQ&+^KT|qC%X>Zz@EVMVCrS}-Ti|vg^=sgr( zH4hz>?=Gav&QGQMeWO8CGxq_h$_->_oc=qQgWOmV*5_Za<56D(eE?fE98!z~O&Tb+ ze8;OzJC@GRVQBj_^LHws;Z{lP_8>d>wdOd;xcIdw{N4WAC9&ifTlRYUzLOzFwo#q> z8!SgQjz^M)i!PGPPZ?0}@Yc-Je5w*ZYzj#{=NvV#l1I45ui8+xj9jw>5Y7Zy&K|zUSe~@imwHc`i|XTkqr%v zkbW|gSK0C+^LxY3cRI3gPKg2oN$O*4wn0V}f?0E1fpG1(uEJ@YN z9cLtg14pHHRNmN$3OBmtC+|p zzv-JR!psoLvF2Zg@{+K4$bVE9FF9_~5h^n9c@<)8`42ViYpLL&Fv~UWm{eTPZWRvL zWMdXc@;wsj*@zXI3QK_ma5Nu*yJ82CXSg*SjagS={))9IcG{<1i#ujbSW=RULL6%W z!QCKWkoO(1>Zenl&pAjmo`X61pPUo_$-!9kEEY6#d;%gq&m5szm8`iO6y!B>>#e}!1xtU-!&M_YYc{_ z_$A=i1Ha+;*`N7M$NO>oe#Xy$`uO432ETOt#^RTaUrlrulT8;|%LhyHBT4=aozjZK zXnZod6S7FAJ+}f)>|4dh^~y-n(4K?uv4_eMVJXXB*;ahxI@`0dWp2ng-U>yRp`qd> zRJ*EV2w(5HY|=*+olX2>?6c6MLY%Ubjo&|)O-h#vI(~&rLw0+$MH%(;i|Va6kS(9v zO-+d7w6l>dAIW{rt30#zJ${X#kpag;%i#knTC6h|y5TnxzghS#$M5hnzm0gW)r}+0 zkp$`Lj(}G5Kq{8j9h;I|h@C=fInJGMW+NLg{>Y@+duDaCILA5ea+SLyp8fJwU6^1x zdjlUDvy{?qx5lubcYRpW+b&{cr5pw{)kcR=IU^d*_TPcVVKIc!L$!5`z;fq zhKMPbS(kU4iR->%)835~r)08K@A|vc#pJyzTwTY$eAnA`*N5|_1!Uz*x(x8 zq|ognS)Vnb;>wZi4G%dkE5Ll5H-p41`cKz z=a8ZNFJUm_#6XChr3=jXelxN8Ue@w`|K=BPpxI%`Nh!3#xCWK}4nx5i7xep_x;V*I zms_{-#xOSL{b;|K3nY` z$PbN*y@s*6>zlZq=DpY$IgGVlZxQEy%Z9FRCSKUh7O#&GbNaJS*L(P_fPtjT4{hjL z4q2w?vg8hBKdg@qK6akV(uM2@vP4KE3!UiZ0wtbjzkLuI5d#DH?gtm{sleaecCfFX)@^q9n;7{YYAtVF# zYn7JmgvN$8r!ra9_*lr`NLESh(+w2iIImtg8C@*}KbX>8$9*Kg0T6&u67cb*{$FA(3MCpdpe zzN^N5!A@=T5~mMjw>CBv8>TS#O^M>Cds+IXVZnX(qGx-mHaw_Lr{D>{i<29jDA{^1 zJGLpVQA9?Kag%?M1~9kHUasxP7YCv)McM!|li}IGAA_G5nd0SMq>K)UoMO5E|3ityc}}c3?dL*8yP3 zs!je;uH>ESZM_`_qCmo;9lneUHHh!?$7wcLagvn4w^wtFDYB-I91$%w^Sc!^r^B*d zK53(BsAG?l67-YrSO^(_wgitHcrWwaex$tILc~ z?*!V=qgOxOve8SjErXtMY>{-x&H^u6aE;LQzBY;@p|7PyRq(#1q71sqwIu1CErSkA zaa~vUw_TqCyWn6ZotPSaK$?7@9oTmFnmr3n7Dwi9olru21Yg#9pNm+2f^FY2%=Mq0)cnQ)eVF%F|A0J)HekUY zV9xBY;6vPkm$2Sj1I2$nVJ~ge^YnAwXpEb(f)j#7H3-f^sk^GQ<4@=Y9a3hV=O4BS)G zJvIFB3tQQ+k_f*NsYaNIv`cUe@N%UjcRbRQtu5&$E<4K9lBTg!k0P57F&>y0a1Z-X zRq2=#h2L*d+a~Oy-pBV(oAzX#KaKD`)SiS{Oxe65s{xjf6>KVGEdSFmVLJQZ(;)xZ zkeE!wW`DVz&FL>3xEo=a@ohS7vc$7%p9Tnx+0#!0JC{VjgC42t4qBD8h{5*03uz&{ zZ<^eg&L&`v`Ni8L8V(qmc9p>kmmP&_6`T%9d1d0@9rs?Cndov)m1tLw~4cR?v5gWg&BMu@&A3}_I7O`nG)P@63 z*5bO7oO?=j;b*EUR0E!TNmTU$x$}EC4MY{F#FMIG!*MAzQTvaexPpVTpF90fGZ&?k z0=x(2|H@5IHl9tbY0E5F{O{-hICy%`hT}xnAUAt$yQg@e2iv&4nHYAMo!j2Ybuv5| zOlxcp=DQ=L^<4mw9Zl5C+YV9FksJ<3^N`9wh#FJheaIpa8*XMj2bt67om>;2a9;7UpnHH1IDZWG#zTMVTbO3wrB(=*&RF8|) zqr}2*S;PHpJciKVf#ZldE;XT1ma*T@9EtFod??kl>jaKd)2X>&^-eFAw?D*#)*a+- z-{CJkBh+Reu^sy@UOaiA3dh*X)rW`!V^0pTw6EE3``g1(NjaKXM}Af*e|^P<9|*`j zj9Q~B^*w1eKcZ77oHbd+mXPJL*N!X1+-aQS>2;!--l zkY?2mN(G0qQWI~@*aKCj6wgf2%v-;7D1Rw`f%5$zS$-l-R%|jPKr`YLhqEL<;l{WC zL)|0}>S&l>H=<#@sAa|=JFbk*59XmJ)X|qP|3l&8*$yn_P)jlS6E^Emh`9PI_V%G% zr>HK1VrnkIle*7idemJat9Uf`}<*8q?>xY|)mKCht5o_pc zhcQ=ju^w@T{-Lo_H~dAA_SG9y6F^`8f=xXVCi?GVuOAsK?(<`}kJ#MS(->B8z(umV zO)O(wk2VqWH?XNkgT=4kWy_ByiKz+f%+Zm#(R;~ivSYOaR&xEP9oKT~bR~dX%-^f= z%s=Su3dS|Zz=mQfV>iF{Bk{*>{w5d`EN`MsZiw?;qcKa`6U;?SKdt^MQM13f66EBg zQsNoY;^t^}xo8Q|k2=miIp!<+Zeiue{QCz~(~H*W)v?JvU8?fIseX0I3wnuv3h&Xz z%a|b8GlC^M7!3rY%JG;W0sg1!4#|Gp%{m;9#GT3U$J@6|{up&g@VAsU!p$R7>Ve7? z^ql~^d15v6(A~Tqz4;8XLXBo;j>orbi@}NJtQXZQ?;!wmpfXCws$Y$Dzw|k*TdYla zeAroxOgh$V9qaHvoY!a99Fo0O%IxJa;yq9HWqD-%`#(6chnKQ@<-THw%-l|-iQ^Bk zK_~nJW>=S1>>^gCqFop`QseF;+8po&d+o#oaYtkJ_(U&p=0VouWVpCKp3OYjv~l1@ zGRm${56<)PodnIiXeTQWi0WfQ6rW56+%3ZTTVhM5y z2_-yl9`fcrpSLIv43PcOv)K+tQ#sKxZGej-W6Cob{uS!|RhnkCce_{lmTn}SP)yF@ z^83vFoVU*oY-3_h6?Y82ElIL_=HY0(lV)!63A3JSfg=Vloog3%U>iwTp(cV9pW0M@ zN0SS?`SpuBt)TbWiF4tmJvgkZE5l}Qy>$l@zG;#B#mZ`&p}?UF9av>1p~;`bVO)Q1 z%d-+WxGnh$71-U@XMhDZU~#qKnH^cX+hn+3+Wh2)R^8_J;wjpP9G${nv+z2w8-K-? zWUku@@)0!~pq;w&)Do^ww_qzzk-r3%*omiBA{BQmT`s0cD2|xUMbHSx&9RqH3u?Rt z+q(d0qx?GIHv-)rAfVptK=8)8ajf%sZ|B>X zIVi!+g~sN&3e7T&Cy>Eai)qZ!R&3GvI8koJ_MQ*$nu3yo(oGOb$T6w|$Un%&fvwnY z=Yzyft(g0_=C&IVwt{cmL=PN%MZM$SYchTe8VMy7(dI`xqjfIn#3d74 zXnvcGP!Nw$Q%K~L`U<8t^lbDNyYj7Xhk&S>$y@~Jbp<^dn3M1cgp`DJTywM`6;2c% zwdYk0CAnockCrU%!UwKjVBQRMjf-X1FT9xRO0#GvzYP{Ow_D)U7abn7>Uy*(Rm7*m|-Q7lL*w$kQaBk`Y3%3J{?R2{+8$P7dh}rR(AYi z z7%Y3#W9_KEZokA(`QK=8B(B7;NteRH%8*CX2W;{YUqf~)C@GAPUgk(fC5C-+$y+R1 z%8p$M5XUcN>ZK-P+Zbm0EAN7|e#L6h6 zT(>hZ2e(Vh)zN`~+l%oB{M`8VIOTpH!l8%PKs)D{==M=LwffM?FGHAcx$)p=6w{_) zb6ON30F$)GJs%h6F^Q6PCxIZvurg@!iwHyrMiOk!K8}Wbv?e|W)IIBP+>1kMW`W8b zSNnFctjlf1^BdUa%Q3G1(C{G#jE`p5E_;gCK4kYV2jyN^QGK*0eJ!`2K+sJnS%S7S z75)j<`U#*|UKk)y;r5H*XevZ6)z8*Rb~z6@h<$SgZr_v97H{9g^CswdrzuZ&5$f1Q zu(OMpqhRa1gK-y?B$QC+&z`2P!g)gsMI+=ZQ1NOMC6r`PNm@b)O@ec*aCq~PjqV%x zm^HZK?RpSnAX>hE6l;FPF9es44f555mAubYq17u+qtzeB(`2Elt{UmfsdqgY(;? z^tpn>JEnpZHuh?W6HTp`T@4T$eaN<6{aRSYrv8vJo^~5BY@Jen#3+QPX0Cjn#KGyR zYj}w(OFL4RSn-uVO&C9dswoV%jxs5dSu<L7IMvF zsAgUVifD>R++0b*Syz5@Wd~>16Osc|GtcKB|GK!6#bF>@XZ`}&+Tevdnacj?LMPZb z=jhOPT2jTaNPvoL2-soO7z`mH?Kw$?_9m}qVb`M?oO%OUe9B`BVMDGD65nEM$Mqp% zr;_dNKh72EZpNfjo!5fB{?klx+Uu|8>Xe)lcg1vd8 zrMPG-J9=Y{IA8;t_;afJCkRian0Dgq6!@#Xo3k%}o}9ZQ52*PEXJeYe6*kNf|E0^7 z6!)c2LunRD)#EsAsWXxMVaECiF1mh+_^*Dm6t^re1SZ+!N6+0MPM7bgyO&{lBZtRn z*5XbSdTmS>R^Sj>$xZ)W#TYN4`B;AFt7s|oPdA3EFAUE^nnF}dp|Os%IxsD#V+GPm zKE31@hOtdhb744LU_-kH)kEin-EFZb9q9Qb)83t`1*Enl1?A#OOOj zp0BG5%h}Rf-u}hgs9%~2F@WI0l(%Wo+jvQ{=QP04G4}PXdE%{k9b2cl)cD|GjA<=f#dB+lX^mZsk=V8n^v(-RsQ0ut61=_er#6N@J4AeJWn^# z=2TXNz@qWdHV$`VC5Jn_5^!T8is8(t-AErivc;cEEu?O0wzN5T<{wy%kJKe z4eGE#(+aQ~=3**(N>#&_=0sEBAM`4lHzMGD$Kp$;^8jOuH5KjU1^p0!1M!#g+59_w z#ii@n**mX@_da0hzeUF1oWf5f#bL~t3}Y!MMQ5ZT>V5pR$tFzjocEJSbd@k7B;Zx` z3}YYt7TE3JKKM0H)2^;A;}eZvQ=%?+R8?YJS+M|ePPOE++v}v(0Sq%YjL7X0x_N@& z#(m6mH$Yqw%3|*N_urBu!#kP%D?!BgB&)3+dd*Qw@G5$RY&`HF52$?Jf_~0 z#OQUb)$g(53v>KgX)AK7A`_t>w0MCNvz$eW6egPg!1?P#j(VJWH@Z}O@nu=DTrS}-tAqLY6BlG>G%>BNX z_~i@RBky|&A~pYm7J*TFsXz2o=_+Vn==ouq-HHnTjbXs-v6n4>(6;W?Jy3gZKX&rL z`{Mj;_R_;?b#L!RRy$vI^I>%COY_0fo*rx~=4>_drDB=~A?|ezFE|WiA0E7CHUzSF zKZ-l7#jMLCTjOcfFgXO{iW+I=A;oONqZILk3;X3!PtmfHwSPQS{9Iw1A4iL`US$^^ zPZXQ4VAdy7eLEq}g1aACR6l2!yV@L=1Yi<@ZrhJOX(xyc7BTUk*1n%l0vLuC42HNM zN&9<<*=K6zFN;|3f0~LNjBL(76U8}iup9qO7Tw-py`M&jrx&pWPsawmx)cO40il_u zVFh*Gi3l#F$cM?ROU{L zv=$GJQ<@1vNQ3LyK;mQANI@AW2(25H74V=@;SI1t9n$J5?+U^&v8s;pL=Zj@Pb^X1 z7lpyzqXzS7a1u;3sUw6EX12D;=5t2H!%4^p-<(UxOZn+<)&E73W3^>dh@P26PngJ9 zrPxV`3CNj&Bp2Zi41RouQk2O-JHB94zHt(|dUjbx$4bW1he#S?`UtVgoRt`7p;^!d z1Txa)t{LcKyYZ^_Ylv8Nh7YaxHS_WX$}DH0NwamMX+`cv*RN9(cJqn1iR>9i>lOk> z01j_)X~C|;&VpGG^Oq`DjKVo_i>!QBN3e-^gtzJnUY<=bP^#MezOeF0R_H`L4ntLHuZ_l2TvjgMZJj<`}Qw0cesZJ0x9BMcbIK z0~((VgT{ZuYD;|;c%c6=Ag!idDV93uuw^**m}W1bbsEhavM7reHc&Df2-#xr%e2=V zn(O>B>E)bMJiLVNAbmcaRGVI+&dbEOaAXux#i=Ej7V&-TPib(WxW!TR3m5^E_W6@q z>F6@+m$dq`yIm!*CK(B2cuA^!CnaH~#k6Q54I1$B_@_}(a;#068D!ExZIFTR*j0<%*BLDBT(gEnCqMBR-0B!Wmx2 z$7XTN!I=A)SJ4}cvKz{%q}xAOfvWACHRYp*LhE6%&y+WlJ#6^U3q~jcx#@(#>;*(N z)-DuY8>-nE&C>`7dYsBjhYlZd4J?iwGXzu11itvuxx^AuT zs&*7L88*sC#%A%&2#ca@Sq(7V0XljfAF3^G1SqK;3cR%iUBFmLbApqZ`3ZeS>KwO*i1CE*PoA*yn-rMI#~Hd-QrTi~$+yJs47re~NxdneHis zwD2TBu<(SBx_KX6qJaA-L$)91sB!=ecscjwS0F?Bfd>q9qq$;z68vF^PB{ytVRHd^PE17yRD=@mBXIG zf|w;UxZA2b9u}OL;|r&??GG&c3Jy4rw+z*jg3Rbnhcl}@%_6WjEbIX`9jLB+{FK~~tb>0BFsOdJ4*i@PY5^fqxEo5vo0~9_^dqOF4L`ogR_ehi|HJLjg*Ok6`U6L$A8iDWl5E!}IPt1(}MrBSB7QbzfMY ztUH}ymf8Gn)_3T~wuCrS;Tff+uizQ?1@|+UN67BF50VY!Z1Ab9GEDFt&gNgfW|gnd zUUZwFoc9$X{NwI$xw&s?CVQoD-w{rYQ29Xd@e=|^Ke}C8Gi;5e=fN*rUj=z7Xjpm; zrjaTil3l}&!guULK2A;{#C=p2;T^DUG^f1{-%*$5$_~$Q7a81hOn*RnNnNRw`3XVd z?0(7~KcPt+DV(%b<23H-PNOB{sBs$c6G&mY(W@){A!@`eSnY>Jr5ffJcf2| zJPrEtNz)tqikDFw5|f6;5Nu2}z-QwXTc8luv?DU;TLct%+Xp22`W_OvyJ9y-qRfp37*MkL%*H`^%HM+MEc2TVf zb+GGXO1-FlW>fq_gaD7_z`+c74Cgon$lu*kY#~AmAx)WwN0T&ok|(_PRI@a3jD%W@ z0k@RXgdx2ZDzs=r+q$!!Lr~rz7N3viEGFQSYxw-PaS<~4Rxo`n@6lalT&R#BG*Q-u z3L&w<9HEVl5MZ?uH6JEf%CPM`|2gE!*Gag&nH)J99YM(q6M|cm5t{@KY20#y#*a;CLM)sy z3ZW&dSr6veV=|R@EJ8Cb`t|3^oAC0WOyviZhY?M9*JtqWVHK~LFOTHhBjx4mIQPhF zshDjHt7TKl!QR%v@Nr`v>%}(j=Sof!Ayt^6>}?{1$9r4>X+4aI9R9TLb5l<7(FnyHE;Q*l8Zj6Q6BMbmmcon);{FOeXtlR>1WBiVTg+PO zte2|QrOQX`niVcI7R28MD9f7)ZG2EkgzIa+O3FpSnx>a>wW$!&<1aYg=Y@QMRF)<` z!q<5C(HsaFPiAU2yCOmh?F{4EX-2E2Yp562b`s7WIgeDjM+l*j7neEYT+ZQ4g`vC_ z4mpokm%cP?*P9VSdqLdVPdO7Q_%!)$DG_flRIePJ`y~dM^t77?+o|xkA&RM)V2CjlRHjD>fueFnSso?$ zi&k0L8YP5?F|vX!9)B@dR_;W>PX{V)&4oze3#DbW5TE$TtEBQW+OhYdXwyLKBNyzY z-T)pLi|D&lz1*R*w*UyJRJ+xMAdQQ@SJp)fjm0*vDy7kafBT5%$_w_cBN8>(zW*x4 z6co$8mIzFFf3GxbE<^}BmA1`=_!l-8KPwNSK}pK|=0Z!c>v^T5xe#XVeVGLNP2c|Q z2M_z~_OIBkzfvbg$Z_pVPN7`w-dQP(5#roG2RjMDjAw&%>SwzwvaA*(v z>FS3ai8a^QCn(f4=1v;pd8{U{9@k>UMGX3R>!6H+hX{c=pn5M-rp5~1V)7zoaV!QX zw?)dQl-7!r@>n4#=*J>XnJ8SKMq9`a9R`Gfm?Xb5TXAhE^c7FHRz|cGn&wVFgIwKj zWa@rnDeVvPuW_L>dQ+a=d<7&m^U%J$GtPI&5zP6NX#)`_<>J?^$-wlnC&&Qv9pq6l zs+un2!wR57_7Op)xB;U$w-OQ~jT{K>=8HahH=}X1#@#$9Bv_QL>9cEMDKg~`ASwxfoebjM zSNSep7$;sCr&wE~TUDG>@bxU8fNBajl@P)y(DKi5{{qvS)!b(v$a3G9E)A+viMp_zFiV=s{EzL&{-|7Q-DotOK$cODdEF2?%AGI5>`~ zBCnu=P+_8*6ELYAUZI=;L<@b4K@VXAKA@lj`CcF;Ob4&1ArN5TqwS)dzhF3v{r3JzOJ@+(Thw85v z@tUc>Uc>8D{Z+*)rsm3__QGhFf5^V5J}nZ3#=*NFJPquLkk?^7SNCK>c6;}0I1AZL z8J8#&iS5&rUlWDY0Z&fQEQppjhU}(N!CV}10Oh$rwVMah1OMv|n;jqRu)7UHQhn!J zT9dea0W`in#dVTDn|KvF=_)E-HCtKSK}g7rKa5=616||71PlRM-2J3>GX+zj0#w>4 zTxvOuH~0jAs2{Y$`pup!I8MarWtzF3MA^pjIgU_oH5NY6+l{EC*t@#a=I!W8!rkpm ztI^i5&!1t%wAD)-@J%K`O#+vM?fSZU5BGy^`5>(>J&$SbX3Bu!lq(B53QfHKYD>Zy zIO-c*REo6zk*e(ND0ENn_Fsv<2lv`~;j?$xIxkD*5<#HsOwhh}yqablJ`_G1E0znI zd048_r<2gU&sG$ou2)|Hinfm{LNjObDj8oxSss*~(-iG-0uY+HXDw-*fp(h?2)de~ z?Nk43#^>w9k1J<73EsKu0bWUl4AdVus=!fcPnmz7HWaA(F%458&gv6f1>wZ=ck>gx zb7y@vglflAY~h1D<4uzD-$~lW>>#9gzL8Mn6jv| zkUqRObyng#L`d%u5>9UH=_rC)2C(ie0&970hkh&^AYL>N;dSDd4Z0w0?9ACcyc5dXwT{6gXxU z49mD=Q#y7L;(M*{!f9oj@O_okJX9N9FcoeH5`f-(#CDIQ7CN$t!(NlSf?iFY^ySZf!L1W(F!qK=X1_3eT5Sb#ym`sIOHA7gpX+W#Y!}~LhGrlFEyHy=j z5>te>eLg!#N$OT~t|+^qlsL#G8&gzDal2aufa?7Qa)b$>2|fI+W}I@E`M}OR=QMVw zG;^!Y%E1(&m%C3WGV>2gSoPpQxY|YuOBKQ!gb{NdR(Oz=_iQUy);tS>DRTNsG}CU(svPE(ZcyV+Dp6g9 z*1{NNR9C?-wIrJe&FMsha(rxZ`Z{350Yg6$j&IwYc&b&siaA^`bn2T$&^8WjDmq2V zkln`ZSN3%kUJtFDi6kb`7*SJI>%QvyCLwau_EmE%3I-;@Qm zUuz(unT-jKrhqh~kWISAmnqWG48LFqt!{>%&lgotk)s(BpD)@$MW_h1P{i{^t56ho z8;je?HX%)1*wjWd(A`X*;n|#_Rc&$sv_(ARZVvYSx5+55&=VGmBkjqE=)iWRMdzO5K#jJ z4T2CtP*C2d*;EjM5%Il@qhmxwM8%AvK{mr8i=sx1ii!}G(SQuogE*q&$ooBYtLdA} z^UwR&^UuSFlixX~PA#{#TXj#}+r!#@E_0ok)yBLkA#xKfYhp@p?;8>|@b?0z;wIfC z``2#d$n3#QPUh{T#U~i+@(y-Q3UkN!9&!bN*2LkdbQzUjoXlgs^QEQI4$A`mH=N8| zo!v6sOyR}s%y<>^!P}S*2z+IeGdGu=!m*(9Os;P2s|-3j1fLB$U*+mfNsnhRTPl@d zZg@SqJI!v^b9~7IofS9h8=G#AF*ULAPATnm^Ld7JnRDi5JuUGx6T}br3Ygiy?dnV& zt^20@)W#&F{BLDbEZ)TK>bx;pkM=z_z)9pc*jfw~ziLVIUXvxQ5-B>L+U({`AHziW zR2S#9F%0;M*3P$M7bI@8DM&aF$ziL_LH3=J^fpy7MDKXB&c!`k%b z&Ze=tj~?ut8q0(Aj^$3nTl6X4jh&sJZqfaG9lAKdak^)lrOdx0O9oEdFVOyhcLloT zzb$vpfw%2)7L2156E-^UjME+Z^xG&!4BWjNqop%>{e8iFN{i<39_*JcSmH`swi?Ma zLP_AM%}(a6`Uc;(ot;^?>UMc0uh3N%Vu@pH0sPg=hK!XvDc?v$GXu?aON+*mXseyS zpTBzPkU6lA?GkpO&8-2sA=%viy2VH~w|y1P&$sG@p_g9fmauPO8Tq)AOJKqQlbocy z_j|+U!CrgPum!Nm#|&Et`_9*fm8{2oD{Lg|gU5|zv;O@HBiXETOa=>AljR8K+<09P zn*EZgo!-=~EsM-%ToZ*zEJRECY1NX(BNB^%yb;Sd^c)zOnha#VX4Q& zOzBb=UQ1b3X(YGAiT73*sWeuqj;-S+=gvFyEuqOT)-TOXqA5viMrqu_n6`BbGlQnF zg*p43VWlu{eQ8)J%=Dv%mBKWr5q8OZH^14mHj*vO3!fROR5dht{ql}8EKkpCH8lot z!Jc5U{5pZTE7jKaGSko1>z%Licr@^jXPv+V-Pw12J0~~+pZSL`IAbPI?X53&7EI7- z>5sfX3QLzH-FF$U;&W|k!QMiq@c};;bt27v>zu?%Ttc4=r_UrVDcr!heUi>; za+--(X5)K}lc)K^&pU5V(rrV_FSZXpPVmkW?>>o#FIhgGlf!)5@)JeQOLGh!)OpyTthu}tL}lNOJnea-Lj&}OfWWyb$vBYW;EM2r8U?^`qx zubm8QD~pZdJ|U^LzO{W!db1~xmb@GgNvokJt!hc@QAz6`QkR^y!SL#StAj|hFRQ)z zCn=HH=0533;T=LL$j?MRvB`}$i(v98HdZ0yukL!?q$Oo?DJ6j&?PVuno^~?9r`}?s zUk+TqEMb3bGWpkXSeeEJSQqAkV#={YfJ zzJn5B8M+Ho@{|5i`}!-US5#v`Uzsg=i!;@9p4zKUOW?T+uQs^fBVG2{;J3^Wq=_=z z?~w9%JdRs12YG|B#Tk@fIqBIaKuL?=G?$s!S4v2$g`|l27%VdtaQ?ueU-W(Pas*Bq zJ^P1%JTQe{wAyXy!6q%KG*vuE-XXx@G{#&>A5yG6^Rg5&aFNFIE`$5)BEFKWXb9kE9KkkVBe2C}wDh4F({F72=W;;vo(w*Ax zZY2@e?!E?dzWKb1MBKlnkzZAsSC;s)`1)4Pr+4W~LYF)x)h8D~wf5zmq&Pg2(ieE8 ztuhw*NepO$9|e{M^2x=*(9i%E#7!Iw|IA2E|Q@X?{;*{CbZ6TX-MZCOWB0n)^>lx1Bv-x~#jW_3 zH0LKQb$_SJ-TL*$z5a|z(u14$yD!k*Id!+r%xZj*q@H*_okDyOXZDPpYd>kgzO#}M zSkn9`0fc`j;hPBG7pQvL88TH5jlUPw;PW4y58U>&^Vn2;{#~7&Q}xAN5@^-h$C zN6sywaTg72i^ey*SM5oXtt}y>_8?Q~($;ZK{4{;bWlN~UkbLMSOuFT$887;zIgd}%`OPQ0fyIqSF5BDlka@OoCe=x}N4Ia&#l~w?GFEd- z2>#Fv8~I^9YUI^a=Z1Uq%xmNk%!sW$)*S2hN7>X4*gd;=#G=WmT3Uy<^6=D!CIm=E71 z4c)yYaBQ_RypSdN+SSg&Lfx*zLxk5(2$-g4-q3~!t{L*D6JI@Yx!{?kCW#jlc7|^v@vDDL0_jF*l9&nZ4V-eE{xfy^l&>E9{rv2? zBPIosS2Ef16yY&v=1l!W(-scNQeC&&A7Yw6Fy2Wk(j8*k6R>+x`{vH@B7H-vjjR|( zEPJ|^S{zaG#9_Zk@0lU-RI>A0k^X~kRukvPB0WkcIakfn8HtVA<;m7Do8MPTc4p1e zJ<=Y|5!Jx?fP6uwfvikuk?a&_$1HtG+O2MIZoOc?32xye7wh!IOL2|M)Y7wZUm)G- zQ>>@@-df?TE!O>eE~MQDFWRq%E)CoV`vV`x2uz3Jr^M10e6NziN&IA2lRQ-vkCh** z8&^3^X6wsR<(Ny-mhYO)n!YdKU*V>|FYtSbGj_HPcCRX7BHtgoFYq=DgGen6Y=;~C z&)pftO&9IY*cVubNRtt;w#0dJwr(?OHJYR+D$NvrzWs*A%+Bq%nZG;C->~_6#{4~N z{{GYaJ!k&DYyP_SrwuUUY? z@Gr&689%zR-{MKDueP|u;u?!X|K{3{w^(d(p~V#zU$(f-#gKZ}DvnzG&f+VS9uCQpT zk3WC5_F1;(23dU5>OZw;?8#r{husoQw-`xiv~80977y9*qZUtD{M}-zwVQ4+l8)bo zpRpOQv~n9z^1twJuMHS#af`)`7W-NpVXx>RE8lN%oW&fAk%Z@*vQ224G}+?sr}Wjv zrq&vKYO&JdMvJ8u^DIUZt}4{?P141BzVq0ddSqO-@)=Eysz`r8KkN%FaxMB=%(0kf zvC!fYi)9ugIdpo=#LXi#n{!h zUTfbH|+CS>0#Noz*d;ct*yAnX{)9P0pA$<=)8|7|ke}+udpRC*7%&HOd&6 zQ8=NvKuqf;mtRDjqV7(gC-torAO1<-m_ACH_?MYCjTe+#8=e-GvqP(#_3m(sge9PF z29*+4S}%+{g32cHTEf<@cNV^>?`pshlEKt0Ve>+_DJwzIG)Hgfd zu(EsgMH%}Z$d%l#>_2|^l;VQf6P;}pvTHyklngb6hF_UGQiSj%t-2Rao>ScJp6RGn zc$QWvU_{q07*xJutrlF+&Ee?=p8=tW-KXSFD4x)+m&BuFChhLQ;G{e6Qffc4>?OKi zIf@n4m7&36R8?0wPIQOs=&m+)HFb5yuC|UY$JmAI>WtmFI=WnArxt~5=}c*IjbZX4 zt+EGD!Xmi}x1mMhR>h49eiYB!S@*G~N zvY^vui|+AQ(C_4I(YYzvTtr*+K{`tgl)ck=bBn$-RA}|(qNk&YtmFJDE-O~W^@z!9 z8}O+>aYI!@*e}q_VlTSf7$YZfm78rE67H3~akLY!oa3=-L4JyAxv*)tNmWv1Vqt=F z$5!1uJzPYMQaF?F*#s44WuL_bw%X;a*s9Yrvv#?K7hNF~kv(?rZpDqZc$ae$%q+F` z>FLxu6p_Phkwk~xrQ3Ao?Z?GVE{1|V$&T;?C>+t{H#Tx2`_$OoZvGPPmFvWzk<^|> zst_B|AK&F{*rq%5&X>57$DYRO9wyV(&>WjyeK|~CqRZdy#BA3?QjS==aE4M-XuTu5 zopIZBhwjbxxaCoqe8CZU5T zI7Jn7WL|_?Nf_;0U(O&*bpCgo*LLt^e45y0#;DBWewDZ@4<|-SW!aUy%ipXJX zMTcFFow`Fv2Hgl5bf(-*RaIX;EY*+3*l({TH^s=;p-UBA_ZZbZtBHy`8>a%*@k;bJ zLtQ0I(V@^Q!t3hYG%utRv)ajvBCQ(;ca0M4(<)|H* zdRjX;P^oc{x3xuHAv%d`TU+G!q2FwJ^<^8Ur5nk%b~JJ4&%0>tu?K8|)d?#22c>R@ zriqTLt1p+crV!oO1J19zbmldiB5`vYsgx^uE&}bbaqG)fLufI=O&lo;b8=MY+__t4 z_RiqRqI+>Ms`F5#Dxjd~C`)}=5hl8fcby%(nGzVfpYGN{=gm)ai-2^89B1$z-L${3 z*{n6vI>o9MI^NeP+^{B4=+A4A;|pF0>)22=&uZdpuH$^Mg$?s!a+o@coE3X?x}>{d z55rk(^CX?t4OF~NRsNvZu!NFc_ho|mGQX8-b+(ymR^5VO+mb1xwesg)q$+P!>St)$ zdt8U~&g0FEJmof)GNL2CgnMOg95WU6@P&e=Vv4zQ=5b?!j@ZavIjuSK<|y~VI+3vU zYvrfn)0%=4-2#i=+7-$Lh-}hko=wJP7!*9{7Az|B%snlmk!rFlF&tmhsH$OQqz+9@ zt$asc+Nv~PsbkP$8`HScgl(A2kNpB|&)i|V!m)v0u2pK9DA&b?Ib$u)N?^>64m zNiiu#B^__1;)-LL>KYn-!>Nq$_uZn3T~zilWz~zXRG+#D_Ks1#NvtD@$pRoaO{qyx zq+X-4vtzIBAF5)B+HMAm2Ag3W+1wLjRZgK&uR@V&%`2OW8x>b*>Y4!)EK=$!DB%Mx zKt}&@9{$wVcZpG5&id6w`Eel?V8r}|2wy^-B$An@{M`8B{!{annZo)#$87 z(nF&?ud!b>CapkzjLKnk{|w}|0O_H>pu5F(_X`SS&)P1hZ4#d^vkB$RRcZq?{zJEK zc;&Q?F)B4bUZv9AQ%5zUAkGK-^|jp=@mw6LwowW_a#|a@9#N8rk~At_qFi-Ax4Bfh zbGeS(bcU$#gWU8UVoIuVvyIBmiUZsS#6Bm<+GqW&E0^%`KRW>85Kz91St1r2khoL`0 z$sf7pF89c3?E)%AC-?(34GMiEYKCw|U)Uhemt*t45I0Vh6E7?jDZ5t= z_+->@Lw=5q2t|y%a@z2QYB>FL6rL_7tq@>RM8mL`of-~0_q?Su*?O_Lw<2Xj&&H(rG#U@x@fLWYgZgjHA1 zteSt%;}Wey!cHIinN7}bP%C$5;4a&DZ_FD2}po8Kj7Qc?s; zglK^BB;p)>->x8~&D+X;(cEJisj635sg|>>ua?@P4|!zM1g}N=g{cu!z^1C4I47XB z60dix>dk$t$yq5RwL)!6*gv4nCS6+LY%J3&fpr6zHN6R`I5Fgcs6pFoDywd0X)PP5 z*fsvJuO`}w+a|LApk`vTl2y%XO0|5QS?3c9=DE<8uakc}0!B)J+$X33F^uZmh+f&7 zn8|n{-7&|LvFQt3b$${Zv$4wAgcAiSH+ir__sD6XRKIGp(w$|bM+c=T@y@q>6 z-Wy8&5enPLUfC>6zWMJKCR!DcPFkCWDoLj(Gv>^~&n;x3 zsJ{FkaY(0pI}bY`Z#p9D9Snlc+zb0RVNOiY zt)(U^OhRdu3~`&7SJsclsYlN?QH!gS>#X?9+_5J~CGX_->UObf2-(ON6v%1!#;SXB zucz&XD?0@TrEetDKizg4rZRT3JcQI?+idw)8Tr0F?9?N=vqP20TcM!nFt0DCOClE{ zVGvE&+3>Nu57_%LJ5z4{-+inz1>>r@sh54Ml&@N;Q#N6*Y?j>%-OkpDG~KI?RW;cE z3bp#2!n8?HZK&O};|*1_;zSl22`Y}6Q|?=0<9x!dRP2afUnZS~HX&z2weBH>I$F&O z#8OLT$+niNQ+;Jem3rwI_Yz4+I``R@K9N-o|83&+WpA9c&P)e7$!BhbmC?;pP9%@w z#=f$zdA9NmOVclO$jziW(I<+xprK#5S5RN}L{e#88mTl^$qC`On%JtCN^{E(y0v#9 zqufMa*|$nPaa^h8HchXbc4@3i)uE<7Rzx)os{)m*8cd&&?;5scj!7+U;)~5q@rD1& zM(R7Jbeo7*ma9$GiLROP3Yni1wvm$i?|&W=x6OsOB0%7nJraIc)!A)uP;#@f5A z$l-2b7VKvebZl{Ro$)h|^B{G^7CNh`7WpG6HKNOJrbSLZ$Av_6)rlH;xGF$7RFtG4 z241;NB*N5 z@?3PsBf9*hO5_UYw}`I#VIz0=jWU1bmYwjBiYk1p`~TCZPC{IOWPTwtWN(4oWpdD6 zCg<4O3$F>1MdHSprKn7rb*WP|n@JqjHtu0l3IU311eOR{`C8IhoGm_}d z{;<+`L$o?2@tgU1^f1OBn?Kqwy5&9|{vUO(w!R#V=ZyZ`&K8wyKGV9zs8;&M1`WgJ zK5Uk|h3S#}#x%%ltkr2~j-)FWQJvF32a?AlFH8Rqt$nP*$CFS`FQ2Divub#YluqJ zgH%kO+lw%BE4z5Gs_aKa_t$E%#FYlf%{KDfE3|sbrYGTExlSDAyO^b2-GtIE@w3C5 zqTE@|i94b*)8`M=iuX^|Ih$xycCI?2+r)X~=||iOc*NqmBYe`N@LH|jht_}XUSw2u zPLjSQ{8ZMpg-!D$o}_sB6yn^iRWIVD z-R<-~MhA|BwVbZi8fYqEYo14nQ9X^gv>#tfJhLWFV zw+7Wb!wuq3@Go#DWy4zumOqa@TSwWe%JN>e2(n0)b*U^VWz{Y_J=wv@-bOZ5X7O9Y z8nSdPfl%-+_L5*3R0Y*Q;b)1k2Ak(dkivC?20=GK6QDw90kjI*0PTQ2fQ~~yLdoB2 z)gI~(je&}wmC$R@``>rf>U)^;(8aY3b7%sz5PAmM4t);QLO<6!F<Pr6(KcCTcGODXVm$N{t zB|k)UbN-=K4Z3oVZZ*1C((B~WZ9z95yKJLNkZifdqg=f}{PZpU_f*&ap6YTt1%Lks zPIWQy^6&rSR9D4-T0YF-RGrkR&OcA;h0ey4Iy3Pv`l_PIlSfXOSTvz%ZpD2+=$ZaE z!#dU%k~0mmzCQ>xgRcTRrtt6rK6*re@rCZs&`9)xFGG3o-s1vtY+xs}1ij!F&{ObU z_rLdO07nEuYIHL)$4Ky2s0Lo}PACjt2p(!d9qi(lRY3PRfHoL80wDMoGe%)1T*OKeBf&eNJ@A4vplbN2FJIR=AK*O)Am;(*L9OuB z3%&vc;RSa<+3kX7~&ckADc4Keum$$ngNdU}p%uU|(n? zyr8)F$HRMF{oZ2$ay;Nus1zH)FQJ#=1;2x~!ACvz^n)&;=s5tdyI;-$`~saLg5afH z@ipz$1_!{mf)|t*)Vsn*UHQ6>n_t}juXX22u@O85&4U;GC$v0-pf2OS%s77vKI;2t z(T1)A{{H>w1$#iX@Y&$=&^h?%nE>kLB!$jJq#d+ z1?1KF?ET#Eg()0ByY8nN!8uSdyxSq zj1FuBZ-Q3A3r>R8!WV)EhPcgL6@rO)_5~xS00iHF4iiCe3v>!z@Bs81yx^};0$%>8 zM_OO`0s&qy1qu})WWY4PmZ}#X{PB894XMuDAA^1x0y_*={ z@Ks>?&9uGPgMS&#L?bp}=dsk{d(;ZJ?iNM|d^x!JR;FiouX`TXedvPkUVQkiZ>KFW z68!!S#w)y_%A?c43noG0ck5@$L2Ed%g4xrz-Ujl!`-La6Oa-FSrvL3NN@1 z8Vg?qww~rTWz;#({4ZX6`MP@nMuI({#qinS2h-^X5*dtm?*#`J(w^uAuZ9l8d%g9q zqPuG*lNEZwN1)%}OTo+{TSFio{E%_s!x%4ovk!C~Z@hTx@0mr@pcmW;jfWSM50Vta z3w{DEhZp=5Dub^9-=A$S02JrCIN`lc_Fl#_Pe2s`<={D}7G5xLAEky*28Xf1P56+j z1S4K{K{b~Sg-=G&90Ii141|z;R(L0@+37uKqV+2k(NnA_UM79-Kv(o`vZ{ZW&d9LtZfdk-5N#& zaRncP4#Eq*2-U!QJ?-LgFMN)gK14HxU>=5AA$UFORp@foGJT>KybH>Pm;Lm3)bdCZH9Y4m~@LucW= zPHpj!M;zgToi17yr4YhsDk%8 zti66{@kf9C3hi924Yt`xLEr`ZK-uuw;DJ}EEqJg0S^U!SEsuHC%>TkX2d%=$>ofNH zlf~iuhu3IVYy{^)r{D!2hx~X}1wVtD!F%1iURSWVfFFhOu@U@Nh(GfX{ss2l#2f%G zI1(y@_xgFo|2yOjZYt;n$3QjkUU#mzdD~V{pig-i2=;+m!DoXlx6n-RUe9aD>y{Pw z>=|etMuP3PQXqK2!B82z*G21f#fpnIa~p-jM(}3nFudSQs21MqVYS}Zka5R~TXxiT z+7id5;Db;Gyx_A?5Z>!m^}1li^SThq#YXTAr~qE@BWMo1*Q=UV$vz$`L%+9@@gKXB z`!_-|Xxyprf`_0Z@Pg^PnP%amj!j+1Q|k4Iil_93z1)IvY6^~l#=;9ufePRSS3z?^ z2xTxgyvM8rp93B~$XJH20lOb!s)P@M&d2W9^?FIgd%6NThmGJdC;_LW*Gua4e2PbO z?q`e+Yy?9q_)~x&cnq2&ktN`;JG;#Xe}QDlr9Nko@+AeuCL7E;N;L`(u7>3Kf!Fit z^@^$*`oY)CCD;qjg<{3qDNGrZiZfBrxZ>dIX1m{81;02!+S>l2b&!*tc zzj6WS1^)(B!h0Q+;=ug=1nqx{fbUOmKR(H@!8sUpH0nA|NO49EIZeA_BRCEk3op19 zDu9nV9kp>rK6{1>LNE9`^fJ8H@t7IC#b?|wefnG>c_Kbqhc zs4u+Wb-!~jfET1V%OB z`F0q?tM=TiI?=xHQ4gEzd2>2*wde((yp(78@MYkpuDB85E5SM4wb~CqAN)B;9`GS` zxmK_CV8x424ld4O4Oc@3V193xF9dt_(dr8HUYDD=;-2b9O`#Wj9V&+Ry3M>UHF2$} zf#idYV2^=}|2+sW>L#cfUN8h5hmX3-T$ftv5Y}^FQz-CqC<|V&4>S}$8*Dk8o(`V@ z{(Lp<53jD#YSOh_F_T!-ohEL!5bb{*?T3-z6VPG!GH^i-oeJLTUlTuD_-0l*-?$h* znqL=$kNVVHU)y2y`RD~tK#Rp5jQH6Er`<|EAp(SX6xt783jX^xBEoweW<&4bkxQOd z{%>8(gU^5$JPHNjYrt;#6bL>WoB-v)=Yy*uxq87VlgShPG%yt6kF03tgBRV2F#)Z> zrBE^aa`4~Ke0X&iHz-K#1*a4cE(w9ZLRIj;ftTHlpXE4(0`GxjGgAn*n#yB5^n!nY zNGGK7VM4B;=7Vopej6CJykKCOn{g}{w7lRj%Z~up3o(KO4_m$9Da#A?y2q`FzF_in z+FzE#$Jt2y^d?_5yYV>&dU8%qo6n-g%=cuV-Op`MNs~6d4Y7s;~5PS zoyG}#_aW$02Yu?ozrvo*$^_q_Mm+Ixbrqi{_*!ZmtoTQwuZv*oB;Ro##~suvCy?SB q9$&U@{krmXo7YvY+wY7?@eOP&$Fq}(y#AHSSN6YBad}wZuKyo+1(^l_ diff --git a/files/vmm.lib b/files/vmm.lib index 28d1f43b97dffeb8f6d9fcf98b795b41297ebf4f..7be0dfa9b42232159c2c0e95dca08a61dc23b381 100644 GIT binary patch delta 1863 zcmb`HO>7fK6vzK#J9gr)B(`I(y}RCB$4-J|RUxG)&6!y99#UWzvBvD!$eVygA6q6dRfh{oF!5(L%4NX9&z4a~ZImb6 zXEe@f@q#U5U` z=T|8cI#H{LiZeU5QaoF{I5RgkT`I1kP?%p>F62i>jt>tPMv4oKURn;QwUbR%2VL`e zePb(2GXt};E5+4Sy5*J{`d8hO^MmPCy6M)+Y>TL;{CIxxWbtBd?$YAV`vT39w~onKlk952q)d*-%ZD`DSgdhVGkA3FT67!Lrr z5eBw8fR|C=N*oxLfGVSKd#($3$nW8kn4Qlgc{jZ1)bT62w4f=F3`DTsm?J;$?>)G$RX~zHke~S9U z|AVNDyN4>le(!EgwCqXMehr=x^tB^k>h4vszfH1xJ%;^CtG(am5rXL3NSaFRecZYm zk!!cx%l{0X4*TnaUxowrU{+A=Zlu{k*`y-7SP1n~;B^{Ko&xF-`Y5G{Ci*Q}qG~Ke zld(^nEFHS#iqJ2y3DHcNv>;mO8|l38(w|aE_-H!5ApEo)FF6AoIp#HJGBHjGxr54H z4{asJovl26&l9D0s#AOgc5rG2l|^MKF^Yv@-iJ6$pGvX*UVg@zhS>c? NV*T^JGkf6z{stI&-TVLm delta 987 zcmb8sOGs2v7zgnG@s5t;YnsQM*L{yRi3_zNB8bW80g)07YFLpPNkZKbc@;V{H@N@bn{@*y>;g(rU?m3hakFh6WLyWT zRQ9-D7MQ0?4)B#WZPru41=cCgn$JmKh8}bN4y~ntA(~I~<*94{Ig^iI0jF1(9A?KB z2OhFxiqF46JIwh7(To-Z5kMR|Vu&J%1ky;sfrl(?WMII82^Zb+vrQjRX!_qda=v|} z@}@nnBpeJ$J{XaKV2ix=f-=&4MDFJgd(8{aJr{<(wbq~m@X=))3Mmpm1%{;5MG50z$a>_cZ+U1E=40QPA zPlK-H?1D7ex-8fea+mS9JrM}|gVP+m>l9?v(dD}{sUmX3y&zqlDS0;`L*8XA=C9i6 z$UE<#9P2eyTo$r7lrDd=*HuEMdu&0kJ)*o$=_MS17V(Xqt*ZMAMA @@ -26,13 +26,15 @@ extern "C" { * about the parameters please see github wiki for Memory Process File System * and LeechCore. THIS IS THE PREFERED WAY OF INITIALIZING VMM.DLL * Important parameters are: -* -vdll = show printf style outputs) +* -printf = show printf style outputs) * -v -vv -vvv = extra verbosity levels) * -device = device as on format for LeechCore - please see leechcore.h or * Github documentation for additional information. Some values * are: , fpga, usb3380, hvsavedstate, totalmeltdown, pmem * -remote = remote LeechCore instance - please see leechcore.h or Github * documentation for additional information. +* -norefresh = disable background refreshes (even if backing memory is +* volatile memory). * -- argc * -- argv * -- return = success/fail @@ -289,7 +291,7 @@ typedef struct tdVMMDLL_PLUGIN_REGINFO { * -- ppMEMs = array of scatter read headers. * -- cpMEMs = count of ppDMAs. * -- pcpDMAsRead = optional count of number of successfully read ppDMAs. -* -- flags = optional flags as given by VMM_FLAG_* +* -- flags = optional flags as given by VMMDLL_FLAG_* * -- return = the number of successfully read items. */ DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); @@ -322,13 +324,25 @@ BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DW * -- pb * -- cb * -- pcbRead -* -- flags = flags as in VMM_FLAG_* +* -- flags = flags as in VMMDLL_FLAG_* * -- return = success/fail. NB! reads may report as success even if 0 bytes are * read - it's recommended to verify pcbReadOpt parameter. */ _Success_(return) BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); +/* +* Prefetch a number of addresses (specified in the pA array) into the memory +* cache. This function is to be used to batch larger known reads into local +* cache before making multiple smaller reads - which will then happen from +* the cache. Function exists for performance reasons. +* -- dwPID = PID of target process, (DWORD)-1 for physical memory. +* -- pPrefetchAddresses = array of addresses to read into cache. +* -- cPrefetchAddresses +*/ +_Success_(return) +BOOL VMMDLL_MemPrefetchPages(_In_ DWORD dwPID, _In_reads_(cPrefetchAddresses) PULONG64 pPrefetchAddresses, _In_ DWORD cPrefetchAddresses); + /* * Write a contigious arbitrary amount of memory. Please note some virtual memory * such as pages of executables (such as DLLs) may be shared between different @@ -527,6 +541,87 @@ BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMD _Success_(return) BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +/* +* Retrieve the virtual address of a given function inside a process/module. +* -- dwPID +* -- szModuleName +* -- szFunctionName +* -- return = virtual address of function, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetProcAddress(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szFunctionName); + +/* +* Retrieve the base address of a given module. +* -- dwPID +* -- szModuleName +* -- return = virtual address of module base, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetModuleBase(_In_ DWORD dwPID, _In_ LPSTR szModuleName); + + + +//----------------------------------------------------------------------------- +// WINDOWS SPECIFIC UTILITY FUNCTIONS BELOW: +//----------------------------------------------------------------------------- + +typedef struct tdVMMDLL_WIN_THUNKINFO_IAT { + BOOL fValid; + BOOL f32; // if TRUE fn is a 32-bit/4-byte entry, otherwise 64-bit/8-byte entry. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaFunction; // value if import address table 'thunk' == address of imported function. + ULONG64 vaNameModule; // address of name string for imported module. + ULONG64 vaNameFunction; // address of name string for imported function. +} VMMDLL_WIN_THUNKINFO_IAT, *PVMMDLL_WIN_THUNKINFO_IAT; + +typedef struct tdVMMDLL_WIN_THUNKINFO_EAT { + BOOL fValid; + DWORD valueThunk; // value of export address table 'thunk'. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaNameFunction; // address of name string for exported function. + ULONG64 vaFunction; // address of exported function (module base + value parameter). +} VMMDLL_WIN_THUNKINFO_EAT, *PVMMDLL_WIN_THUNKINFO_EAT; + +/* +* Retrieve information about the import address table IAT thunk for an imported +* function. This includes the virtual address of the IAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- szImportModuleName +* -- szImportFunctionName +* -- pThunkIAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoIAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_IAT pThunkInfoIAT); + +/* +* Retrieve information about the export address table EAT thunk for an exported +* function. This includes the virtual address of the EAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- pThunkEAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoEAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szExportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_EAT pThunkInfoEAT); + +/* +* Decompress compressed memory page stored in the MemCompression process. +* -- vaCompressedData = virtual address in 'MemCompression' to decompress. +* -- cbCompressedData = length of compressed data in 'MemCompression' to decompress (or zero for auto-detect). +* -- pbDecompressedPage +* -- pcbCompressedData = optional ptr to receive length of compressed buffer. +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinMemCompression_DecompressPage( + _In_ ULONG64 vaCompressedData, + _In_opt_ DWORD cbCompressedData, + _Out_writes_(4096) PBYTE pbDecompressedPage, + _Out_opt_ PDWORD pcbCompressedData +); //----------------------------------------------------------------------------- diff --git a/files/vmmpy.py b/files/vmmpy.py index 6b62ebe..65d65fe 100644 --- a/files/vmmpy.py +++ b/files/vmmpy.py @@ -9,7 +9,7 @@ # # https://github.com/ufrisk/ # -# (c) Ulf Frisk, 2018 +# (c) Ulf Frisk, 2018-2019 # Author: Ulf Frisk, pcileech@frizk.net # @@ -56,7 +56,7 @@ def VmmPy_Close(): N/A Example: - VmmPy_Close() --> True + VmmPy_Close() """ VMMPYC_Close() @@ -485,6 +485,58 @@ def VmmPy_VfsWrite(path_file, bytes_data, offset = 0): VmmPy_VfsWrite(path_file, bytes_data, offset) +#------------------------------------------------------------------------------ +# VmmPy WINDOWS ONLY FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ + +def VmmPy_WinGetThunkInfoEAT(pid, module_name, exported_function): + """Retrieve information about a single export address table (EAT) entry. This may be useful for hooking. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + module_name -- str: name of the module to retrieve. + exported_function -- str: name of the exported function to retrieve. + return -- dict: information about the EAT entry. + + Example: + VmmPy_WinGetThunkInfoEAT(4, 'ntoskrnl.exe', 'KeGetCurrentIrql') --> {'vaFunction': 18446735288139539584, 'valueThunk': 1479808, 'vaNameFunction': 18446735288147899428, 'vaThunk': 18446735288147849312} + """ + return VMMPYC_WinGetThunkInfoEAT(pid, module_name, exported_function) + + + +def VmmPy_WinGetThunkInfoIAT(pid, module_name, imported_module_name, imported_module_function): + """Retrieve information about a single import address table (IAT) entry. This may be useful for hooking. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + module_name -- str: name of the module to retrieve. + imported_module_name -- str: name of the imported module to retrieve. + imported_module_function -- str: name of the imported function to retrieve. + return -- dict: information about the IAT entry. + + Example: + VmmPy_WinGetThunkInfoIAT(4, 'ntoskrnl.exe', 'hal.dll', 'HalSendNMI') --> {'32': False, 'vaFunction': 18446735288149190896, 'vaNameFunction': 18446735288143568050, 'vaNameModule': 18446735288143568362, 'vaThunk': 18446735288143561136} + """ + return VMMPYC_WinGetThunkInfoIAT(pid, module_name, imported_module_name, imported_module_function) + + + +def VmmPy_WinDecompressPage(va_compressed, len_compressed = 0): + """Decompress a page stored in the MemCompression process in Windows 10. + + Keyword arguments: + va_compressed -- int: the virtual address inside 'MemCompression' where the compressed buffer starts. + len_compressed -- int: optional length of the compressed buffer (leave out for auto-detect). + return -- dict: containing decompressed data and size of compressed buffer. + + Example: + VmmPy_WinDecompressPage(0x00000210bfb40000) --> {'c': 456, 'b': b'...'} + """ + return VMMPYC_WinMemCompression_DecompressPage(va_compressed, len_compressed) + + + #------------------------------------------------------------------------------ # VmmPy UTIL FUNCTIONALITY BELOW: #------------------------------------------------------------------------------ diff --git a/files/vmmpyc.pyd b/files/vmmpyc.pyd index 4932d84808e732596d7fb0286385a7109850eb9c..0b9a4c271cdfb2c26f389a06396dbbca6cf52cfe 100644 GIT binary patch literal 35840 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 literal 32768 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®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 a_4fA;_8#XgJKd{2SVJxP=jXps1OEk~v%-V` diff --git a/vmm/leechcore.h b/vmm/leechcore.h index a2da3e6..46af16a 100644 --- a/vmm/leechcore.h +++ b/vmm/leechcore.h @@ -68,13 +68,14 @@ // placed in same directory as the executable file. // // PMEM : load the rekall winpmem driver into the kernel and connect to it -// to acquire memory. The driver file 'winpmem_x64.sys' is found in -// the Rekall directory after most recent version has been installed. -// Copy 'winpmem_x64.sys' to the directory of leechcore.dll and run -// executable as elevated admin using syntax below: +// to acquire memory. The signed driver `.sys` file may be found at: +// https://github.com/Velocidex/c-aff4/tree/master/tools/pmem/resources/winpmem +// Download the driver file `att_winpmem_64.sys` and copy it to the +// directory of leechcore.dll and run executable as elevated admin +// using syntax below: // Syntax: -// PMEM (use winpmem_x64.sys in directory of executable) -// PMEM:// +// PMEM (use att_winpmem_64.sys in directory of executable) +// PMEM:// // // TOTALMELTDOWN : read/write - requires a Windows 7 system vulnerable to the // "Total Meltdown" vulnerability - CVE-2018-1038. @@ -110,7 +111,7 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // -// Header Version: 1.0 +// Header Version: 1.1.0 // #ifndef __LEECHCORE_H__ #define __LEECHCORE_H__ @@ -151,6 +152,7 @@ typedef long long unsigned int QWORD, *PQWORD, ULONG64, *PULONG64; #define _Printf_format_string_ #define _Inout_updates_bytes_(x) #define _In_reads_(cbDataIn) +#define _Out_writes_opt_(x) #define _Success_(return) #endif /* LINUX */ @@ -227,7 +229,10 @@ typedef struct tdLEECHCORE_PAGESTAT_MINIMAL { } LEECHCORE_PAGESTAT_MINIMAL, *PLEECHCORE_PAGESTAT_MINIMAL; /* -* Open a connection to the target device. +* Open a connection to the target device. The LeechCore initialization may fail +* if the underlying device cannot be opened or if the LeechCore is already +* initialized. If already initialized please connect with device EXISTING or +* call LeechCore_Close() before opening a new device. * -- pInformation * -- result */ @@ -455,9 +460,9 @@ DLLEXPORT BOOL LeechCore_CommandData( _In_ ULONG64 fOption, _In_reads_(cbDataIn) PBYTE pbDataIn, _In_ DWORD cbDataIn, - _Out_writes_(cbDataOut) PBYTE pbDataOut, + _Out_writes_opt_(cbDataOut) PBYTE pbDataOut, _In_ DWORD cbDataOut, - _Out_ PDWORD pcbDataOut + _Out_opt_ PDWORD pcbDataOut ); #ifdef __cplusplus diff --git a/vmm/m_ldrmodules.c b/vmm/m_ldrmodules.c index 34df75a..9e46e32 100644 --- a/vmm/m_ldrmodules.c +++ b/vmm/m_ldrmodules.c @@ -35,7 +35,7 @@ typedef struct tdOBLDRMODULES_CACHE_ENTRY { */ POBLDRMODULES_CACHE_ENTRY LdrModule_GetEAT(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ PVMM_MODULEMAP_ENTRY pModule) { - DWORD i, o, cEATs; + DWORD i, o, cEATs = 0; PVMMPROC_WINDOWS_EAT_ENTRY pEATs = NULL; POBLDRMODULES_CACHE_ENTRY pObCacheEntry = NULL; PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; @@ -47,10 +47,9 @@ POBLDRMODULES_CACHE_ENTRY LdrModule_GetEAT(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ VmmOb_DECREF(pObCacheEntry); pObCacheEntry = NULL; // 2: retrieve exported functions - cEATs = LDRMODULES_MAX_IATEAT; pEATs = LocalAlloc(0, LDRMODULES_MAX_IATEAT * sizeof(VMMPROC_WINDOWS_EAT_ENTRY)); if(!pEATs) { goto fail; } - VmmWin_PE_LoadEAT_DisplayBuffer(ctx->pProcess, pModule, pEATs, &cEATs); + VmmWin_PE_LoadEAT_DisplayBuffer(ctx->pProcess, pModule, pEATs, LDRMODULES_MAX_IATEAT, &cEATs); if(!cEATs) { goto fail; } // 3: fill "display buffer" pObCacheEntry = VmmOb_Alloc('EA', LMEM_ZEROINIT, sizeof(OBLDRMODULES_CACHE_ENTRY) + (QWORD)cEATs * 64 + 1, NULL, NULL); @@ -87,7 +86,7 @@ fail: */ POBLDRMODULES_CACHE_ENTRY LdrModule_GetIAT(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ PVMM_MODULEMAP_ENTRY pModule) { - DWORD i, o, cIATs; + DWORD i, o, cIATs = 0; PVMMWIN_IAT_ENTRY pIATs = NULL; POBLDRMODULES_CACHE_ENTRY pObCacheEntry = NULL; PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; @@ -99,10 +98,9 @@ POBLDRMODULES_CACHE_ENTRY LdrModule_GetIAT(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ VmmOb_DECREF(pObCacheEntry); pObCacheEntry = NULL; // 2: retrieve exported functions - cIATs = LDRMODULES_MAX_IATEAT; pIATs = LocalAlloc(0, LDRMODULES_MAX_IATEAT * sizeof(VMMWIN_IAT_ENTRY)); if(!pIATs) { goto fail; } - VmmWin_PE_LoadIAT_DisplayBuffer(ctx->pProcess, pModule, pIATs, &cIATs); + VmmWin_PE_LoadIAT_DisplayBuffer(ctx->pProcess, pModule, pIATs, LDRMODULES_MAX_IATEAT, &cIATs); if(!cIATs) { goto fail; } // 3: fill "display buffer" pObCacheEntry = VmmOb_Alloc('IA', LMEM_ZEROINIT, sizeof(OBLDRMODULES_CACHE_ENTRY) + (QWORD)cIATs * 128 + 1, NULL, NULL); diff --git a/vmm/mm_x64_winpaged.c b/vmm/mm_x64_winpaged.c new file mode 100644 index 0000000..3d204ad --- /dev/null +++ b/vmm/mm_x64_winpaged.c @@ -0,0 +1,54 @@ +// mm_x64_winpaged.c : implementation related to the x64 windows paging subsystem +// (including paged out virtual/compressed virtual memory). +// +// (c) Ulf Frisk, 2019 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "mm_x64_winpaged.h" + +#define COMPRESS_ALGORITHM_INVALID 0 +#define COMPRESS_ALGORITHM_NULL 1 +#define COMPRESS_ALGORITHM_MSZIP 2 +#define COMPRESS_ALGORITHM_XPRESS 3 +#define COMPRESS_ALGORITHM_XPRESS_HUFF 4 +#define COMPRESS_ALGORITHM_LZMS 5 +#define COMPRESS_ALGORITHM_MAX 6 +#define COMPRESS_RAW (1 << 29) + +_Success_(return) +BOOL MmX64WinPaged_MemCompression_DecompressPage(_In_ QWORD vaCompressedData, _In_opt_ DWORD cbCompressedData, _Out_writes_(4096) PBYTE pbDecompressedPage, _Out_opt_ PDWORD pcbCompressedData) +{ + BOOL result = FALSE; + DWORD i, cbReadCompressedData = 0, cbDecompressed = 0; + BYTE pbCompressed[0x1000] = { 0 }; + PVMM_PROCESS pObProcess = NULL; + if(pcbCompressedData) { *pcbCompressedData = 0; } + if(!ctxVmm->fn.RtlDecompressBuffer) { return FALSE; } + if(cbCompressedData > 0x1000) { return FALSE; } + if(!ctxVmm->kernel.dwPidMemCompression) { return FALSE; } + if(!(pObProcess = VmmProcessGet(ctxVmm->kernel.dwPidMemCompression))) { return FALSE; } + // buffer size specified - use value! + if(cbCompressedData) { + result = + VmmRead(pObProcess, vaCompressedData, pbCompressed, cbCompressedData) && + (VMM_STATUS_SUCCESS == ctxVmm->fn.RtlDecompressBuffer(COMPRESS_ALGORITHM_XPRESS, pbDecompressedPage, 0x1000, pbCompressed, cbCompressedData, &cbDecompressed)) && + (cbDecompressed == 0x1000); + VmmOb_DECREF(pObProcess); pObProcess = NULL; + if(pcbCompressedData) { *pcbCompressedData = cbCompressedData; } + return result; + } + // buffer not specified - try auto-detect! + VmmReadEx(pObProcess, vaCompressedData, pbCompressed, 0x1000, &cbReadCompressedData, VMM_FLAG_ZEROPAD_ON_FAIL); + VmmOb_DECREF(pObProcess); pObProcess = NULL; + if(cbReadCompressedData < 0x10) { return FALSE; } + for(i = 0x10; i < 0x1000; i++) { + result = + (VMM_STATUS_SUCCESS == ctxVmm->fn.RtlDecompressBuffer(COMPRESS_ALGORITHM_XPRESS, pbDecompressedPage, 0x1000, pbCompressed, i, &cbDecompressed)) && + (cbDecompressed == 0x1000); + if(result) { + if(pcbCompressedData) { *pcbCompressedData = i; } + return TRUE; + } + } + return FALSE; +} diff --git a/vmm/mm_x64_winpaged.h b/vmm/mm_x64_winpaged.h new file mode 100644 index 0000000..4b387ea --- /dev/null +++ b/vmm/mm_x64_winpaged.h @@ -0,0 +1,26 @@ +// mm_x64_winpaged.h : definitions related to the x64 windows paging subsystem +// (including paged out virtual/compressed virtual memory). +// +// (c) Ulf Frisk, 2019 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __MM_X64_WINPAGED_H__ +#define __MM_X64_WINPAGED_H__ +#include "vmm.h" + +/* +* Decompress compressed memory page stored in the MemCompression process. +* -- vaCompressedData = virtual address in 'MemCompression' to decompress. +* -- cbCompressedData = length of compressed data in 'MemCompression' to decompress. +* -- pbDecompressedPage +* -- return +*/ +_Success_(return) +BOOL MmX64WinPaged_MemCompression_DecompressPage( + _In_ QWORD vaCompressedData, + _In_opt_ DWORD cbCompressedData, + _Out_writes_(4096) PBYTE pbDecompressedPage, + _Out_opt_ PDWORD pcbCompressedData +); + +#endif /* __MM_X64_WINPAGED_H__ */ diff --git a/vmm/pe.c b/vmm/pe.c index be5fbc1..6e9c56c 100644 --- a/vmm/pe.c +++ b/vmm/pe.c @@ -5,8 +5,8 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // - #include "vmm.h" +#include "pe.h" PIMAGE_NT_HEADERS PE_HeaderGetVerify(_In_ PVMM_PROCESS pProcess, _In_opt_ QWORD vaModuleBase, _Inout_ PBYTE pbModuleHeader, _Out_opt_ PBOOL pfHdr32) { @@ -40,7 +40,102 @@ QWORD PE_GetSize(_In_ PVMM_PROCESS pProcess, _In_opt_ QWORD vaModuleBase) return cbSize; } -QWORD PE_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In_ LPSTR lpProcName) +_Success_(return) +BOOL PE_GetThunkInfoIAT(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportProcName, _Out_ PPE_THUNKINFO_IAT pThunkInfoIAT) +{ + BYTE pbModuleHeader[0x1000] = { 0 }; + PIMAGE_NT_HEADERS64 ntHeader64; + PIMAGE_NT_HEADERS32 ntHeader32; + QWORD i, oImportDirectory; + PIMAGE_IMPORT_DESCRIPTOR pIID; + PQWORD pIAT64, pHNA64; + PDWORD pIAT32, pHNA32; + DWORD cbModule, cbRead; + PBYTE pbModule = NULL; + BOOL f32, fFnName; + DWORD c, j; + LPSTR szNameFunction, szNameModule; + // load both 32/64 bit ntHeader (only one will be valid) + if(!(ntHeader64 = PE_HeaderGetVerify(pProcess, vaModuleBase, pbModuleHeader, &f32))) { goto fail; } + ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; + cbModule = f32 ? + ntHeader32->OptionalHeader.SizeOfImage : + ntHeader64->OptionalHeader.SizeOfImage; + if(cbModule > 0x02000000) { goto fail; } + oImportDirectory = f32 ? + ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress : + ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + if(!oImportDirectory || (oImportDirectory >= cbModule)) { goto fail; } + if(!(pbModule = LocalAlloc(LMEM_ZEROINIT, cbModule))) { goto fail; } + VmmReadEx(pProcess, vaModuleBase, pbModule, cbModule, &cbRead, VMM_FLAG_ZEROPAD_ON_FAIL); + if(cbRead <= 0x2000) { goto fail; } + // Walk imported modules / functions + pIID = (PIMAGE_IMPORT_DESCRIPTOR)(pbModule + oImportDirectory); + i = 0, c = 0; + while((oImportDirectory + (i + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR) < cbModule) && pIID[i].FirstThunk) { + if(pIID[i].Name > cbModule - 64) { i++; continue; } + if(f32) { + // 32-bit PE + j = 0; + pIAT32 = (PDWORD)(pbModule + pIID[i].FirstThunk); + pHNA32 = (PDWORD)(pbModule + pIID[i].OriginalFirstThunk); + while(TRUE) { + if((QWORD)(pIAT32 + j) + sizeof(DWORD) - (QWORD)pbModule > cbModule) { break; } + if((QWORD)(pHNA32 + j) + sizeof(DWORD) - (QWORD)pbModule > cbModule) { break; } + if(!pIAT32[j]) { break; } + if(!pHNA32[j]) { break; } + fFnName = (pHNA32[j] < cbModule - 40); + szNameFunction = (LPSTR)(pbModule + pHNA32[j] + 2); + szNameModule = (LPSTR)(pbModule + pIID[i].Name); + if(fFnName && !strcmp(szNameFunction, szImportProcName) && !_stricmp(szNameModule, szImportModuleName)) { + pThunkInfoIAT->fValid = TRUE; + pThunkInfoIAT->f32 = TRUE; + pThunkInfoIAT->vaThunk = vaModuleBase + pIID[i].FirstThunk + sizeof(DWORD) * j; + pThunkInfoIAT->vaFunction = pIAT32[j]; + pThunkInfoIAT->vaNameFunction = vaModuleBase + pHNA32[j] + 2; + pThunkInfoIAT->vaNameModule = vaModuleBase + pIID[i].Name; + LocalFree(pbModule); + return TRUE; + } + c++; + j++; + } + } else { + // 64-bit PE + j = 0; + pIAT64 = (PQWORD)(pbModule + pIID[i].FirstThunk); + pHNA64 = (PQWORD)(pbModule + pIID[i].OriginalFirstThunk); + while(TRUE) { + if((QWORD)(pIAT64 + j) + sizeof(QWORD) - (QWORD)pbModule > cbModule) { break; } + if((QWORD)(pHNA64 + j) + sizeof(QWORD) - (QWORD)pbModule > cbModule) { break; } + if(!pIAT64[j]) { break; } + if(!pHNA64[j]) { break; } + fFnName = (pHNA64[j] < cbModule - 40); + szNameFunction = (LPSTR)(pbModule + pHNA64[j] + 2); + szNameModule = (LPSTR)(pbModule + pIID[i].Name); + if(fFnName && !strcmp(szNameFunction, szImportProcName) && !_stricmp(szNameModule, szImportModuleName)) { + pThunkInfoIAT->fValid = TRUE; + pThunkInfoIAT->f32 = FALSE; + pThunkInfoIAT->vaThunk = vaModuleBase + pIID[i].FirstThunk + sizeof(QWORD) * j; + pThunkInfoIAT->vaFunction = pIAT64[j]; + pThunkInfoIAT->vaNameFunction = vaModuleBase + pHNA64[j] + 2; + pThunkInfoIAT->vaNameModule = vaModuleBase + pIID[i].Name; + LocalFree(pbModule); + return TRUE; + } + c++; + j++; + } + } + i++; + } +fail: + LocalFree(pbModule); + return FALSE; +} + +_Success_(return) +BOOL PE_GetThunkInfoEAT(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In_ LPSTR szProcName, _Out_ PPE_THUNKINFO_EAT pThunkInfoEAT) { BYTE pbModuleHeader[0x1000] = { 0 }; PIMAGE_NT_HEADERS32 ntHeader32; @@ -49,7 +144,6 @@ QWORD PE_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In PWORD pwNameOrdinals; DWORD i, cbProcName, cbExportDirectoryOffset, cbRead = 0; LPSTR sz; - QWORD vaFnPtr; QWORD vaExportDirectory; DWORD cbExportDirectory; PBYTE pbExportDirectory = NULL; @@ -76,7 +170,7 @@ QWORD PE_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In if((vaRVAAddrNames < vaExportDirectory) || (vaRVAAddrNames > vaExportDirectory + cbExportDirectory - exp->NumberOfNames * sizeof(DWORD))) { goto cleanup; } if((vaNameOrdinals < vaExportDirectory) || (vaNameOrdinals > vaExportDirectory + cbExportDirectory - exp->NumberOfNames * sizeof(WORD))) { goto cleanup; } if((vaRVAAddrFunctions < vaExportDirectory) || (vaRVAAddrFunctions > vaExportDirectory + cbExportDirectory - exp->NumberOfNames * sizeof(DWORD))) { goto cleanup; } - cbProcName = (DWORD)strnlen_s(lpProcName, MAX_PATH) + 1; + cbProcName = (DWORD)strnlen_s(szProcName, MAX_PATH) + 1; cbExportDirectoryOffset = (DWORD)(vaExportDirectory - vaModuleBase); pdwRVAAddrNames = (PDWORD)(pbExportDirectory + exp->AddressOfNames - cbExportDirectoryOffset); pwNameOrdinals = (PWORD)(pbExportDirectory + exp->AddressOfNameOrdinals - cbExportDirectoryOffset); @@ -84,16 +178,27 @@ QWORD PE_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In for(i = 0; i < exp->NumberOfNames; i++) { if(pdwRVAAddrNames[i] - cbExportDirectoryOffset + cbProcName > cbExportDirectory) { continue; } sz = (LPSTR)(pbExportDirectory + pdwRVAAddrNames[i] - cbExportDirectoryOffset); - if(0 == memcmp(sz, lpProcName, cbProcName)) { + if(0 == memcmp(sz, szProcName, cbProcName)) { if(pwNameOrdinals[i] >= exp->NumberOfFunctions) { goto cleanup; } - vaFnPtr = (QWORD)(vaModuleBase + pdwRVAAddrFunctions[pwNameOrdinals[i]]); + pThunkInfoEAT->fValid = TRUE; + pThunkInfoEAT->vaFunction = (QWORD)(vaModuleBase + pdwRVAAddrFunctions[pwNameOrdinals[i]]); + pThunkInfoEAT->valueThunk = pdwRVAAddrFunctions[pwNameOrdinals[i]]; + pThunkInfoEAT->vaThunk = vaExportDirectory + exp->AddressOfFunctions - cbExportDirectoryOffset + sizeof(DWORD) * pwNameOrdinals[i]; + pThunkInfoEAT->vaNameFunction = vaExportDirectory + pdwRVAAddrNames[i] - cbExportDirectoryOffset; LocalFree(pbExportDirectory); - return vaFnPtr; + return TRUE; } } cleanup: LocalFree(pbExportDirectory); - return 0; + return FALSE; +} + +QWORD PE_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In_ LPSTR lpProcName) +{ + PE_THUNKINFO_EAT oThunkInfoEAT = { 0 }; + PE_GetThunkInfoEAT(pProcess, vaModuleBase, lpProcName, &oThunkInfoEAT); + return oThunkInfoEAT.vaFunction; } WORD PE_SectionGetNumberOfEx(_In_ PVMM_PROCESS pProcess, _In_opt_ QWORD vaModuleBase, _In_reads_opt_(0x1000) PBYTE pbModuleHeaderOpt) diff --git a/vmm/pe.h b/vmm/pe.h index 47818d7..5912892 100644 --- a/vmm/pe.h +++ b/vmm/pe.h @@ -15,6 +15,23 @@ static const LPCSTR PE_DATA_DIRECTORIES[16] = { "EXPORT", "IMPORT", "RESOURCE", "EXCEPTION", "SECURITY", "BASERELOC", "DEBUG", "ARCHITECTURE", "GLOBALPTR", "TLS", "LOAD_CONFIG", "BOUND_IMPORT", "IAT", "DELAY_IMPORT", "COM_DESCRIPTOR", "RESERVED" }; +typedef struct tdPE_THUNKINFO_IAT { + BOOL fValid; + BOOL f32; // if TRUE fn is a 32-bit/4-byte entry, otherwise 64-bit/8-byte entry. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaFunction; // value if import address table 'thunk' == address of imported function. + ULONG64 vaNameModule; // address of name string for imported module. + ULONG64 vaNameFunction; // address of name string for imported function. +} PE_THUNKINFO_IAT, *PPE_THUNKINFO_IAT; + +typedef struct tdPE_THUNKINFO_EAT { + BOOL fValid; + DWORD valueThunk; // value of export address table 'thunk'. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaNameFunction; // address of name string for exported function. + ULONG64 vaFunction; // address of exported function (module base + value parameter). +} PE_THUNKINFO_EAT, *PPE_THUNKINFO_EAT; + /* * Retrieve the size of the module given its base. * -- pProcess @@ -32,6 +49,31 @@ QWORD PE_GetSize(_In_ PVMM_PROCESS pProcess, _In_opt_ QWORD vaModuleBase); */ QWORD PE_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In_ LPSTR lpProcName); +/* +* Lookup the virtual address of an exported function or symbol in the module supplied +* among with additional information returned in the pThunkInfoEAT struct. +* -- pProcess +* -- vaModuleBase = PE module base address. +* -- szProcName +* -- pThunkInfoEAT +* -- return +*/ +_Success_(return) +BOOL PE_GetThunkInfoEAT(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In_ LPSTR szProcName, _Out_ PPE_THUNKINFO_EAT pThunkInfoEAT); + +/* +* Retrieve an import address table (IAT) entry for a specific function. +* This may be useful for IAT patching functionality. +* -- pProcess +* -- vaModuleBase +* -- szImportModuleName +* -- szImportProcName +* -- pThunkInfoIAT +* -- return +*/ +_Success_(return) +BOOL PE_GetThunkInfoIAT(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModuleBase, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportProcName, _Out_ PPE_THUNKINFO_IAT pThunkInfoIAT); + /* * Retrieve the module name and optionally the module size. * -- pProcess diff --git a/vmm/statistics.c b/vmm/statistics.c index a2c6141..ca23bcd 100644 --- a/vmm/statistics.c +++ b/vmm/statistics.c @@ -12,47 +12,34 @@ VOID _PageStatPrintMemMap(_Inout_ PPAGE_STATISTICS ps) { - BOOL fIsLinePrinted = FALSE; - QWORD i, qwAddrBase, qwAddrEnd; + QWORD i, qwAddrEnd; if(!ps->i.fIsFirstPrintCompleted) { - vmmprintf(" Memory Map: \n START END #PAGES \n"); + printf(" Memory Map: \n START END #PAGES \n"); } - if(!ps->i.MemMapIdx && !ps->i.MemMap[0]) { - vmmprintf(" \n \n"); + if(!ps->i.MemMapIdx) { + printf(" \n \n"); return; } - if(ps->i.MemMapPrintCommitIdx >= PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 4) { - vmmprintf(" Maximum number of memory map entries reached. \n \n"); + if(ps->i.MemMapIdx >= PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 2) { + printf(" Maximum number of memory map entries reached. \n \n"); return; } - qwAddrBase = ps->i.qwAddrBase + ps->i.MemMapPrintCommitPages * 0x1000; - for(i = ps->i.MemMapPrintCommitIdx; i < PAGE_STATISTICS_MEM_MAP_MAX_ENTRY; i++) { - if(!ps->i.MemMap[i] && i == 0) { - continue; - } - if(!ps->i.MemMap[i] || (i == PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 1)) { + for(i = max(1, ps->i.MemMapPrintIdx); i <= ps->i.MemMapIdx; i++) { + if(!ps->i.MemMap[i].cPages) { break; } - qwAddrEnd = qwAddrBase + 0x1000 * (QWORD)ps->i.MemMap[i]; - if((i % 2) == 0) { - fIsLinePrinted = TRUE; - vmmprintf( - " %016llx - %016llx %08x \n", - qwAddrBase, - qwAddrEnd - 1, - ps->i.MemMap[i]); - if(i >= ps->i.MemMapPrintCommitIdx + 2) { - ps->i.MemMapPrintCommitPages += ps->i.MemMap[ps->i.MemMapPrintCommitIdx++]; - ps->i.MemMapPrintCommitPages += ps->i.MemMap[ps->i.MemMapPrintCommitIdx++]; - - } - } - qwAddrBase = qwAddrEnd; + qwAddrEnd = ps->i.MemMap[i].qwAddrBase + ((QWORD)ps->i.MemMap[i].cPages << 12); + printf( + " %016llx - %016llx %08x \n", + ps->i.MemMap[i].qwAddrBase, + qwAddrEnd - 1, + ps->i.MemMap[i].cPages); } - if(!fIsLinePrinted) { // print extra line for formatting reasons. - vmmprintf(" (No memory successfully read yet) \n"); + ps->i.MemMapPrintIdx = ps->i.MemMapIdx; + if(!ps->i.MemMap[1].cPages) { // print extra line for formatting reasons. + printf(" (No memory successfully read yet) \n"); } - vmmprintf(" \n"); + printf(" \n"); } VOID _PageStatShowUpdate(_Inout_ PPAGE_STATISTICS ps) @@ -135,55 +122,54 @@ VOID _PageStatThreadLoop(_In_ PPAGE_STATISTICS ps) ExitThread(0); } -VOID PageStatClose(_Inout_ PPAGE_STATISTICS ps) +VOID PageStatClose(_In_opt_ PPAGE_STATISTICS *ppPageStat) { BOOL status; DWORD dwExitCode; - ps->i.fUpdate = TRUE; - ps->i.fThreadExit = TRUE; - while((status = GetExitCodeThread(ps->i.hThread, &dwExitCode)) && STILL_ACTIVE == dwExitCode) { + if(!ppPageStat || !*ppPageStat) { return; } + (*ppPageStat)->i.fUpdate = TRUE; + (*ppPageStat)->i.fThreadExit = TRUE; + while((status = GetExitCodeThread((*ppPageStat)->i.hThread, &dwExitCode)) && STILL_ACTIVE == dwExitCode) { SwitchToThread(); } if(!status) { Sleep(200); } + LocalFree(*ppPageStat); + *ppPageStat = NULL; } -VOID PageStatInitialize(_Inout_ PPAGE_STATISTICS ps, _In_ QWORD qwAddrBase, _In_ QWORD qwAddrMax, _In_ LPSTR szAction, _In_ BOOL fKMD, _In_ BOOL fMemMap) +_Success_(return) +BOOL PageStatInitialize(_Out_ PPAGE_STATISTICS *ppPageStat, _In_ QWORD qwAddrBase, _In_ QWORD qwAddrMax, _In_ LPSTR szAction, _In_ BOOL fKMD, _In_ BOOL fMemMap) { - memset(ps, 0, sizeof(PAGE_STATISTICS)); + PPAGE_STATISTICS ps; + ps = *ppPageStat = LocalAlloc(LMEM_ZEROINIT, sizeof(PAGE_STATISTICS)); + if(!ps) { return FALSE; } ps->qwAddr = qwAddrBase; ps->cPageTotal = (qwAddrMax - qwAddrBase + 1) / 4096; ps->szAction = szAction; ps->fKMD = fKMD; ps->i.fMemMap = fMemMap; - ps->i.qwAddrBase = qwAddrBase; ps->i.qwTickCountStart = GetTickCount64(); ps->i.hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_PageStatThreadLoop, ps, 0, NULL); + return TRUE; } -VOID PageStatUpdate(_Inout_opt_ PPAGE_STATISTICS ps, _In_ QWORD qwAddr, _In_ QWORD cPageSuccessAdd, _In_ QWORD cPageFailAdd) +VOID PageStatUpdate(_In_opt_ PPAGE_STATISTICS pPageStat, _In_ QWORD qwAddr, _In_ QWORD cPageSuccessAdd, _In_ QWORD cPageFailAdd) { - if(!ps) { return; } - ps->qwAddr = qwAddr; - ps->cPageSuccess += cPageSuccessAdd; - ps->cPageFail += cPageFailAdd; - // add to memory map, even == success, odd = fail. - if(ps->i.MemMapIdx < PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 2) { - if(cPageSuccessAdd) { - if(ps->i.MemMapIdx % 2 == 1) { - ps->i.MemMapIdx++; - } - ps->i.MemMap[ps->i.MemMapIdx] += (DWORD)cPageSuccessAdd; - } - if(cPageFailAdd) { - if(ps->i.MemMapIdx % 2 == 0) { - ps->i.MemMapIdx++; - } - ps->i.MemMap[ps->i.MemMapIdx] += (DWORD)cPageFailAdd; + if(!pPageStat) { return; } + pPageStat->qwAddr = qwAddr; + pPageStat->cPageSuccess += cPageSuccessAdd; + pPageStat->cPageFail += cPageFailAdd; + // add to memory map + if(cPageSuccessAdd && (pPageStat->i.MemMapIdx < PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 1)) { + if(!pPageStat->i.MemMapIdx || (qwAddr - (cPageSuccessAdd << 12)) != (pPageStat->i.MemMap[pPageStat->i.MemMapIdx].qwAddrBase + ((QWORD)pPageStat->i.MemMap[pPageStat->i.MemMapIdx].cPages << 12))) { + pPageStat->i.MemMapIdx++; + pPageStat->i.MemMap[pPageStat->i.MemMapIdx].qwAddrBase = qwAddr - (cPageSuccessAdd << 12); } + pPageStat->i.MemMap[pPageStat->i.MemMapIdx].cPages += (DWORD)cPageSuccessAdd; } - ps->i.fUpdate = TRUE; + pPageStat->i.fUpdate = TRUE; } // ---------------------------------------------------------------------------- @@ -199,6 +185,7 @@ const LPSTR NAMES_VMM_STATISTICS_CALL[] = { "VMMDLL_MemReadEx", "VMMDLL_MemWrite", "VMMDLL_MemVirt2Phys", + "VMMDLL_MemPrefetchPages", "VMMDLL_PidList", "VMMDLL_PidGetFromName", "VMMDLL_ProcessGetInformation", @@ -210,6 +197,11 @@ const LPSTR NAMES_VMM_STATISTICS_CALL[] = { "VMMDLL_ProcessGetSections", "VMMDLL_ProcessGetEAT", "VMMDLL_ProcessGetIAT", + "VMMDLL_ProcessGetProcAddress", + "VMMDLL_ProcessGetModuleBase", + "VMMDLL_WinGetThunkEAT", + "VMMDLL_WinGetThunkIAT", + "VMMDLL_WinMemCompression_DecompressPage", "PluginManager_List", "PluginManager_Read", "PluginManager_Write", @@ -268,17 +260,17 @@ VOID Statistics_CallToString(_In_opt_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb) LEECHCORE_STATISTICS LeechCoreStatistics = { 0 }; DWORD cbLeechCoreStatistics = sizeof(LEECHCORE_STATISTICS); if(!pb) { - *pcb = 71 * (STATISTICS_ID_MAX + LEECHCORE_STATISTICS_ID_MAX + 6); + *pcb = 79 * (STATISTICS_ID_MAX + LEECHCORE_STATISTICS_ID_MAX + 6); return; } QueryPerformanceFrequency((PLARGE_INTEGER)&qwFreq); o += snprintf( pb + o, cb - o, - "FUNCTION CALL STATISTICS: \n" \ - "VALUES IN DECIMAL, TIME IN MICROSECONDS uS, STATISTICS = %s \n" \ - "FUNCTION CALL NAME CALLS TIME AVG TIME TOTAL\n" \ - "======================================================================\n", + "FUNCTION CALL STATISTICS: \n" \ + "VALUES IN DECIMAL, TIME IN MICROSECONDS uS, STATISTICS = %s \n" \ + "FUNCTION CALL NAME CALLS TIME AVG TIME TOTAL\n" \ + "==============================================================================\n", ctxMain->pvStatistics ? "ENABLED " : "DISABLED" ); // statistics @@ -290,7 +282,7 @@ VOID Statistics_CallToString(_In_opt_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb) o += snprintf( pb + o, cb - o, - "%-32.32s %8i %8i %16lli\n", + "%-40.40s %8i %8i %16lli\n", NAMES_VMM_STATISTICS_CALL[i], (DWORD)pStat->c, (DWORD)(uS / pStat->c), @@ -302,7 +294,7 @@ VOID Statistics_CallToString(_In_opt_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb) o += snprintf( pb + o, cb - o, - "%-32.32s %8i %8i %16lli\n", + "%-40.40s %8i %8i %16lli\n", NAMES_VMM_STATISTICS_CALL[i], 0, 0, 0ULL); } @@ -315,7 +307,7 @@ VOID Statistics_CallToString(_In_opt_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb) o += snprintf( pb + o, cb - o, - "%-32.32s %8i %8i %16lli\n", + "%-40.40s %8i %8i %16lli\n", LEECHCORE_STATISTICS_NAME[i], (DWORD)LeechCoreStatistics.Call[i].c, (DWORD)(uS / LeechCoreStatistics.Call[i].c), @@ -325,11 +317,11 @@ VOID Statistics_CallToString(_In_opt_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb) o += snprintf( pb + o, cb - o, - "%-32.32s %8i %8i %16lli\n", + "%-40.40s %8i %8i %16lli\n", LEECHCORE_STATISTICS_NAME[i], 0, 0, 0ULL); } } } - *pcb = o; + *pcb = o - 1; } diff --git a/vmm/statistics.h b/vmm/statistics.h index 31a0293..298f28c 100644 --- a/vmm/statistics.h +++ b/vmm/statistics.h @@ -7,7 +7,7 @@ #define __STATISTICS_H__ #include "vmm.h" -#define PAGE_STATISTICS_MEM_MAP_MAX_ENTRY 4096 +#define PAGE_STATISTICS_MEM_MAP_MAX_ENTRY 2048 typedef struct tdPageStatistics { QWORD qwAddr; @@ -24,70 +24,80 @@ typedef struct tdPageStatistics { HANDLE hThread; WORD wConsoleCursorPosition; QWORD qwTickCountStart; - QWORD qwAddrBase; QWORD MemMapIdx; - QWORD MemMapPrintCommitIdx; - QWORD MemMapPrintCommitPages; - DWORD MemMap[PAGE_STATISTICS_MEM_MAP_MAX_ENTRY]; + QWORD MemMapPrintIdx; + struct { + QWORD qwAddrBase; + DWORD cPages; + } MemMap[PAGE_STATISTICS_MEM_MAP_MAX_ENTRY]; } i; } PAGE_STATISTICS, *PPAGE_STATISTICS; /* * Initialize the page statistics. This will also start displaying the page statistics * on the screen asynchronously. PageStatClose must be called to stop this. -* -- ps = ptr to the PAGE_STATISTICS struct to initialize. +* -- ps = ptr to NULL pPageStat PageStatInitialize will initialize. Must be free'd with PageStatClose. * -- qwAddrBase = the base address that the statistics will be based upon. * -- qwAddrMax = the maximum address. * -- szAction = the text shown as action. * -- fKMD = is KMD mode. * -- fPageMap = display read memory map when PageStatClose is called. +* -- return */ -VOID PageStatInitialize(_Inout_ PPAGE_STATISTICS ps, _In_ QWORD qwAddrBase, _In_ QWORD qwAddrMax, _In_ LPSTR szAction, _In_ BOOL fKMD, _In_ BOOL fMemMap); +_Success_(return) +BOOL PageStatInitialize(_Out_ PPAGE_STATISTICS *ppPageStat, _In_ QWORD qwAddrBase, _In_ QWORD qwAddrMax, _In_ LPSTR szAction, _In_ BOOL fKMD, _In_ BOOL fMemMap); /* * Do one last update of the on-screen page statistics, display the read memory map if * previously set in PageStatInitialize and stop the on-screen updates. -* -- ps = ptr to the PAGE_STATISTICS struct to stop using. +* -- pPageStat = ptr to the PPAGE_STATISTICS struct to close and free. */ -VOID PageStatClose(_Inout_ PPAGE_STATISTICS ps); +VOID PageStatClose(_In_opt_ PPAGE_STATISTICS *ppPageStat); /* * Update the page statistics with the current address and with successfully and failed * pages. Should not be called before PageStatInitialize and not after PageStatClose. * This function must be used if the memory map should be shown; otherwise it's possible * to alter the PPAGE_STATISTICS struct members directly. -* -- ps = pointer to page statistics struct (optional). +* -- pPageStat = pointer to page statistics struct. * -- qwAddr = new address (after completed operation). * -- cPageSuccessAdd = number of successfully read pages. * -- cPageFailAdd = number of pages that failed. */ -VOID PageStatUpdate(_Inout_opt_ PPAGE_STATISTICS ps, _In_ QWORD qwAddr, _In_ QWORD cPageSuccessAdd, _In_ QWORD cPageFailAdd); +VOID PageStatUpdate(_In_opt_ PPAGE_STATISTICS pPageStat, _In_ QWORD qwAddr, _In_ QWORD cPageSuccessAdd, _In_ QWORD cPageFailAdd); -#define STATISTICS_ID_INITIALIZE 0x00 -#define STATISTICS_ID_VMMDLL_VfsList 0x01 -#define STATISTICS_ID_VMMDLL_VfsRead 0x02 -#define STATISTICS_ID_VMMDLL_VfsWrite 0x03 -#define STATISTICS_ID_VMMDLL_VfsInitializePlugins 0x04 -#define STATISTICS_ID_VMMDLL_MemReadEx 0x05 -#define STATISTICS_ID_VMMDLL_MemWrite 0x06 -#define STATISTICS_ID_VMMDLL_MemVirt2Phys 0x07 -#define STATISTICS_ID_VMMDLL_PidList 0x08 -#define STATISTICS_ID_VMMDLL_PidGetFromName 0x09 -#define STATISTICS_ID_VMMDLL_ProcessGetInformation 0x0a -#define STATISTICS_ID_VMMDLL_ProcessGetMemoryMap 0x0b -#define STATISTICS_ID_VMMDLL_ProcessGetMemoryMapEntry 0x0c -#define STATISTICS_ID_VMMDLL_ProcessGetModuleMap 0x0d -#define STATISTICS_ID_VMMDLL_ProcessGetModuleFromName 0x0e -#define STATISTICS_ID_VMMDLL_ProcessGetDirectories 0x0f -#define STATISTICS_ID_VMMDLL_ProcessGetSections 0x10 -#define STATISTICS_ID_VMMDLL_ProcessGetEAT 0x11 -#define STATISTICS_ID_VMMDLL_ProcessGetIAT 0x12 -#define STATISTICS_ID_PluginManager_List 0x13 -#define STATISTICS_ID_PluginManager_Read 0x14 -#define STATISTICS_ID_PluginManager_Write 0x15 -#define STATISTICS_ID_PluginManager_Notify 0x16 -#define STATISTICS_ID_MAX 0x16 -#define STATISTICS_ID_NOLOG 0xffffffff +// NB! also update statistics.c!NAMES_VMM_STATISTICS_CALL +#define STATISTICS_ID_INITIALIZE 0x00 +#define STATISTICS_ID_VMMDLL_VfsList 0x01 +#define STATISTICS_ID_VMMDLL_VfsRead 0x02 +#define STATISTICS_ID_VMMDLL_VfsWrite 0x03 +#define STATISTICS_ID_VMMDLL_VfsInitializePlugins 0x04 +#define STATISTICS_ID_VMMDLL_MemReadEx 0x05 +#define STATISTICS_ID_VMMDLL_MemWrite 0x06 +#define STATISTICS_ID_VMMDLL_MemVirt2Phys 0x07 +#define STATISTICS_ID_VMMDLL_MemPrefetchPages 0x08 +#define STATISTICS_ID_VMMDLL_PidList 0x09 +#define STATISTICS_ID_VMMDLL_PidGetFromName 0x0a +#define STATISTICS_ID_VMMDLL_ProcessGetInformation 0x0b +#define STATISTICS_ID_VMMDLL_ProcessGetMemoryMap 0x0c +#define STATISTICS_ID_VMMDLL_ProcessGetMemoryMapEntry 0x0d +#define STATISTICS_ID_VMMDLL_ProcessGetModuleMap 0x0e +#define STATISTICS_ID_VMMDLL_ProcessGetModuleFromName 0x0f +#define STATISTICS_ID_VMMDLL_ProcessGetDirectories 0x10 +#define STATISTICS_ID_VMMDLL_ProcessGetSections 0x11 +#define STATISTICS_ID_VMMDLL_ProcessGetEAT 0x12 +#define STATISTICS_ID_VMMDLL_ProcessGetIAT 0x13 +#define STATISTICS_ID_VMMDLL_ProcessGetProcAddress 0x14 +#define STATISTICS_ID_VMMDLL_ProcessGetModuleBase 0x15 +#define STATISTICS_ID_VMMDLL_WinGetThunkEAT 0x16 +#define STATISTICS_ID_VMMDLL_WinGetThunkIAT 0x17 +#define STATISTICS_ID_VMMDLL_WinMemCompression_DecompressPage 0x18 +#define STATISTICS_ID_PluginManager_List 0x19 +#define STATISTICS_ID_PluginManager_Read 0x1a +#define STATISTICS_ID_PluginManager_Write 0x1b +#define STATISTICS_ID_PluginManager_Notify 0x1c +#define STATISTICS_ID_MAX 0x1c +#define STATISTICS_ID_NOLOG 0xffffffff VOID Statistics_CallSetEnabled(_In_ BOOL fEnabled); BOOL Statistics_CallGetEnabled(); diff --git a/vmm/version.h b/vmm/version.h index 43c1b06..5720f62 100644 --- a/vmm/version.h +++ b/vmm/version.h @@ -2,7 +2,7 @@ #define STRINGIZE(s) STRINGIZE2(s) #define VERSION_MAJOR 2 -#define VERSION_MINOR 0 +#define VERSION_MINOR 1 #define VERSION_REVISION 0 #define VERSION_BUILD 0 diff --git a/vmm/vmm.c b/vmm/vmm.c index 1b43acf..30b9181 100644 --- a/vmm/vmm.c +++ b/vmm/vmm.c @@ -610,7 +610,7 @@ VOID VmmCache2Initialize(_In_ WORD wTblTag) * -- pProcess * -- pObPrefetchAddresses */ -VOID VmmCachePrefetch(_In_ PVMM_PROCESS pProcess, _In_opt_ PVMMOB_DATASET pObPrefetchAddresses) +VOID VmmCachePrefetchPages(_In_opt_ PVMM_PROCESS pProcess, _In_opt_ PVMMOB_DATASET pObPrefetchAddresses) { QWORD va; DWORD i, c = 0; @@ -624,7 +624,11 @@ VOID VmmCachePrefetch(_In_ PVMM_PROCESS pProcess, _In_opt_ PVMMOB_DATASET pObPre c++; } } - VmmReadScatterVirtual(pProcess, ppMEMs, c, 0); + if(pProcess) { + VmmReadScatterVirtual(pProcess, ppMEMs, c, 0); + } else { + VmmReadScatterPhysical(ppMEMs, c, 0); + } LocalFree(ppMEMs); } @@ -860,6 +864,7 @@ PVMM_PROCESS VmmProcessCreateEntry(_In_ BOOL fTotalRefresh, _In_ DWORD dwPID, _I if(!pProcess) { goto fail; } InitializeCriticalSectionAndSpinCount(&pProcess->LockUpdate, 4096); memcpy(pProcess->szName, szName, 16); + pProcess->szName[15] = 0; pProcess->dwPID = dwPID; pProcess->dwState = dwState; pProcess->paDTB = paDTB; @@ -1350,7 +1355,7 @@ BOOL VmmReadString_Unicode2Ansi(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Ou BOOL result; WCHAR wsz[0x1000]; if(cch) { sz[0] = 0; } - if(!cch || cch > 0x1000) { return FALSE; } + if((cch < 2) || (cch > 0x1000)) { return FALSE; } result = VmmRead(pProcess, qwVA, (PBYTE)wsz, cch << 1); if(!result) { return FALSE; } for(i = 0; i < cch - 1; i++) { @@ -1394,6 +1399,15 @@ VOID VmmInitializeMemoryModel(_In_ VMM_MEMORYMODEL_TP tp) } } +VOID VmmInitializeFunctions() +{ + HMODULE hNtDll = NULL; + if((hNtDll = LoadLibraryA("ntdll.dll"))) { + ctxVmm->fn.RtlDecompressBuffer = (VMMFN_RtlDecompressBuffer*)GetProcAddress(hNtDll, "RtlDecompressBuffer"); + FreeLibrary(hNtDll); + } +} + BOOL VmmInitialize() { // 1: allocate & initialize @@ -1411,6 +1425,7 @@ BOOL VmmInitialize() // 5: OTHER INIT: VmmObContainer_Initialize(&ctxVmm->ObCEPROCESSCachePrefetch, NULL); InitializeCriticalSection(&ctxVmm->MasterLock); + VmmInitializeFunctions(); return TRUE; fail: VmmClose(); diff --git a/vmm/vmm.h b/vmm/vmm.h index ebc9597..45811e7 100644 --- a/vmm/vmm.h +++ b/vmm/vmm.h @@ -299,8 +299,24 @@ typedef struct tdVMM_KERNELINFO { QWORD vaEntry; QWORD vaPsLoadedModuleList; QWORD vaKDBG; + DWORD dwPidMemCompression; } VMM_KERNELINFO; +typedef NTSTATUS VMMFN_RtlDecompressBuffer( + USHORT CompressionFormat, + PUCHAR UncompressedBuffer, + ULONG UncompressedBufferSize, + PUCHAR CompressedBuffer, + ULONG CompressedBufferSize, + PULONG FinalUncompressedSize +); + +typedef struct tdVMM_DYNAMIC_LOAD_FUNCTIONS { + // functions below may be loaded on startup + // NB! null checks are required before use! + VMMFN_RtlDecompressBuffer *RtlDecompressBuffer; // ntdll.dll!RtlDecompressBuffer +} VMM_DYNAMIC_LOAD_FUNCTIONS; + typedef struct tdVMM_CONTEXT { CRITICAL_SECTION MasterLock; VMMOBCONTAINER PROC; // contains VMM_PROCESS_TABLE @@ -320,6 +336,7 @@ typedef struct tdVMM_CONTEXT { } ThreadProcCache; VMM_STATISTICS stat; VMM_KERNELINFO kernel; + VMM_DYNAMIC_LOAD_FUNCTIONS fn; PVOID pVmmVfsModuleList; VMMOBCONTAINER ObCEPROCESSCachePrefetch; VMM_CACHE_TABLE PHYS; @@ -763,7 +780,7 @@ VOID VmmCacheInvalidate(_In_ QWORD pa); * -- pProcess * -- pObPrefetchAddresses */ -VOID VmmCachePrefetch(_In_ PVMM_PROCESS pProcess, _In_opt_ PVMMOB_DATASET pObPrefetchAddresses); +VOID VmmCachePrefetchPages(_In_opt_ PVMM_PROCESS pProcess, _In_opt_ PVMMOB_DATASET pObPrefetchAddresses); /* * Initialize the memory model specified and discard any previous memory models diff --git a/vmm/vmm.vcxproj b/vmm/vmm.vcxproj index 5c855bb..e091a8b 100644 --- a/vmm/vmm.vcxproj +++ b/vmm/vmm.vcxproj @@ -116,6 +116,7 @@ copy $(ProjectDir)\vmmdll.h $(SolutionDir)\files\ /y + @@ -136,6 +137,7 @@ copy $(ProjectDir)\vmmdll.h $(SolutionDir)\files\ /y + diff --git a/vmm/vmm.vcxproj.filters b/vmm/vmm.vcxproj.filters index 68571dd..e05ca6f 100644 --- a/vmm/vmm.vcxproj.filters +++ b/vmm/vmm.vcxproj.filters @@ -75,6 +75,9 @@ Header Files + + Header Files + @@ -125,6 +128,9 @@ Source Files + + Source Files + diff --git a/vmm/vmmdll.c b/vmm/vmmdll.c index a1f26c5..c946933 100644 --- a/vmm/vmmdll.c +++ b/vmm/vmmdll.c @@ -15,6 +15,7 @@ #include "vmmproc.h" #include "vmmwin.h" #include "vmmvfs.h" +#include "mm_x64_winpaged.h" // ---------------------------------------------------------------------------- // Synchronization macro below. The VMM isn't thread safe so it's important to @@ -22,7 +23,7 @@ // with internal VMM housekeeping functionality. // ---------------------------------------------------------------------------- -#define CALL_SYNCHRONIZED_IMPLEMENTATION_VMM(id, fn) { \ +#define CALL_SYNCHRONIZED_IMPLEMENTATION_VMM(id, fn) { \ QWORD tm; \ BOOL result; \ if(!ctxVmm) { return FALSE; } \ @@ -34,16 +35,16 @@ return result; \ } -#define CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_NTSTATUS(id, fn) { \ - QWORD tm; \ - NTSTATUS nt; \ - if(!ctxVmm) { return ((NTSTATUS)0xC0000001L); } /* UNSUCCESSFUL */ \ - tm = Statistics_CallStart(); \ - VmmLockAcquire(); \ - nt = fn; \ - VmmLockRelease(); \ - Statistics_CallEnd(id, tm); \ - return nt; \ +#define CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_RETURN(id, RetTp, RetValFail, fn) { \ + QWORD tm; \ + RetTp retVal; \ + if(!ctxVmm) { return ((RetTp)RetValFail); } /* UNSUCCESSFUL */ \ + tm = Statistics_CallStart(); \ + VmmLockAcquire(); \ + retVal = fn; \ + VmmLockRelease(); \ + Statistics_CallEnd(id, tm); \ + return retVal; \ } //----------------------------------------------------------------------------- @@ -172,8 +173,8 @@ VOID VmmDll_PrintHelp() " Valid options: , PMEM, FPGA, TOTALMELTDOWN \n" \ " --- \n" \ " = memory dump file name optionally including path.\n" \ - " PMEM = use rekall winpmem 'winpmem_x64.sys' to aquire live memory. \n" \ - " PMEM://c:\\path\\to\\winpmem_x64.sys = path to rekall winpmem driver.\n" \ + " PMEM = use winpmem 'winpmem_64.sys' to acquire live memory. \n" \ + " PMEM://c:\\path\\to\\winpmem_64.sys = path to winpmem driver. \n" \ " --- \n" \ " Below acquisition devices require pcileech.dll and are not built-in: \n" \ " TOTALMELTDOWN = use CVE-2018-1038 (vulnerable windows 7 only) \n" \ @@ -430,15 +431,19 @@ BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset) { - CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_NTSTATUS( + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_RETURN( STATISTICS_ID_VMMDLL_VfsRead, + NTSTATUS, + VMMDLL_STATUS_UNSUCCESSFUL, VmmVfs_Read(wcsFileName, pb, cb, pcbRead, cbOffset)) } NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset) { - CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_NTSTATUS( + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_RETURN( STATISTICS_ID_VMMDLL_VfsWrite, + NTSTATUS, + VMMDLL_STATUS_UNSUCCESSFUL, VmmVfs_Write(wcsFileName, pb, cb, pcbWrite, cbOffset)) } @@ -551,6 +556,38 @@ BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4 return VMMDLL_MemReadEx(dwPID, qwVA, pbPage, 4096, &dwRead, 0) && (dwRead == 4096); } +_Success_(return) +BOOL VMMDLL_MemPrefetchPages_Impl(_In_ DWORD dwPID, _In_reads_(cPrefetchAddresses) PULONG64 pPrefetchAddresses, _In_ DWORD cPrefetchAddresses) +{ + DWORD i; + BOOL result = FALSE; + PVMM_PROCESS pObProcess = NULL; + PVMMOB_DATASET pObPrefetchAddresses = NULL; + if(dwPID != (DWORD)-1) { + pObProcess = VmmProcessGet(dwPID); + if(!pObProcess) { goto fail; } + } + pObPrefetchAddresses = VmmObDataSet_Alloc(TRUE); + if(!pObPrefetchAddresses) { goto fail; } + for(i = 0; i < cPrefetchAddresses; i++) { + VmmObDataSet_Put(pObPrefetchAddresses, pPrefetchAddresses[i] & ~0xfff); + } + VmmCachePrefetchPages(pObProcess, pObPrefetchAddresses); + result = TRUE; +fail: + VmmOb_DECREF(pObPrefetchAddresses); + VmmOb_DECREF(pObProcess); + return result; +} + +_Success_(return) +BOOL VMMDLL_MemPrefetchPages(_In_ DWORD dwPID, _In_reads_(cPrefetchAddresses) PULONG64 pPrefetchAddresses, _In_ DWORD cPrefetchAddresses) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_MemPrefetchPages, + VMMDLL_MemPrefetchPages_Impl(dwPID, pPrefetchAddresses, cPrefetchAddresses)) +} + _Success_(return) BOOL VMMDLL_MemWrite_Impl(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _In_ PBYTE pb, _In_ DWORD cb) { @@ -873,7 +910,7 @@ BOOL VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl( i = PE_EatGetNumberOf(pObProcess, pModule->BaseAddress); if(!pEAT) { *pcData = i; goto success; } if(cData < i) { goto fail; } - VmmWin_PE_LoadEAT_DisplayBuffer(pObProcess, pModule, (PVMMPROC_WINDOWS_EAT_ENTRY)pEAT, &cData); + VmmWin_PE_LoadEAT_DisplayBuffer(pObProcess, pModule, (PVMMPROC_WINDOWS_EAT_ENTRY)pEAT, cData, &cData); *pcData = cData; goto success; } @@ -882,7 +919,7 @@ BOOL VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl( i = PE_IatGetNumberOf(pObProcess, pModule->BaseAddress); if(!pIAT) { *pcData = i; goto success; } if(cData < i) { goto fail; } - VmmWin_PE_LoadIAT_DisplayBuffer(pObProcess, pModule, (PVMMWIN_IAT_ENTRY)pIAT, &cData); + VmmWin_PE_LoadIAT_DisplayBuffer(pObProcess, pModule, (PVMMWIN_IAT_ENTRY)pIAT, cData, &cData); *pcData = cData; goto success; } @@ -928,6 +965,107 @@ BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMD VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl(dwPID, szModule, cData, pcData, NULL, NULL, NULL, pData, FALSE, FALSE, FALSE, TRUE)) } +ULONG64 VMMDLL_ProcessGetProcAddress_Impl(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szFunctionName) +{ + QWORD vaFn = 0; + VMMDLL_MODULEMAP_ENTRY oModuleEntry = { 0 }; + PVMM_PROCESS pObProcess = NULL; + pObProcess = VmmProcessGet(dwPID); + if(!pObProcess) { return 0; } + if(VMMDLL_ProcessGetModuleFromName_Impl(dwPID, szModuleName, &oModuleEntry)) { + vaFn = PE_GetProcAddress(pObProcess, oModuleEntry.BaseAddress, szFunctionName); + } + VmmOb_DECREF(pObProcess); + return vaFn; +} + +ULONG64 VMMDLL_ProcessGetProcAddress(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szFunctionName) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_RETURN( + STATISTICS_ID_VMMDLL_ProcessGetIAT, + ULONG64, + 0, + VMMDLL_ProcessGetProcAddress_Impl(dwPID, szModuleName, szFunctionName)) +} + +ULONG64 VMMDLL_ProcessGetModuleBase_Impl(_In_ DWORD dwPID, _In_ LPSTR szModuleName) +{ + QWORD vaModuleBase = 0; + VMMDLL_MODULEMAP_ENTRY oModuleEntry = { 0 }; + PVMM_PROCESS pObProcess = NULL; + pObProcess = VmmProcessGet(dwPID); + if(!pObProcess) { return 0; } + if(VMMDLL_ProcessGetModuleFromName_Impl(dwPID, szModuleName, &oModuleEntry)) { + vaModuleBase = oModuleEntry.BaseAddress; + } + VmmOb_DECREF(pObProcess); + return vaModuleBase; +} + +ULONG64 VMMDLL_ProcessGetModuleBase(_In_ DWORD dwPID, _In_ LPSTR szModuleName) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_RETURN( + STATISTICS_ID_VMMDLL_ProcessGetModuleBase, + ULONG64, + 0, + VMMDLL_ProcessGetModuleBase_Impl(dwPID, szModuleName)) +} + +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoEAT_Impl(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szExportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_EAT pThunkInfoEAT) +{ + BOOL result; + VMMDLL_MODULEMAP_ENTRY oModuleEntry = { 0 }; + PVMM_PROCESS pObProcess = NULL; + pObProcess = VmmProcessGet(dwPID); + if(!pObProcess) { return 0; } + if(VMMDLL_ProcessGetModuleFromName_Impl(dwPID, szModuleName, &oModuleEntry)) { + result = PE_GetThunkInfoEAT(pObProcess, oModuleEntry.BaseAddress, szExportFunctionName, (PPE_THUNKINFO_EAT)pThunkInfoEAT); + } + VmmOb_DECREF(pObProcess); + return result; +} + +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoEAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szExportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_EAT pThunkInfoEAT) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_WinGetThunkEAT, + VMMDLL_WinGetThunkInfoEAT_Impl(dwPID, szModuleName, szExportFunctionName, pThunkInfoEAT)) +} + +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoIAT_Impl(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_IAT pThunkInfoIAT) +{ + BOOL result = FALSE; + VMMDLL_MODULEMAP_ENTRY oModuleEntry = { 0 }; + PVMM_PROCESS pObProcess = NULL; + if(sizeof(VMMDLL_WIN_THUNKINFO_IAT) != sizeof(PE_THUNKINFO_IAT)) { return FALSE; } + pObProcess = VmmProcessGet(dwPID); + if(!pObProcess) { return 0; } + if(VMMDLL_ProcessGetModuleFromName_Impl(dwPID, szModuleName, &oModuleEntry)) { + result = PE_GetThunkInfoIAT(pObProcess, oModuleEntry.BaseAddress, szImportModuleName, szImportFunctionName, (PPE_THUNKINFO_IAT)pThunkInfoIAT); + } + VmmOb_DECREF(pObProcess); + return result; +} + +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoIAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_IAT pThunkInfoIAT) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_WinGetThunkIAT, + VMMDLL_WinGetThunkInfoIAT_Impl(dwPID, szModuleName, szImportModuleName, szImportFunctionName, pThunkInfoIAT)) +} + +_Success_(return) +BOOL VMMDLL_WinMemCompression_DecompressPage(_In_ ULONG64 vaCompressedData, _In_opt_ DWORD cbCompressedData, _Out_writes_(4096) PBYTE pbDecompressedPage, _Out_opt_ PDWORD pcbCompressedData) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_WinMemCompression_DecompressPage, + MmX64WinPaged_MemCompression_DecompressPage(vaCompressedData, cbCompressedData, pbDecompressedPage, pcbCompressedData)) +} + _Success_(return) BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_opt_ LPSTR sz, _Out_ PDWORD pcsz) { diff --git a/vmm/vmmdll.def b/vmm/vmmdll.def index 502d011..5e61a51 100644 --- a/vmm/vmmdll.def +++ b/vmm/vmmdll.def @@ -23,6 +23,7 @@ EXPORTS VMMDLL_MemReadPage VMMDLL_MemRead VMMDLL_MemReadEx + VMMDLL_MemPrefetchPages VMMDLL_MemWrite VMMDLL_MemVirt2Phys @@ -38,5 +39,11 @@ EXPORTS VMMDLL_ProcessGetSections VMMDLL_ProcessGetEAT VMMDLL_ProcessGetIAT + VMMDLL_ProcessGetProcAddress + VMMDLL_ProcessGetModuleBase + VMMDLL_WinGetThunkInfoIAT + VMMDLL_WinGetThunkInfoEAT + + VMMDLL_WinMemCompression_DecompressPage VMMDLL_UtilFillHexAscii diff --git a/vmm/vmmdll.h b/vmm/vmmdll.h index 353159a..42befa8 100644 --- a/vmm/vmmdll.h +++ b/vmm/vmmdll.h @@ -4,7 +4,7 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // -// Header Version: 2.0 +// Header Version: 2.1 // #include @@ -26,13 +26,15 @@ extern "C" { * about the parameters please see github wiki for Memory Process File System * and LeechCore. THIS IS THE PREFERED WAY OF INITIALIZING VMM.DLL * Important parameters are: -* -vdll = show printf style outputs) +* -printf = show printf style outputs) * -v -vv -vvv = extra verbosity levels) * -device = device as on format for LeechCore - please see leechcore.h or * Github documentation for additional information. Some values * are: , fpga, usb3380, hvsavedstate, totalmeltdown, pmem * -remote = remote LeechCore instance - please see leechcore.h or Github * documentation for additional information. +* -norefresh = disable background refreshes (even if backing memory is +* volatile memory). * -- argc * -- argv * -- return = success/fail @@ -289,7 +291,7 @@ typedef struct tdVMMDLL_PLUGIN_REGINFO { * -- ppMEMs = array of scatter read headers. * -- cpMEMs = count of ppDMAs. * -- pcpDMAsRead = optional count of number of successfully read ppDMAs. -* -- flags = optional flags as given by VMM_FLAG_* +* -- flags = optional flags as given by VMMDLL_FLAG_* * -- return = the number of successfully read items. */ DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); @@ -322,13 +324,25 @@ BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DW * -- pb * -- cb * -- pcbRead -* -- flags = flags as in VMM_FLAG_* +* -- flags = flags as in VMMDLL_FLAG_* * -- return = success/fail. NB! reads may report as success even if 0 bytes are * read - it's recommended to verify pcbReadOpt parameter. */ _Success_(return) BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); +/* +* Prefetch a number of addresses (specified in the pA array) into the memory +* cache. This function is to be used to batch larger known reads into local +* cache before making multiple smaller reads - which will then happen from +* the cache. Function exists for performance reasons. +* -- dwPID = PID of target process, (DWORD)-1 for physical memory. +* -- pPrefetchAddresses = array of addresses to read into cache. +* -- cPrefetchAddresses +*/ +_Success_(return) +BOOL VMMDLL_MemPrefetchPages(_In_ DWORD dwPID, _In_reads_(cPrefetchAddresses) PULONG64 pPrefetchAddresses, _In_ DWORD cPrefetchAddresses); + /* * Write a contigious arbitrary amount of memory. Please note some virtual memory * such as pages of executables (such as DLLs) may be shared between different @@ -527,6 +541,87 @@ BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMD _Success_(return) BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +/* +* Retrieve the virtual address of a given function inside a process/module. +* -- dwPID +* -- szModuleName +* -- szFunctionName +* -- return = virtual address of function, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetProcAddress(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szFunctionName); + +/* +* Retrieve the base address of a given module. +* -- dwPID +* -- szModuleName +* -- return = virtual address of module base, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetModuleBase(_In_ DWORD dwPID, _In_ LPSTR szModuleName); + + + +//----------------------------------------------------------------------------- +// WINDOWS SPECIFIC UTILITY FUNCTIONS BELOW: +//----------------------------------------------------------------------------- + +typedef struct tdVMMDLL_WIN_THUNKINFO_IAT { + BOOL fValid; + BOOL f32; // if TRUE fn is a 32-bit/4-byte entry, otherwise 64-bit/8-byte entry. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaFunction; // value if import address table 'thunk' == address of imported function. + ULONG64 vaNameModule; // address of name string for imported module. + ULONG64 vaNameFunction; // address of name string for imported function. +} VMMDLL_WIN_THUNKINFO_IAT, *PVMMDLL_WIN_THUNKINFO_IAT; + +typedef struct tdVMMDLL_WIN_THUNKINFO_EAT { + BOOL fValid; + DWORD valueThunk; // value of export address table 'thunk'. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaNameFunction; // address of name string for exported function. + ULONG64 vaFunction; // address of exported function (module base + value parameter). +} VMMDLL_WIN_THUNKINFO_EAT, *PVMMDLL_WIN_THUNKINFO_EAT; + +/* +* Retrieve information about the import address table IAT thunk for an imported +* function. This includes the virtual address of the IAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- szImportModuleName +* -- szImportFunctionName +* -- pThunkIAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoIAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_IAT pThunkInfoIAT); + +/* +* Retrieve information about the export address table EAT thunk for an exported +* function. This includes the virtual address of the EAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- pThunkEAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoEAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szExportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_EAT pThunkInfoEAT); + +/* +* Decompress compressed memory page stored in the MemCompression process. +* -- vaCompressedData = virtual address in 'MemCompression' to decompress. +* -- cbCompressedData = length of compressed data in 'MemCompression' to decompress (or zero for auto-detect). +* -- pbDecompressedPage +* -- pcbCompressedData = optional ptr to receive length of compressed buffer. +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinMemCompression_DecompressPage( + _In_ ULONG64 vaCompressedData, + _In_opt_ DWORD cbCompressedData, + _Out_writes_(4096) PBYTE pbDecompressedPage, + _Out_opt_ PDWORD pcbCompressedData +); //----------------------------------------------------------------------------- diff --git a/vmm/vmmproc.c b/vmm/vmmproc.c index 3979ec2..6fa488a 100644 --- a/vmm/vmmproc.c +++ b/vmm/vmmproc.c @@ -230,11 +230,9 @@ BOOL VmmProcPHYS_ScanForKernel(_Out_ PQWORD ppaPML4, _In_ QWORD paBase, _In_ QWO LEECHCORE_PAGESTAT_MINIMAL PageStatMinimal; BOOL result; // initialize / allocate memory - pbBuffer8M = LocalAlloc(0, 0x800000); - pPageStat = (PPAGE_STATISTICS)LocalAlloc(LMEM_ZEROINIT, sizeof(PAGE_STATISTICS)); - if(!pbBuffer8M || !pPageStat) { goto fail; } paCurrent = paBase; - PageStatInitialize(pPageStat, paCurrent, paMax, szDescription, FALSE, FALSE); + if(!(pbBuffer8M = LocalAlloc(0, 0x800000))) { goto fail; } + if(!PageStatInitialize(&pPageStat, paCurrent, paMax, szDescription, FALSE, FALSE)) { goto fail; } PageStatMinimal.h = (HANDLE)pPageStat; PageStatMinimal.pfnPageStatUpdate = PageStatUpdate; // loop kmd-find @@ -247,8 +245,7 @@ BOOL VmmProcPHYS_ScanForKernel(_Out_ PQWORD ppaPML4, _In_ QWORD paBase, _In_ QWO result = VmmProcPHYS_VerifyWindowsEPROCESS(pbBuffer8M, 0x00800000, o + i, ppaPML4); if(result) { pPageStat->szAction = "Windows System PageDirectoryBase/PML4 located"; - PageStatClose(pPageStat); - LocalFree(pPageStat); + PageStatClose(&pPageStat); LocalFree(pbBuffer8M); return TRUE; } @@ -257,8 +254,7 @@ BOOL VmmProcPHYS_ScanForKernel(_Out_ PQWORD ppaPML4, _In_ QWORD paBase, _In_ QWO } } fail: - if(pPageStat) { PageStatClose(pPageStat); } - LocalFree(pPageStat); + PageStatClose(&pPageStat); LocalFree(pbBuffer8M); *ppaPML4 = 0; return FALSE; diff --git a/vmm/vmmwin.c b/vmm/vmmwin.c index e5d4553..2155c82 100644 --- a/vmm/vmmwin.c +++ b/vmm/vmmwin.c @@ -126,8 +126,8 @@ VOID VmmWin_PE_DIRECTORY_DisplayBuffer( } } - -VOID VmmWin_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_opt_(*pcEATs) PVMMPROC_WINDOWS_EAT_ENTRY pEATs, _Inout_ PDWORD pcEATs) +_Success_(return) +BOOL VmmWin_PE_LoadEAT_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_opt_(cEATs) PVMMPROC_WINDOWS_EAT_ENTRY pEATs, _In_ DWORD cEATs, _Out_ PDWORD pcEATs) { BYTE pbModuleHeader[0x1000] = { 0 }; PIMAGE_NT_HEADERS64 ntHeader64; @@ -138,10 +138,9 @@ VOID VmmWin_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM QWORD i, oNameOrdinal, ooName, oName, oFunction, wOrdinalFnIdx; DWORD vaFunctionOffset; BOOL fHdr32; - DWORD cEATs = *pcEATs; *pcEATs = 0; // load both 32/64 bit ntHeader (only one will be valid) - if(!(ntHeader64 = VmmWin_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { goto cleanup; } + if(!(ntHeader64 = VmmWin_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { goto fail; } ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; // Load Export Address Table (EAT) oExportDirectory = fHdr32 ? @@ -150,9 +149,9 @@ VOID VmmWin_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM cbExportDirectory = fHdr32 ? ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size : ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; - if(!oExportDirectory || !cbExportDirectory || cbExportDirectory > 0x01000000) { goto cleanup; } - if(!(pbExportDirectory = LocalAlloc(0, cbExportDirectory))) { goto cleanup; } - if(!VmmRead(pProcess, pModule->BaseAddress + oExportDirectory, pbExportDirectory, (DWORD)cbExportDirectory)) { goto cleanup; } + if(!oExportDirectory || !cbExportDirectory || cbExportDirectory > 0x01000000) { goto fail; } + if(!(pbExportDirectory = LocalAlloc(0, cbExportDirectory))) { goto fail; } + if(!VmmRead(pProcess, pModule->BaseAddress + oExportDirectory, pbExportDirectory, (DWORD)cbExportDirectory)) { goto fail; } // Walk exported functions pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)pbExportDirectory; for(i = 0; i < pExportDirectory->NumberOfNames && i < cEATs; i++) { @@ -175,11 +174,14 @@ VOID VmmWin_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM strncpy_s(pEATs[i].szFunction, 40, (LPSTR)(pbExportDirectory - oExportDirectory + oName), _TRUNCATE); } *pcEATs = (DWORD)i; -cleanup: LocalFree(pbExportDirectory); + return TRUE; +fail: + LocalFree(pbExportDirectory); + return FALSE; } -VOID VmmWin_PE_LoadIAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_(*pcIATs) PVMMWIN_IAT_ENTRY pIATs, _Inout_ PDWORD pcIATs) +VOID VmmWin_PE_LoadIAT_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_(*pcIATs) PVMMWIN_IAT_ENTRY pIATs, _In_ DWORD cIATs, _Out_ PDWORD pcIATs) { BYTE pbModuleHeader[0x1000] = { 0 }; PIMAGE_NT_HEADERS64 ntHeader64; @@ -191,7 +193,7 @@ VOID VmmWin_PE_LoadIAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM PBYTE pbModule; DWORD cbModule, cbRead; BOOL fHdr32, fFnName; - DWORD c, j, cIATs = *pcIATs; + DWORD c, j; *pcIATs = 0; // Load the module if(pModule->SizeOfImage > 0x02000000) { return; } @@ -325,7 +327,7 @@ VOID VmmWin_ScanLdrModules64(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ DWORD iModuleLdr; // prefetch existing addresses (if any) & allocate new vaModuleLdr DataSet pObDataSet_vaModuleLdr = VmmObContainer_GetOb(&pProcess->pObProcessPersistent->ObCLdrModulesCachePrefetch64); - VmmCachePrefetch(pProcess, pObDataSet_vaModuleLdr); + VmmCachePrefetchPages(pProcess, pObDataSet_vaModuleLdr); VmmOb_DECREF(pObDataSet_vaModuleLdr); pObDataSet_vaModuleLdr = VmmObDataSet_Alloc(TRUE); if(!pObDataSet_vaModuleLdr) { goto fail; } @@ -453,7 +455,7 @@ BOOL VmmWin_ScanLdrModules32(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ DWORD iModuleLdr; // prefetch existing addresses (if any) & allocate new vaModuleLdr DataSet pObDataSet_vaModuleLdr = VmmObContainer_GetOb(&pProcess->pObProcessPersistent->ObCLdrModulesCachePrefetch32); - VmmCachePrefetch(pProcess, pObDataSet_vaModuleLdr); + VmmCachePrefetchPages(pProcess, pObDataSet_vaModuleLdr); VmmOb_DECREF(pObDataSet_vaModuleLdr); pObDataSet_vaModuleLdr = VmmObDataSet_Alloc(TRUE); if(!pObDataSet_vaModuleLdr) { goto fail; } @@ -955,15 +957,15 @@ VOID VmmWin_OffsetLocatorEPROCESS64(_In_ PVMM_PROCESS pSystemProcess) // find offset for PEB (in EPROCESS) for(i = 0x300, f = FALSE; i < 0x480; i += 8) { if(*(PQWORD)(pb0 + i)) { continue; } - vaPEB = *(PQWORD)(pb1 + i); - if(!vaPEB || (vaPEB & 0xffff800000000fff)) { continue; } - // Verify potential PEB - if(!VmmVirt2PhysEx(*(PQWORD)(pb1 + pOffsetEPROCESS->DTB), TRUE, vaPEB, &paPEB)) { continue; } - if(!VmmReadPhysicalPage(paPEB, pbPage)) { continue; } - if(*(PWORD)pbPage == 0x5a4d) { continue; } // MZ header -> likely entry point or something not PEB ... - pOffsetEPROCESS->PEB = i; - f = TRUE; - break; +vaPEB = *(PQWORD)(pb1 + i); +if(!vaPEB || (vaPEB & 0xffff800000000fff)) { continue; } +// Verify potential PEB +if(!VmmVirt2PhysEx(*(PQWORD)(pb1 + pOffsetEPROCESS->DTB), TRUE, vaPEB, &paPEB)) { continue; } +if(!VmmReadPhysicalPage(paPEB, pbPage)) { continue; } +if(*(PWORD)pbPage == 0x5a4d) { continue; } // MZ header -> likely entry point or something not PEB ... +pOffsetEPROCESS->PEB = i; +f = TRUE; +break; } if(!f) { return; } // find "optional" offset for user cr3/pml4 (post meltdown only) @@ -1016,7 +1018,7 @@ BOOL VmmWin_EnumerateEPROCESS64(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot PVMM_PROCESS pObProcess = NULL; QWORD vaSystemEPROCESS, vaEPROCESS, cNewProcessCollision = 0; DWORD iProc = 0; - BOOL fShowTerminated; + BOOL fShowTerminated, fUser; PVMM_WIN_EPROCESS_OFFSET pOffsetEPROCESS = &ctxVmm->kernel.OffsetEPROCESS; PVMMOB_DATASET pObSetAddressEPROCESS = NULL; fShowTerminated = ctxVmm->flags & VMM_FLAG_PROCESS_SHOW_TERMINATED; @@ -1040,7 +1042,7 @@ BOOL VmmWin_EnumerateEPROCESS64(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot pqwPEB = (PQWORD)(pb + pOffsetEPROCESS->PEB); // prefetch pages into cache (if any) pObSetAddressEPROCESS = VmmObContainer_GetOb(&ctxVmm->ObCEPROCESSCachePrefetch); - VmmCachePrefetch(pSystemProcess, pObSetAddressEPROCESS); + VmmCachePrefetchPages(pSystemProcess, pObSetAddressEPROCESS); VmmOb_DECREF(pObSetAddressEPROCESS); // initialize address set if(!(pObSetAddressEPROCESS = VmmObDataSet_Alloc(TRUE))) { return FALSE; } @@ -1054,6 +1056,9 @@ BOOL VmmWin_EnumerateEPROCESS64(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot VmmOb_DECREF(pObProcess); pObProcess = NULL; if(*pqwDTB && *(PQWORD)szName && (fShowTerminated || !*pdwState)) { + fUser = + !((*pdwPID == 4) || ((*pdwState == 0) && (*pqwPEB == 0))) || + (*(PQWORD)(szName + 0x00) == 0x72706d6f436d654d) && (*(PDWORD)(szName + 0x08) == 0x69737365); // MemCompression "process" pObProcess = VmmProcessCreateEntry( fTotalRefresh, *pdwPID, @@ -1061,7 +1066,7 @@ BOOL VmmWin_EnumerateEPROCESS64(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot ~0xfff & *pqwDTB, pOffsetEPROCESS->DTB_User ? (~0xfff & *pqwDTB_User) : 0, szName, - !((*pdwPID == 4) || ((*pdwState == 0) && (*pqwPEB == 0)))); + fUser); if(!pObProcess) { vmmprintfv("VMM: WARNING: PID '%i' already exists.\n", *pdwPID); if(++cNewProcessCollision >= 8) { @@ -1233,7 +1238,7 @@ BOOL VmmWin_EnumerateEPROCESS32(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot PVMM_PROCESS pObProcess = NULL; DWORD vaSystemEPROCESS, vaEPROCESS, cPID = 0, cNewProcessCollision = 0; DWORD iProc = 0; - BOOL fShowTerminated; + BOOL fShowTerminated, fUser; PVMM_WIN_EPROCESS_OFFSET pOffsetEPROCESS = &ctxVmm->kernel.OffsetEPROCESS; PVMMOB_DATASET pObSetAddressEPROCESS = NULL; fShowTerminated = ctxVmm->flags & VMM_FLAG_PROCESS_SHOW_TERMINATED; @@ -1257,7 +1262,7 @@ BOOL VmmWin_EnumerateEPROCESS32(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot pdwPEB = (PDWORD)(pb + pOffsetEPROCESS->PEB); // prefetch pages into cache (if any) pObSetAddressEPROCESS = VmmObContainer_GetOb(&ctxVmm->ObCEPROCESSCachePrefetch); - VmmCachePrefetch(pSystemProcess, pObSetAddressEPROCESS); + VmmCachePrefetchPages(pSystemProcess, pObSetAddressEPROCESS); VmmOb_DECREF(pObSetAddressEPROCESS); // initialize address set if(!(pObSetAddressEPROCESS = VmmObDataSet_Alloc(TRUE))) { return FALSE; } @@ -1272,6 +1277,9 @@ BOOL VmmWin_EnumerateEPROCESS32(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot VmmOb_DECREF(pObProcess); pObProcess = NULL; if(*pdwDTB && *(PQWORD)szName && (fShowTerminated || !*pdwState)) { + fUser = + !((*pdwPID == 4) || ((*pdwState == 0) && (*pdwPEB == 0))) || + ((*(PQWORD)(szName + 0x00) == 0x72706d6f436d654d) && (*(PDWORD)(szName + 0x08) == 0x69737365)); // MemCompression "process" pObProcess = VmmProcessCreateEntry( fTotalRefresh, *pdwPID, @@ -1279,7 +1287,7 @@ BOOL VmmWin_EnumerateEPROCESS32(_In_ PVMM_PROCESS pSystemProcess, _In_ BOOL fTot *pdwDTB & 0xffffffe0, pOffsetEPROCESS->DTB_User ? (~0xfff & *pdwDTB_User) : 0, szName, - !((*pdwPID == 4) || ((*pdwState == 0) && (*pdwPEB == 0)))); + fUser); } if(pObProcess) { pObProcess->os.win.vaEPROCESS = vaEPROCESS; diff --git a/vmm/vmmwin.h b/vmm/vmmwin.h index fda2a40..7d45e68 100644 --- a/vmm/vmmwin.h +++ b/vmm/vmmwin.h @@ -36,9 +36,12 @@ VOID VmmWin_PE_SetSizeSectionIATEAT_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _I * -- pProcess * -- pModule * -- pEATs -* -- pcEATs = number max items of pEATs on entry, number of actual items of pEATs on exit +* -- cEATs +* -- pcEATs = number of actual items of pEATs written. +* -- return */ -VOID VmmWin_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_opt_(*pcEATs) PVMMPROC_WINDOWS_EAT_ENTRY pEATs, _Inout_ PDWORD pcEATs); +_Success_(return) +BOOL VmmWin_PE_LoadEAT_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_opt_(cEATs) PVMMPROC_WINDOWS_EAT_ENTRY pEATs, _In_ DWORD cEATs, _Out_ PDWORD pcEATs); /* * Walk the import address table (IAT) from a given pProcess and store it in the @@ -46,9 +49,10 @@ VOID VmmWin_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM * -- pProcess * -- pModule * -- pIATs -* -- pcIATs = number max items of pIATs on entry, number of actual items of pIATs on exit +* -- cIATs +* -- pcIATs = number of actual items of pIATs on exit */ -VOID VmmWin_PE_LoadIAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_(*pcIATs) PVMMWIN_IAT_ENTRY pIATs, _Inout_ PDWORD pcIATs); +VOID VmmWin_PE_LoadIAT_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_writes_(*pcIATs) PVMMWIN_IAT_ENTRY pIATs, _In_ DWORD cIATs, _Out_ PDWORD pcIATs); /* * Fill the pbDisplayBuffer with a human readable version of the data directories. diff --git a/vmm/vmmwininit.c b/vmm/vmmwininit.c index ac32be3..eface21 100644 --- a/vmm/vmmwininit.c +++ b/vmm/vmmwininit.c @@ -456,7 +456,7 @@ fail: */ BOOL VmmWinInit_TryInitialize(_In_opt_ QWORD paDTBOpt) { - PVMM_PROCESS pObSystemProcess = NULL; + PVMM_PROCESS pObSystemProcess = NULL, pObProcess = NULL; QWORD vaPsInitialSystemProcess, vaSystemEPROCESS; // Fetch Directory Base (DTB (PML4)) and initialize Memory Model. if(paDTBOpt) { @@ -503,6 +503,14 @@ BOOL VmmWinInit_TryInitialize(_In_opt_ QWORD paDTBOpt) // Optionally fetch PsLoadedModuleList / KDBG VmmWinInit_FindPsLoadedModuleListKDBG(pObSystemProcess); VmmOb_DECREF(pObSystemProcess); + // Optionally retrieve PID of MemCompression process + while((pObProcess = VmmProcessGetNext(pObProcess))) { + if(memcmp("MemCompression", pObProcess->szName, 15)) { continue; } + ctxVmm->kernel.dwPidMemCompression = pObProcess->dwPID; + VmmOb_DECREF(pObProcess); + pObProcess = NULL; + break; + } return TRUE; fail: VmmInitializeMemoryModel(VMM_MEMORYMODEL_NA); // clean memory model diff --git a/vmm_example/leechcore.h b/vmm_example/leechcore.h index a2da3e6..46af16a 100644 --- a/vmm_example/leechcore.h +++ b/vmm_example/leechcore.h @@ -68,13 +68,14 @@ // placed in same directory as the executable file. // // PMEM : load the rekall winpmem driver into the kernel and connect to it -// to acquire memory. The driver file 'winpmem_x64.sys' is found in -// the Rekall directory after most recent version has been installed. -// Copy 'winpmem_x64.sys' to the directory of leechcore.dll and run -// executable as elevated admin using syntax below: +// to acquire memory. The signed driver `.sys` file may be found at: +// https://github.com/Velocidex/c-aff4/tree/master/tools/pmem/resources/winpmem +// Download the driver file `att_winpmem_64.sys` and copy it to the +// directory of leechcore.dll and run executable as elevated admin +// using syntax below: // Syntax: -// PMEM (use winpmem_x64.sys in directory of executable) -// PMEM:// +// PMEM (use att_winpmem_64.sys in directory of executable) +// PMEM:// // // TOTALMELTDOWN : read/write - requires a Windows 7 system vulnerable to the // "Total Meltdown" vulnerability - CVE-2018-1038. @@ -110,7 +111,7 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // -// Header Version: 1.0 +// Header Version: 1.1.0 // #ifndef __LEECHCORE_H__ #define __LEECHCORE_H__ @@ -151,6 +152,7 @@ typedef long long unsigned int QWORD, *PQWORD, ULONG64, *PULONG64; #define _Printf_format_string_ #define _Inout_updates_bytes_(x) #define _In_reads_(cbDataIn) +#define _Out_writes_opt_(x) #define _Success_(return) #endif /* LINUX */ @@ -227,7 +229,10 @@ typedef struct tdLEECHCORE_PAGESTAT_MINIMAL { } LEECHCORE_PAGESTAT_MINIMAL, *PLEECHCORE_PAGESTAT_MINIMAL; /* -* Open a connection to the target device. +* Open a connection to the target device. The LeechCore initialization may fail +* if the underlying device cannot be opened or if the LeechCore is already +* initialized. If already initialized please connect with device EXISTING or +* call LeechCore_Close() before opening a new device. * -- pInformation * -- result */ @@ -455,9 +460,9 @@ DLLEXPORT BOOL LeechCore_CommandData( _In_ ULONG64 fOption, _In_reads_(cbDataIn) PBYTE pbDataIn, _In_ DWORD cbDataIn, - _Out_writes_(cbDataOut) PBYTE pbDataOut, + _Out_writes_opt_(cbDataOut) PBYTE pbDataOut, _In_ DWORD cbDataOut, - _Out_ PDWORD pcbDataOut + _Out_opt_ PDWORD pcbDataOut ); #ifdef __cplusplus diff --git a/vmm_example/vmmdll.h b/vmm_example/vmmdll.h index 353159a..42befa8 100644 --- a/vmm_example/vmmdll.h +++ b/vmm_example/vmmdll.h @@ -4,7 +4,7 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // -// Header Version: 2.0 +// Header Version: 2.1 // #include @@ -26,13 +26,15 @@ extern "C" { * about the parameters please see github wiki for Memory Process File System * and LeechCore. THIS IS THE PREFERED WAY OF INITIALIZING VMM.DLL * Important parameters are: -* -vdll = show printf style outputs) +* -printf = show printf style outputs) * -v -vv -vvv = extra verbosity levels) * -device = device as on format for LeechCore - please see leechcore.h or * Github documentation for additional information. Some values * are: , fpga, usb3380, hvsavedstate, totalmeltdown, pmem * -remote = remote LeechCore instance - please see leechcore.h or Github * documentation for additional information. +* -norefresh = disable background refreshes (even if backing memory is +* volatile memory). * -- argc * -- argv * -- return = success/fail @@ -289,7 +291,7 @@ typedef struct tdVMMDLL_PLUGIN_REGINFO { * -- ppMEMs = array of scatter read headers. * -- cpMEMs = count of ppDMAs. * -- pcpDMAsRead = optional count of number of successfully read ppDMAs. -* -- flags = optional flags as given by VMM_FLAG_* +* -- flags = optional flags as given by VMMDLL_FLAG_* * -- return = the number of successfully read items. */ DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); @@ -322,13 +324,25 @@ BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DW * -- pb * -- cb * -- pcbRead -* -- flags = flags as in VMM_FLAG_* +* -- flags = flags as in VMMDLL_FLAG_* * -- return = success/fail. NB! reads may report as success even if 0 bytes are * read - it's recommended to verify pcbReadOpt parameter. */ _Success_(return) BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); +/* +* Prefetch a number of addresses (specified in the pA array) into the memory +* cache. This function is to be used to batch larger known reads into local +* cache before making multiple smaller reads - which will then happen from +* the cache. Function exists for performance reasons. +* -- dwPID = PID of target process, (DWORD)-1 for physical memory. +* -- pPrefetchAddresses = array of addresses to read into cache. +* -- cPrefetchAddresses +*/ +_Success_(return) +BOOL VMMDLL_MemPrefetchPages(_In_ DWORD dwPID, _In_reads_(cPrefetchAddresses) PULONG64 pPrefetchAddresses, _In_ DWORD cPrefetchAddresses); + /* * Write a contigious arbitrary amount of memory. Please note some virtual memory * such as pages of executables (such as DLLs) may be shared between different @@ -527,6 +541,87 @@ BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMD _Success_(return) BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +/* +* Retrieve the virtual address of a given function inside a process/module. +* -- dwPID +* -- szModuleName +* -- szFunctionName +* -- return = virtual address of function, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetProcAddress(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szFunctionName); + +/* +* Retrieve the base address of a given module. +* -- dwPID +* -- szModuleName +* -- return = virtual address of module base, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetModuleBase(_In_ DWORD dwPID, _In_ LPSTR szModuleName); + + + +//----------------------------------------------------------------------------- +// WINDOWS SPECIFIC UTILITY FUNCTIONS BELOW: +//----------------------------------------------------------------------------- + +typedef struct tdVMMDLL_WIN_THUNKINFO_IAT { + BOOL fValid; + BOOL f32; // if TRUE fn is a 32-bit/4-byte entry, otherwise 64-bit/8-byte entry. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaFunction; // value if import address table 'thunk' == address of imported function. + ULONG64 vaNameModule; // address of name string for imported module. + ULONG64 vaNameFunction; // address of name string for imported function. +} VMMDLL_WIN_THUNKINFO_IAT, *PVMMDLL_WIN_THUNKINFO_IAT; + +typedef struct tdVMMDLL_WIN_THUNKINFO_EAT { + BOOL fValid; + DWORD valueThunk; // value of export address table 'thunk'. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaNameFunction; // address of name string for exported function. + ULONG64 vaFunction; // address of exported function (module base + value parameter). +} VMMDLL_WIN_THUNKINFO_EAT, *PVMMDLL_WIN_THUNKINFO_EAT; + +/* +* Retrieve information about the import address table IAT thunk for an imported +* function. This includes the virtual address of the IAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- szImportModuleName +* -- szImportFunctionName +* -- pThunkIAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoIAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_IAT pThunkInfoIAT); + +/* +* Retrieve information about the export address table EAT thunk for an exported +* function. This includes the virtual address of the EAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- pThunkEAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoEAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szExportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_EAT pThunkInfoEAT); + +/* +* Decompress compressed memory page stored in the MemCompression process. +* -- vaCompressedData = virtual address in 'MemCompression' to decompress. +* -- cbCompressedData = length of compressed data in 'MemCompression' to decompress (or zero for auto-detect). +* -- pbDecompressedPage +* -- pcbCompressedData = optional ptr to receive length of compressed buffer. +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinMemCompression_DecompressPage( + _In_ ULONG64 vaCompressedData, + _In_opt_ DWORD cbCompressedData, + _Out_writes_(4096) PBYTE pbDecompressedPage, + _Out_opt_ PDWORD pcbCompressedData +); //----------------------------------------------------------------------------- diff --git a/vmm_example/vmmdll_example.c b/vmm_example/vmmdll_example.c index d322fec..f3b17fa 100644 --- a/vmm_example/vmmdll_example.c +++ b/vmm_example/vmmdll_example.c @@ -64,6 +64,8 @@ int main(_In_ int argc, _In_ char* argv[]) BOOL result; NTSTATUS nt; DWORD i, dwPID; + DWORD dw = 0; + QWORD va; BYTE pbPage1[0x1000], pbPage2[0x1000]; #ifdef _INITIALIZE_FROM_FILE @@ -86,12 +88,12 @@ int main(_In_ int argc, _In_ char* argv[]) printf("------------------------------------------------------------\n"); printf("#01: Initialize from TotalMeltdown: \n"); ShowKeyPress(); - printf("CALL: VMMDLL_InitializeTotalMeltdown\n"); - result = VMMDLL_InitializeTotalMeltdown(); + printf("CALL: VMMDLL_Initialize\n"); + result = result = VMMDLL_Initialize(3, (LPSTR[]) { "", "-device", "totalmeltdown" }); if(result) { - printf("SUCCESS: VMMDLL_InitializeTotalMeltdown\n"); + printf("SUCCESS: VMMDLL_Initialize\n"); } else { - printf("FAIL: VMMDLL_InitializeTotalMeltdown\n"); + printf("FAIL: VMMDLL_Initialize\n"); return 1; } #endif /* _INITIALIZE_FROM_TOTALMELTDOWN */ @@ -101,12 +103,12 @@ int main(_In_ int argc, _In_ char* argv[]) printf("------------------------------------------------------------\n"); printf("#01: Initialize from FPGA: \n"); ShowKeyPress(); - printf("CALL: VMMDLL_InitializeFPGA\n"); - result = VMMDLL_InitializeFPGA(NULL, NULL); + printf("CALL: VMMDLL_Initialize\n"); + result = VMMDLL_Initialize(3, (LPSTR[]) { "", "-device", "fpga" }); if(result) { - printf("SUCCESS: VMMDLL_InitializeFPGA\n"); + printf("SUCCESS: VMMDLL_Initialize\n"); } else { - printf("FAIL: VMMDLL_InitializeFPGA\n"); + printf("FAIL: VMMDLL_Initialize\n"); return 1; } // Retrieve the ID of the FPPA (SP605/PCIeScreamer/AC701 ...) and the bitstream version @@ -572,6 +574,78 @@ int main(_In_ int argc, _In_ char* argv[]) } + // Get base virtual address of ntoskrnl.exe + printf("------------------------------------------------------------\n"); + printf("#17: get ntoskrnl.exe base virtual address \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_ProcessGetModuleBase\n"); + va = VMMDLL_ProcessGetModuleBase(4, "ntoskrnl.exe"); + if(va) { + printf("SUCCESS: VMMDLL_ProcessGetModuleBase\n"); + printf(" %s = %016llx\n", "ntoskrnl.exe", va); + } else { + printf("FAIL: VMMDLL_ProcessGetModuleBase\n"); + return 1; + } + + + // GetProcAddress from ntoskrnl.exe + printf("------------------------------------------------------------\n"); + printf("#18: get proc address for ntoskrnl.exe!KeGetCurrentIrql \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_ProcessGetProcAddress\n"); + va = VMMDLL_ProcessGetProcAddress(4, "ntoskrnl.exe", "KeGetCurrentIrql"); + if(va) { + printf("SUCCESS: VMMDLL_ProcessGetProcAddress\n"); + printf(" %s!%s = %016llx\n", "ntoskrnl.exe", "KeGetCurrentIrql", va); + } else { + printf("FAIL: VMMDLL_ProcessGetProcAddress\n"); + return 1; + } + + + // Get EAT Thunk from ntoskrnl.exe!KeGetCurrentIrql + printf("------------------------------------------------------------\n"); + printf("#19: Address of EAT thunk for ntoskrnl.exe!KeGetCurrentIrql \n"); + ShowKeyPress(); + VMMDLL_WIN_THUNKINFO_EAT oThunkInfoEAT; + ZeroMemory(&oThunkInfoEAT, sizeof(VMMDLL_WIN_THUNKINFO_EAT)); + printf("CALL: VMMDLL_WinGetThunkInfoEAT\n"); + result = VMMDLL_WinGetThunkInfoEAT(4, "ntoskrnl.exe", "KeGetCurrentIrql", &oThunkInfoEAT); + if(result) { + printf("SUCCESS: VMMDLL_WinGetThunkInfoEAT\n"); + printf(" vaFunction: %016llx\n", oThunkInfoEAT.vaFunction); + printf(" vaThunk: %016llx\n", oThunkInfoEAT.vaThunk); + printf(" valueThunk: %08x\n", oThunkInfoEAT.valueThunk); + printf(" vaNameFunc: %016llx\n", oThunkInfoEAT.vaNameFunction); + } else { + printf("FAIL: VMMDLL_WinGetThunkInfoEAT\n"); + return 1; + } + + + // Get IAT Thunk ntoskrnl.exe -> hal.dll!HalSendNMI + printf("------------------------------------------------------------\n"); + printf("#20: Address of IAT thunk for hal.dll!HalSendNMI in ntoskrnl\n"); + ShowKeyPress(); + VMMDLL_WIN_THUNKINFO_IAT oThunkInfoIAT; + ZeroMemory(&oThunkInfoIAT, sizeof(VMMDLL_WIN_THUNKINFO_IAT)); + printf("CALL: VMMDLL_WinGetThunkInfoIAT\n"); + result = VMMDLL_WinGetThunkInfoIAT(4, "ntoskrnl.Exe", "hal.Dll", "HalSendNMI", &oThunkInfoIAT); + if(result) { + printf("SUCCESS: VMMDLL_WinGetThunkInfoIAT\n"); + printf(" vaFunction: %016llx\n", oThunkInfoIAT.vaFunction); + printf(" vaThunk: %016llx\n", oThunkInfoIAT.vaThunk); + printf(" vaNameFunction: %016llx\n", oThunkInfoIAT.vaNameFunction); + printf(" vaNameModule: %016llx\n", oThunkInfoIAT.vaNameModule); + } + else { + printf("FAIL: VMMDLL_WinGetThunkInfoEAT\n"); + return 1; + } + + + // Finish everything and exit! printf("------------------------------------------------------------\n"); printf("#99: FINISHED EXAMPLES! \n"); diff --git a/vmmpyc/leechcore.h b/vmmpyc/leechcore.h index a2da3e6..46af16a 100644 --- a/vmmpyc/leechcore.h +++ b/vmmpyc/leechcore.h @@ -68,13 +68,14 @@ // placed in same directory as the executable file. // // PMEM : load the rekall winpmem driver into the kernel and connect to it -// to acquire memory. The driver file 'winpmem_x64.sys' is found in -// the Rekall directory after most recent version has been installed. -// Copy 'winpmem_x64.sys' to the directory of leechcore.dll and run -// executable as elevated admin using syntax below: +// to acquire memory. The signed driver `.sys` file may be found at: +// https://github.com/Velocidex/c-aff4/tree/master/tools/pmem/resources/winpmem +// Download the driver file `att_winpmem_64.sys` and copy it to the +// directory of leechcore.dll and run executable as elevated admin +// using syntax below: // Syntax: -// PMEM (use winpmem_x64.sys in directory of executable) -// PMEM:// +// PMEM (use att_winpmem_64.sys in directory of executable) +// PMEM:// // // TOTALMELTDOWN : read/write - requires a Windows 7 system vulnerable to the // "Total Meltdown" vulnerability - CVE-2018-1038. @@ -110,7 +111,7 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // -// Header Version: 1.0 +// Header Version: 1.1.0 // #ifndef __LEECHCORE_H__ #define __LEECHCORE_H__ @@ -151,6 +152,7 @@ typedef long long unsigned int QWORD, *PQWORD, ULONG64, *PULONG64; #define _Printf_format_string_ #define _Inout_updates_bytes_(x) #define _In_reads_(cbDataIn) +#define _Out_writes_opt_(x) #define _Success_(return) #endif /* LINUX */ @@ -227,7 +229,10 @@ typedef struct tdLEECHCORE_PAGESTAT_MINIMAL { } LEECHCORE_PAGESTAT_MINIMAL, *PLEECHCORE_PAGESTAT_MINIMAL; /* -* Open a connection to the target device. +* Open a connection to the target device. The LeechCore initialization may fail +* if the underlying device cannot be opened or if the LeechCore is already +* initialized. If already initialized please connect with device EXISTING or +* call LeechCore_Close() before opening a new device. * -- pInformation * -- result */ @@ -455,9 +460,9 @@ DLLEXPORT BOOL LeechCore_CommandData( _In_ ULONG64 fOption, _In_reads_(cbDataIn) PBYTE pbDataIn, _In_ DWORD cbDataIn, - _Out_writes_(cbDataOut) PBYTE pbDataOut, + _Out_writes_opt_(cbDataOut) PBYTE pbDataOut, _In_ DWORD cbDataOut, - _Out_ PDWORD pcbDataOut + _Out_opt_ PDWORD pcbDataOut ); #ifdef __cplusplus diff --git a/vmmpyc/version.h b/vmmpyc/version.h index ac777a4..3faa79f 100644 --- a/vmmpyc/version.h +++ b/vmmpyc/version.h @@ -2,7 +2,7 @@ #define STRINGIZE(s) STRINGIZE2(s) #define VERSION_MAJOR 2 -#define VERSION_MINOR 0 +#define VERSION_MINOR 1 #define VERSION_REVISION 0 #define VERSION_BUILD 0 diff --git a/vmmpyc/vmmdll.h b/vmmpyc/vmmdll.h index 353159a..42befa8 100644 --- a/vmmpyc/vmmdll.h +++ b/vmmpyc/vmmdll.h @@ -4,7 +4,7 @@ // (c) Ulf Frisk, 2018-2019 // Author: Ulf Frisk, pcileech@frizk.net // -// Header Version: 2.0 +// Header Version: 2.1 // #include @@ -26,13 +26,15 @@ extern "C" { * about the parameters please see github wiki for Memory Process File System * and LeechCore. THIS IS THE PREFERED WAY OF INITIALIZING VMM.DLL * Important parameters are: -* -vdll = show printf style outputs) +* -printf = show printf style outputs) * -v -vv -vvv = extra verbosity levels) * -device = device as on format for LeechCore - please see leechcore.h or * Github documentation for additional information. Some values * are: , fpga, usb3380, hvsavedstate, totalmeltdown, pmem * -remote = remote LeechCore instance - please see leechcore.h or Github * documentation for additional information. +* -norefresh = disable background refreshes (even if backing memory is +* volatile memory). * -- argc * -- argv * -- return = success/fail @@ -289,7 +291,7 @@ typedef struct tdVMMDLL_PLUGIN_REGINFO { * -- ppMEMs = array of scatter read headers. * -- cpMEMs = count of ppDMAs. * -- pcpDMAsRead = optional count of number of successfully read ppDMAs. -* -- flags = optional flags as given by VMM_FLAG_* +* -- flags = optional flags as given by VMMDLL_FLAG_* * -- return = the number of successfully read items. */ DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); @@ -322,13 +324,25 @@ BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DW * -- pb * -- cb * -- pcbRead -* -- flags = flags as in VMM_FLAG_* +* -- flags = flags as in VMMDLL_FLAG_* * -- return = success/fail. NB! reads may report as success even if 0 bytes are * read - it's recommended to verify pcbReadOpt parameter. */ _Success_(return) BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); +/* +* Prefetch a number of addresses (specified in the pA array) into the memory +* cache. This function is to be used to batch larger known reads into local +* cache before making multiple smaller reads - which will then happen from +* the cache. Function exists for performance reasons. +* -- dwPID = PID of target process, (DWORD)-1 for physical memory. +* -- pPrefetchAddresses = array of addresses to read into cache. +* -- cPrefetchAddresses +*/ +_Success_(return) +BOOL VMMDLL_MemPrefetchPages(_In_ DWORD dwPID, _In_reads_(cPrefetchAddresses) PULONG64 pPrefetchAddresses, _In_ DWORD cPrefetchAddresses); + /* * Write a contigious arbitrary amount of memory. Please note some virtual memory * such as pages of executables (such as DLLs) may be shared between different @@ -527,6 +541,87 @@ BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMD _Success_(return) BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_opt_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +/* +* Retrieve the virtual address of a given function inside a process/module. +* -- dwPID +* -- szModuleName +* -- szFunctionName +* -- return = virtual address of function, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetProcAddress(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szFunctionName); + +/* +* Retrieve the base address of a given module. +* -- dwPID +* -- szModuleName +* -- return = virtual address of module base, zero on fail. +*/ +ULONG64 VMMDLL_ProcessGetModuleBase(_In_ DWORD dwPID, _In_ LPSTR szModuleName); + + + +//----------------------------------------------------------------------------- +// WINDOWS SPECIFIC UTILITY FUNCTIONS BELOW: +//----------------------------------------------------------------------------- + +typedef struct tdVMMDLL_WIN_THUNKINFO_IAT { + BOOL fValid; + BOOL f32; // if TRUE fn is a 32-bit/4-byte entry, otherwise 64-bit/8-byte entry. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaFunction; // value if import address table 'thunk' == address of imported function. + ULONG64 vaNameModule; // address of name string for imported module. + ULONG64 vaNameFunction; // address of name string for imported function. +} VMMDLL_WIN_THUNKINFO_IAT, *PVMMDLL_WIN_THUNKINFO_IAT; + +typedef struct tdVMMDLL_WIN_THUNKINFO_EAT { + BOOL fValid; + DWORD valueThunk; // value of export address table 'thunk'. + ULONG64 vaThunk; // address of import address table 'thunk'. + ULONG64 vaNameFunction; // address of name string for exported function. + ULONG64 vaFunction; // address of exported function (module base + value parameter). +} VMMDLL_WIN_THUNKINFO_EAT, *PVMMDLL_WIN_THUNKINFO_EAT; + +/* +* Retrieve information about the import address table IAT thunk for an imported +* function. This includes the virtual address of the IAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- szImportModuleName +* -- szImportFunctionName +* -- pThunkIAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoIAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szImportModuleName, _In_ LPSTR szImportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_IAT pThunkInfoIAT); + +/* +* Retrieve information about the export address table EAT thunk for an exported +* function. This includes the virtual address of the EAT thunk which is useful +* for hooking. +* -- dwPID +* -- szModuleName +* -- pThunkEAT +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinGetThunkInfoEAT(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _In_ LPSTR szExportFunctionName, _Out_ PVMMDLL_WIN_THUNKINFO_EAT pThunkInfoEAT); + +/* +* Decompress compressed memory page stored in the MemCompression process. +* -- vaCompressedData = virtual address in 'MemCompression' to decompress. +* -- cbCompressedData = length of compressed data in 'MemCompression' to decompress (or zero for auto-detect). +* -- pbDecompressedPage +* -- pcbCompressedData = optional ptr to receive length of compressed buffer. +* -- return +*/ +_Success_(return) +BOOL VMMDLL_WinMemCompression_DecompressPage( + _In_ ULONG64 vaCompressedData, + _In_opt_ DWORD cbCompressedData, + _Out_writes_(4096) PBYTE pbDecompressedPage, + _Out_opt_ PDWORD pcbCompressedData +); //----------------------------------------------------------------------------- diff --git a/vmmpyc/vmmpyc.c b/vmmpyc/vmmpyc.c index e039473..0bb625b 100644 --- a/vmmpyc/vmmpyc.c +++ b/vmmpyc/vmmpyc.c @@ -48,6 +48,16 @@ VMMPYC_Initialize(PyObject *self, PyObject *args) return Py_BuildValue("s", NULL); // None returned on success. } +// () -> None +static PyObject* +VMMPYC_Close(PyObject *self, PyObject *args) +{ + Py_BEGIN_ALLOW_THREADS; + VMMDLL_Close(); + Py_END_ALLOW_THREADS; + return Py_BuildValue("s", NULL); // None returned on success. +} + //----------------------------------------------------------------------------- @@ -63,9 +73,9 @@ VMMPYC_ConfigGet(PyObject *self, PyObject *args) BOOL result; ULONG64 fOption, qwValue = 0; if(!PyArg_ParseTuple(args, "K", &fOption)) { return NULL; } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ConfigGet(fOption, &qwValue); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ConfigGet: Unable to retrieve config value for setting."); } return PyLong_FromUnsignedLongLong(qwValue); } @@ -77,9 +87,9 @@ VMMPYC_ConfigSet(PyObject *self, PyObject *args) BOOL result; ULONG64 fOption, qwValue = 0; if(!PyArg_ParseTuple(args, "KK", &fOption, &qwValue)) { return NULL; } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ConfigSet(fOption, qwValue); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ConfigSet: Unable to set config value for setting."); } return Py_BuildValue("s", NULL); // None returned on success. } @@ -139,9 +149,9 @@ VMMPYC_MemReadScatter(PyObject *self, PyObject *args) } Py_DECREF(pyListSrc); // call c-dll for vmm - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_MemReadScatter(dwPID, ppMEMs, cMEMs, flags); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { LocalFree(pb); return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemReadScatter: Failed."); @@ -177,9 +187,9 @@ VMMPYC_MemRead(PyObject *self, PyObject *args) if(cb > 0x01000000) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemRead: Read larger than maxium supported (0x01000000) bytes requested."); } pb = LocalAlloc(0, cb); if(!pb) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_MemReadEx(dwPID, qwA, pb, cb, &cbRead, flags); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { LocalFree(pb); return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemRead: Failed."); @@ -213,10 +223,10 @@ VMMPYC_MemWrite(PyObject *self, PyObject *args) } iResult = PyBuffer_ToContiguous(pb, &pyBuffer, cb, 'C'); PyBuffer_Release(&pyBuffer); - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = (iResult == 0) && VMMDLL_MemWrite(dwPID, va, pb, (DWORD)cb); LocalFree(pb); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemWrite: Failed."); } return Py_BuildValue("s", NULL); // None returned on success. } @@ -229,9 +239,9 @@ VMMPYC_MemVirt2Phys(PyObject *self, PyObject *args) DWORD dwPID; ULONG64 va, pa; if(!PyArg_ParseTuple(args, "kK", &dwPID, &va)) { return NULL; } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_MemVirt2Phys(dwPID, va, &pa); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemVirt2Phys: Failed."); } return PyLong_FromUnsignedLongLong(pa); } @@ -248,13 +258,13 @@ VMMPYC_ProcessGetMemoryMap(PyObject *self, PyObject *args) CHAR sz[5]; if(!PyArg_ParseTuple(args, "k|p", &dwPID, &fIdentifyModules)) { return NULL; } if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ProcessGetMemoryMap(dwPID, NULL, &cMemMapEntries, fIdentifyModules) && cMemMapEntries && (pMemMapEntries = LocalAlloc(0, cMemMapEntries * sizeof(VMMDLL_MEMMAP_ENTRY))) && VMMDLL_ProcessGetMemoryMap(dwPID, pMemMapEntries, &cMemMapEntries, fIdentifyModules); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyList); LocalFree(pMemMapEntries); @@ -294,9 +304,9 @@ VMMPYC_ProcessGetMemoryMapEntry(PyObject *self, PyObject *args) CHAR sz[5]; if(!PyArg_ParseTuple(args, "kK|p", &dwPID, &va, &fIdentifyModules)) { return NULL; } if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ProcessGetMemoryMapEntry(dwPID, &e, va, fIdentifyModules); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyDict); return PyErr_Format(PyExc_RuntimeError, "VMMDLL_ProcessGetMemoryMapEntry: Failed."); @@ -327,13 +337,13 @@ VMMPYC_ProcessGetModuleMap(PyObject *self, PyObject *args) PVMMDLL_MODULEMAP_ENTRY pe, pModuleEntries = NULL; if(!PyArg_ParseTuple(args, "k", &dwPID)) { return NULL; } if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ProcessGetModuleMap(dwPID, NULL, &cModuleEntries) && cModuleEntries && (pModuleEntries = LocalAlloc(0, cModuleEntries * sizeof(VMMDLL_MODULEMAP_ENTRY))) && VMMDLL_ProcessGetModuleMap(dwPID, pModuleEntries, &cModuleEntries); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyList); LocalFree(pModuleEntries); @@ -365,10 +375,10 @@ VMMPYC_ProcessGetModuleFromName(PyObject *self, PyObject *args) VMMDLL_MODULEMAP_ENTRY e; if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModuleName)) { return NULL; } if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; ZeroMemory(&e, sizeof(VMMDLL_MODULEMAP_ENTRY)); result = VMMDLL_ProcessGetModuleFromName(dwPID, szModuleName, &e); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyDict); return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetModuleFromName: Failed."); @@ -389,9 +399,9 @@ VMMPYC_PidGetFromName(PyObject *self, PyObject *args) DWORD dwPID; LPSTR szProcessName; if(!PyArg_ParseTuple(args, "s", &szProcessName)) { return NULL; } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_PidGetFromName(szProcessName, &dwPID); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_PidGetFromName: Failed."); } return PyLong_FromLong(dwPID); } @@ -405,12 +415,12 @@ VMMPYC_PidList(PyObject *self, PyObject *args) ULONG64 cPIDs = 0; DWORD i, *pPIDs = NULL; if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_PidList(NULL, &cPIDs) && (pPIDs = LocalAlloc(LMEM_ZEROINIT, cPIDs * sizeof(DWORD))) && VMMDLL_PidList(pPIDs, &cPIDs); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyList); LocalFree(pPIDs); @@ -434,12 +444,12 @@ VMMPYC_ProcessGetInformation(PyObject *self, PyObject *args) SIZE_T cbInfo = sizeof(VMMDLL_PROCESS_INFORMATION); if(!PyArg_ParseTuple(args, "k", &dwPID)) { return NULL; } if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; ZeroMemory(&info, sizeof(VMMDLL_PROCESS_INFORMATION)); info.magic = VMMDLL_PROCESS_INFORMATION_MAGIC; info.wVersion = VMMDLL_PROCESS_INFORMATION_VERSION; result = VMMDLL_ProcessGetInformation(dwPID, &info, &cbInfo); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyDict); return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetInformation: Failed."); @@ -481,11 +491,11 @@ VMMPYC_ProcessGetDirectories(PyObject *self, PyObject *args) LPCSTR DIRECTORIES[16] = { "EXPORT", "IMPORT", "RESOURCE", "EXCEPTION", "SECURITY", "BASERELOC", "DEBUG", "ARCHITECTURE", "GLOBALPTR", "TLS", "LOAD_CONFIG", "BOUND_IMPORT", "IAT", "DELAY_IMPORT", "COM_DESCRIPTOR", "RESERVED" }; if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = (pDirectories = LocalAlloc(0, 16 * sizeof(IMAGE_DATA_DIRECTORY))) && VMMDLL_ProcessGetDirectories(dwPID, szModule, pDirectories, 16, &cDirectories); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyList); LocalFree(pDirectories); @@ -518,13 +528,13 @@ VMMPYC_ProcessGetSections(PyObject *self, PyObject *args) szName[8] = 0; if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ProcessGetSections(dwPID, szModule, NULL, 0, &cSections) && cSections && (pSections = LocalAlloc(0, cSections * sizeof(IMAGE_SECTION_HEADER))) && VMMDLL_ProcessGetSections(dwPID, szModule, pSections, cSections, &cSections); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyList); LocalFree(pSections); @@ -564,13 +574,13 @@ VMMPYC_ProcessGetEAT(PyObject *self, PyObject *args) LPSTR szModule; if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ProcessGetEAT(dwPID, szModule, NULL, 0, &cEATs) && cEATs && (pEATs = LocalAlloc(0, cEATs * sizeof(VMMDLL_EAT_ENTRY))) && VMMDLL_ProcessGetEAT(dwPID, szModule, pEATs, cEATs, &cEATs); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyList); LocalFree(pEATs); @@ -601,13 +611,13 @@ VMMPYC_ProcessGetIAT(PyObject *self, PyObject *args) LPSTR szModule; if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = VMMDLL_ProcessGetIAT(dwPID, szModule, NULL, 0, &cIATs) && cIATs && (pIATs = LocalAlloc(0, cIATs * sizeof(VMMDLL_IAT_ENTRY))) && VMMDLL_ProcessGetIAT(dwPID, szModule, pIATs, cIATs, &cIATs); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { Py_DECREF(pyList); LocalFree(pIATs); @@ -650,7 +660,7 @@ VMMPYC_UtilFillHexAscii(PyObject *self, PyObject *args) } iResult = PyBuffer_ToContiguous(pb, &pyBuffer, cb, 'C'); PyBuffer_Release(&pyBuffer); - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = (iResult == 0) && VMMDLL_UtilFillHexAscii(pb, cb, cbInitialOffset, NULL, &csz) && @@ -658,8 +668,8 @@ VMMPYC_UtilFillHexAscii(PyObject *self, PyObject *args) (sz = (LPSTR)LocalAlloc(0, csz)) && VMMDLL_UtilFillHexAscii(pb, cb, cbInitialOffset, sz, &csz); LocalFree(pb); - Py_END_ALLOW_THREADS - if(!result || !sz) { + Py_END_ALLOW_THREADS; + if(!result || !sz) { LocalFree(sz); return PyErr_Format(PyExc_RuntimeError, "VMMPYC_UtilFillHexAscii: Failed."); } @@ -690,9 +700,9 @@ VMMPYC_VfsRead(PyObject *self, PyObject *args) } pb = LocalAlloc(0, cb); if(!pb) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; nt = VMMDLL_VfsRead(wszFileName, pb, cb, &cbRead, cbOffset); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(nt != VMMDLL_STATUS_SUCCESS) { LocalFree(pb); return PyErr_Format(PyExc_RuntimeError, "VMMPYC_VfsRead: Failed."); @@ -734,14 +744,124 @@ VMMPYC_VfsWrite(PyObject *self, PyObject *args) } iResult = PyBuffer_ToContiguous(pb, &pyBuffer, cb, 'C'); PyBuffer_Release(&pyBuffer); - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; result = (iResult == 0) && (VMMDLL_STATUS_SUCCESS == VMMDLL_VfsWrite(wszFileName, pb, cb, &cbWritten, cbOffset)); LocalFree(pb); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_VfsWrite: Failed."); } return Py_BuildValue("s", NULL); // None returned on success. } +// (DWORD, STR, STR) -> ULONG64 +static PyObject* +VMMPYC_ProcessGetProcAddress(PyObject *self, PyObject *args) +{ + ULONG64 va; + DWORD dwPID; + LPSTR szModuleName, szProcName; + if(!PyArg_ParseTuple(args, "kss", &dwPID, &szModuleName, &szProcName)) { return NULL; } + Py_BEGIN_ALLOW_THREADS; + va = VMMDLL_ProcessGetProcAddress(dwPID, szModuleName, szProcName); + Py_END_ALLOW_THREADS; + return va ? + PyLong_FromUnsignedLongLong(va) : + PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetProcAddress: Failed."); +} + +// (DWORD, STR) -> ULONG64 +static PyObject* +VMMPYC_ProcessGetModuleBase(PyObject *self, PyObject *args) +{ + ULONG64 va; + DWORD dwPID; + LPSTR szModuleName; + if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModuleName)) { return NULL; } + Py_BEGIN_ALLOW_THREADS; + va = VMMDLL_ProcessGetModuleBase(dwPID, szModuleName); + Py_END_ALLOW_THREADS; + return va ? + PyLong_FromUnsignedLongLong(va) : + PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetModuleBase: Failed."); +} + +// (DWORD, STR, STR) -> {...} +static PyObject* +VMMPYC_WinGetThunkInfoEAT(PyObject *self, PyObject *args) +{ + PyObject *pyDict; + BOOL result; + DWORD dwPID; + VMMDLL_WIN_THUNKINFO_EAT oThunkInfoEAT = { 0 }; + LPSTR szModuleName, szExportFunctionName; + if(!PyArg_ParseTuple(args, "kss", &dwPID, &szModuleName, &szExportFunctionName)) { return NULL; } + Py_BEGIN_ALLOW_THREADS; + result = VMMDLL_WinGetThunkInfoEAT(dwPID, szModuleName, szExportFunctionName, &oThunkInfoEAT); + Py_END_ALLOW_THREADS; + if(!result || !oThunkInfoEAT.fValid) { + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_WinGetThunkInfoEAT: Failed."); + } + pyDict = PyDict_New(); + if(pyDict) { + PyDict_SetItemString(pyDict, "vaFunction", PyLong_FromUnsignedLongLong(oThunkInfoEAT.vaFunction)); + PyDict_SetItemString(pyDict, "valueThunk", PyLong_FromUnsignedLong(oThunkInfoEAT.valueThunk)); + PyDict_SetItemString(pyDict, "vaNameFunction", PyLong_FromUnsignedLongLong(oThunkInfoEAT.vaNameFunction)); + PyDict_SetItemString(pyDict, "vaThunk", PyLong_FromUnsignedLongLong(oThunkInfoEAT.vaThunk)); + } + return pyDict; +} + +// (DWORD, STR, STR, STR) -> {...} +static PyObject* +VMMPYC_WinGetThunkInfoIAT(PyObject *self, PyObject *args) +{ + PyObject *pyDict; + BOOL result; + DWORD dwPID; + VMMDLL_WIN_THUNKINFO_IAT oThunkInfoIAT = { 0 }; + LPSTR szModuleName, szImportModuleName, szImportFunctionName; + if(!PyArg_ParseTuple(args, "ksss", &dwPID, &szModuleName, &szImportModuleName, &szImportFunctionName)) { return NULL; } + Py_BEGIN_ALLOW_THREADS; + result = VMMDLL_WinGetThunkInfoIAT(dwPID, szModuleName, szImportModuleName, szImportFunctionName, &oThunkInfoIAT); + Py_END_ALLOW_THREADS; + if(!result || !oThunkInfoIAT.fValid) { + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_WinGetThunkInfoEAT: Failed."); + } + pyDict = PyDict_New(); + if(pyDict) { + PyDict_SetItemString(pyDict, "32", PyBool_FromLong(oThunkInfoIAT.f32 ? 1 : 0)); + PyDict_SetItemString(pyDict, "vaFunction", PyLong_FromUnsignedLongLong(oThunkInfoIAT.vaFunction)); + PyDict_SetItemString(pyDict, "vaNameFunction", PyLong_FromUnsignedLongLong(oThunkInfoIAT.vaNameFunction)); + PyDict_SetItemString(pyDict, "vaNameModule", PyLong_FromUnsignedLongLong(oThunkInfoIAT.vaNameModule)); + PyDict_SetItemString(pyDict, "vaThunk", PyLong_FromUnsignedLongLong(oThunkInfoIAT.vaThunk)); + } + return pyDict; +} + +// (ULONG64, DWORD) -> {b: PBYTE, c: DWORD} +static PyObject* +VMMPYC_WinMemCompression_DecompressPage(PyObject *self, PyObject *args) +{ + PyObject *pyDict; + BOOL result; + DWORD cb, cbCompressed; + ULONG64 va; + BYTE pbDecompressed[0x1000] = { 0 }; + if(!PyArg_ParseTuple(args, "Kk", &va, &cb)) { return NULL; } + Py_BEGIN_ALLOW_THREADS; + result = VMMDLL_WinMemCompression_DecompressPage(va, cb, pbDecompressed, &cbCompressed); + Py_END_ALLOW_THREADS; + if(!result) { + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_WinMemCompression_DecompressPage: Failed."); + } + pyDict = PyDict_New(); + if(pyDict) { + PyDict_SetItemString(pyDict, "c", PyLong_FromUnsignedLong(cbCompressed)); + PyDict_SetItemString(pyDict, "b", PyBytes_FromStringAndSize(pbDecompressed, 0x1000)); + } + return pyDict; +} + + typedef struct tdVMMPYC_VFSLIST { struct tdVMMPYC_VFSLIST *FLink; @@ -788,20 +908,20 @@ VMMPYC_VfsList(PyObject *self, PyObject *args) PVMMPYC_VFSLIST pE = NULL, pE_Next; if(!PyArg_ParseTuple(args, "s", &szPath)) { return NULL; } if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } - Py_BEGIN_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS; { // char* -> wchar* for(i = 0; i < MAX_PATH - 1; i++) { wszPath[i] = szPath[i]; if(0 == szPath[i]) { break; } } - wszPath[MAX_PATH - 1] = 0; + wszPath[MAX_PATH - 1] = 0; } hFileList.h = &pE; hFileList.pfnAddFile = VMMPYC_VfsList_AddFile; hFileList.pfnAddDirectory = VMMPYC_VfsList_AddDirectory; result = VMMDLL_VfsList(wszPath, &hFileList); pE = *(PVMMPYC_VFSLIST*)hFileList.h; - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; while(pE) { if((PyDict_Attr = PyDict_New())) { PyDict_SetItemString(PyDict_Attr, "f_isdir", PyBool_FromLong(pE->fIsDir ? 1 : 0)); @@ -825,6 +945,7 @@ VMMPYC_VfsList(PyObject *self, PyObject *args) static PyMethodDef VMMPYC_EmbMethods[] = { {"VMMPYC_Initialize", VMMPYC_Initialize, METH_VARARGS, "Initialize the VMM"}, + {"VMMPYC_Close", VMMPYC_Close, METH_VARARGS, "Try close the VMM"}, {"VMMPYC_ConfigGet", VMMPYC_ConfigGet, METH_VARARGS, "Get a device specific option value."}, {"VMMPYC_ConfigSet", VMMPYC_ConfigSet, METH_VARARGS, "Set a device specific option value."}, {"VMMPYC_MemReadScatter", VMMPYC_MemReadScatter, METH_VARARGS, "Read multiple 4kB page sized and aligned chunks of memory given as an address list."}, @@ -842,9 +963,14 @@ static PyMethodDef VMMPYC_EmbMethods[] = { {"VMMPYC_ProcessGetSections", VMMPYC_ProcessGetSections, METH_VARARGS, "Retrieve the sections for a specific process and module."}, {"VMMPYC_ProcessGetEAT", VMMPYC_ProcessGetEAT, METH_VARARGS, "Retrieve the export address table (EAT) for a specific process and module."}, {"VMMPYC_ProcessGetIAT", VMMPYC_ProcessGetIAT, METH_VARARGS, "Retrieve the import address table (IAT) for a specific process and module."}, + {"VMMPYC_ProcessGetProcAddress", VMMPYC_ProcessGetProcAddress, METH_VARARGS, "Retrieve the proc address of a given module!function."}, + {"VMMPYC_ProcessGetModuleBase", VMMPYC_ProcessGetModuleBase, METH_VARARGS, "Retrieve the module base address given a module."}, + {"VMMPYC_WinGetThunkInfoEAT", VMMPYC_WinGetThunkInfoEAT, METH_VARARGS, "Retrieve information about the export address table (EAT) thunk. (useful for patching)."}, + {"VMMPYC_WinGetThunkInfoIAT", VMMPYC_WinGetThunkInfoIAT, METH_VARARGS, "Retrieve information about the import address table (IAT) thunk. (useful for patching)."}, {"VMMPYC_VfsRead", VMMPYC_VfsRead, METH_VARARGS, "Read from a file in the virtual file system."}, {"VMMPYC_VfsWrite", VMMPYC_VfsWrite, METH_VARARGS, "Write to a file in the virtual file system."}, {"VMMPYC_VfsList", VMMPYC_VfsList, METH_VARARGS, "List files and folder for a specific directory in the Virutal File System."}, + {"VMMPYC_WinMemCompression_DecompressPage", VMMPYC_WinMemCompression_DecompressPage, METH_VARARGS, "Decompress compressed memory in the MemCompression process (if any)."}, {"VMMPYC_UtilFillHexAscii", VMMPYC_UtilFillHexAscii, METH_VARARGS, "Convert a bytes object into a human readable 'memory dump' style type of string."}, {NULL, NULL, 0, NULL} };