From bb65312557067e5bc84c53df065768185b0db180 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 15:01:42 +0000 Subject: [PATCH 01/50] Bump coverlet.collector from 3.2.0 to 6.0.0 Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 3.2.0 to 6.0.0. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/compare/v3.2.0...v6.0.0) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- .../AsmResolver.Symbols.Pdb.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index 9b7323361..e2958c2b4 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -14,7 +14,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 74743b9c8..d1a6d8390 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -14,7 +14,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 2ca66b227e8f826ec7fc979d154b8264b45f242f Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 5 Jun 2023 14:39:18 +0200 Subject: [PATCH 02/50] Add read support forwarder symbols. --- src/AsmResolver.PE/Exports/ExportedSymbol.cs | 58 ++++++++++++++++-- .../Exports/SerializedExportDirectory.cs | 13 +++- .../Exports/ExportDirectoryTest.cs | 13 ++++ .../Properties/Resources.Designer.cs | 21 +++++++ .../Properties/Resources.resx | 9 +++ .../Resources/ActualDll.dll | Bin 0 -> 10752 bytes .../Resources/ForwarderTest.exe | Bin 0 -> 12288 bytes .../Resources/ProxyDll.dll | Bin 0 -> 10752 bytes 8 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 test/AsmResolver.PE.Tests/Resources/ActualDll.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/ForwarderTest.exe create mode 100644 test/AsmResolver.PE.Tests/Resources/ProxyDll.dll diff --git a/src/AsmResolver.PE/Exports/ExportedSymbol.cs b/src/AsmResolver.PE/Exports/ExportedSymbol.cs index 472f96828..2e52cf49e 100644 --- a/src/AsmResolver.PE/Exports/ExportedSymbol.cs +++ b/src/AsmResolver.PE/Exports/ExportedSymbol.cs @@ -1,4 +1,6 @@ +using System; using System.Diagnostics.CodeAnalysis; +using System.IO; using AsmResolver.Collections; namespace AsmResolver.PE.Exports @@ -29,6 +31,19 @@ public ExportedSymbol(ISegmentReference address, string? name) Address = address; } + /// + /// Creates a new forwarder symbol that is exported by name. + /// + /// The reference to the segment representing the symbol. + /// The name of the symbol. + /// The name of the forwarded symbol. + public ExportedSymbol(ISegmentReference address, string? name, string? forwarderName) + { + Name = name; + Address = address; + ForwarderName = forwarderName; + } + /// /// Gets the export directory this symbol was added to (if available). /// @@ -82,6 +97,7 @@ public string? Name /// /// For exported functions, this reference points to the first instruction that is executed. /// For exported fields, this reference points to the first byte of data that this field consists of. + /// For forwarded symbols, this reference points to the name of the symbol the forwarder is referencing. /// public ISegmentReference Address { @@ -89,15 +105,47 @@ public ISegmentReference Address set; } - /// - public override string ToString() + /// + /// When the symbol is a forwarder symbol, gets or sets the full name of the symbol that this export is forwarded to. + /// + /// + /// For exports by name, this name should be in the format ModuleName.ExportName. + /// For exports by ordinal, this name should be in the format ModuleName.#1. + /// Failure in doing so will make the Windows PE loader not able to resolve the forwarder symbol. + /// + public string? ForwarderName + { + get; + set; + } + + /// + /// Gets a value indicating whether the symbol is forwarded to another external symbol. + /// + [MemberNotNullWhen(true, nameof(ForwarderName))] + public bool IsForwarder => ForwarderName is not null; + + /// + /// Obtains a name that can be used as a in another export. + /// + /// The name. + public string FormatNameAsForwarderSymbol() => FormatFullName('.'); + + private string FormatFullName(char separator) { string displayName = Name ?? $"#{Ordinal.ToString()}"; - return ParentDirectory is null - ? displayName - : $"{ParentDirectory.Name}!{displayName}"; + if (ParentDirectory is not {Name: { } parentName}) + return displayName; + + if (string.Equals(Path.GetExtension(parentName), ".dll", StringComparison.OrdinalIgnoreCase)) + parentName = Path.GetFileNameWithoutExtension(parentName); + + return $"{parentName}{separator}{displayName}"; } + /// + public override string ToString() => FormatFullName('!'); + /// public ISegmentReference GetReference() => Address; } diff --git a/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs b/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs index 431188b85..03a530291 100644 --- a/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs +++ b/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using AsmResolver.IO; +using AsmResolver.PE.File.Headers; namespace AsmResolver.PE.Exports { @@ -10,6 +11,7 @@ namespace AsmResolver.PE.Exports public class SerializedExportDirectory : ExportDirectory { private readonly PEReaderContext _context; + private readonly DataDirectory _dataDirectory; private readonly uint _nameRva; private readonly uint _numberOfFunctions; private readonly uint _numberOfNames; @@ -27,6 +29,7 @@ public SerializedExportDirectory(PEReaderContext context, ref BinaryStreamReader if (!reader.IsValid) throw new ArgumentNullException(nameof(reader)); _context = context ?? throw new ArgumentNullException(nameof(context)); + _dataDirectory = new DataDirectory(reader.StartRva, reader.Length); ExportFlags = reader.ReadUInt32(); TimeDateStamp = reader.ReadUInt32(); @@ -87,7 +90,15 @@ protected override IList GetExports() string? name = null; ordinalNameTable?.TryGetValue(i, out name); - result.Add(new ExportedSymbol(_context.File.GetReferenceToRva(rva), name)); + string? forwarderName = null; + if (rva >= _dataDirectory.VirtualAddress + && rva < _dataDirectory.VirtualAddress + _dataDirectory.Size + && _context.File.TryCreateReaderAtRva(rva, out var forwarderReader)) + { + forwarderName = forwarderReader.ReadAsciiString(); + } + + result.Add(new ExportedSymbol(_context.File.GetReferenceToRva(rva), name, forwarderName)); } return result; diff --git a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs index 0b63631d2..17d7fcc85 100644 --- a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs @@ -207,5 +207,18 @@ public void PersistentExportedSymbolMany() Assert.Equal(unnamedSymbol3.Ordinal, newSymbol.Ordinal); Assert.Equal(unnamedSymbol3.Address.Rva, newSymbol.Address.Rva); } + + [Fact] + public void ReadForwarderSymbol() + { + var image = PEImage.FromBytes(Properties.Resources.ForwarderDlls_ProxyDll); + + var bar = image.Exports!.Entries.First(x=>x.Name == "Bar"); + var baz = image.Exports.Entries.First(x=>x.Name == "Baz"); + + Assert.True(bar.IsForwarder); + Assert.Equal("ActualDll.Foo", bar.ForwarderName); + Assert.False(baz.IsForwarder); + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 61ac7d57a..4bf9bcf87 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -240,5 +240,26 @@ public static byte[] LargeIndicesPdb { return ((byte[])(obj)); } } + + public static byte[] ForwarderDlls_ActualDll { + get { + object obj = ResourceManager.GetObject("ForwarderDlls_ActualDll", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] ForwarderDlls_ProxyDll { + get { + object obj = ResourceManager.GetObject("ForwarderDlls_ProxyDll", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] ForwarderDlls_ForwarderTest { + get { + object obj = ResourceManager.GetObject("ForwarderDlls_ForwarderTest", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index 5ffcc282d..b7fdd3092 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -102,4 +102,13 @@ ..\Resources\LargeIndicesPdb.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\ActualDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\ProxyDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\ForwarderTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.PE.Tests/Resources/ActualDll.dll b/test/AsmResolver.PE.Tests/Resources/ActualDll.dll new file mode 100644 index 0000000000000000000000000000000000000000..a56d0a83dff1033e372fca39ee4fbe6b51dacf73 GIT binary patch literal 10752 zcmeHN4Rl+@l^)rae`91)i*-YQ7a|j<)UIQN;QY9eV>yf)863w>vXDeptmi~UmW1?_ zI3aD4RZL^Q)i!JkOSkN95;!d!*lu7s;Dr9jIK|E%Yf?^03Y6@^!ZT)DLt9Ar>D%wl zd-6|!?w+&l*>ehW&b<3G_uiSgbLY;yCpF%@k7Y8(vQTxMu|7a5F7f=sk6a65mt1w^ z682c$sabs%ˮ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 Bpw$2X literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/ForwarderTest.exe b/test/AsmResolver.PE.Tests/Resources/ForwarderTest.exe new file mode 100644 index 0000000000000000000000000000000000000000..253655436bb85c312c4bfcd64ca5085b2b3404a0 GIT binary patch literal 12288 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 literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/ProxyDll.dll b/test/AsmResolver.PE.Tests/Resources/ProxyDll.dll new file mode 100644 index 0000000000000000000000000000000000000000..7afa3ba445f959abe6cc94ad018a386d46ab7a37 GIT binary patch literal 10752 zcmeHN4|G&jdcTv*B$I?N!OhStN`1)0#A>o3LpA)#GJy%c)X5q{0xSk5!{h~~PG+WW z-aw$KgdMVyyv|u{SBs}^k7RAj9$gPos{y;)2@_3#&_-%qSafA;s}I82jdoXqu6_M| z@4d+%#O>+ndV0?4JLkUd&;9Oqzx&FZEVl992iwvz6YLf)FJtavRshaT zI`_br0EuR-8^hP}492nu$CHLE0)m{1Cxf*@RBx09pQLq8Ln&jeXmbQmGo*pBNmAkh zPwS8fJgtn~0Wfh3)4C$o^`0Y(&~#=y&d_%~VerZ&p;H1Zor@QlOvM-nFzw;cd%4)= zlYERlKL;!@7|9o+%y?Yrx?C4yE^&XehFa*g*nnS5y_;lvuVr=zH`tyhnP;8_|rykcjK7NIh&-01=z4MR~ z>?s65BEHPR7#|6KFxjniu`A9W-L~~4-OWze=w-3F;j{kJXvt* z(M-lhN1?s6=d83We%U0I(rbSFLz9%NnSl;UQ_3bp%D6?j9l-W{QsdRhV+1eR&_#Vu z()P%pMi#+#zPH6;<$EPZ0pHu@;A+;B`Mms|M}F6NQaJ@B`;xYOBWS4HhcWb>+wXR` zTrZg7)jy@^vh}=#_ps#^5abEvaR}{8O7|*H<$!w4jMv9_@d^6^I09bD15kdF$5>A? zcAG+efjEcy%digh7XXw2)DK&_pp?SSRjKx!R-VZQwoi?%Qhou9m(TI>DkeF2c|`e0 zGgB(^$S)tK26gg3@p7g|&TogZ8ub;`^2qNZHIsqk8O|gxw>T~>UWgD+I=>%f%H^@4*y*mo2y;5GS2aGCPN$ErGA^%pa%>{C^7 z)fBrEN-NRKd6t@8{gtZnJ?|r`l$ZYtsBGDwcP!0K%c%C(^Wi{Mvc%QQm}{!X@+q}F zN$EDUTxgh_lh|lNWLAt9re#(8V^E=+H=gCZK(%s;rm_>W;i}o<-9l??I-Y6rxH@@K z{xL7Vi$xN%D1Qkjc;sREUw8_TsUX1odoO(prlY;SetG%4^Q3BDh2+f3r&W775Y=9# zkt4@*%#79*cR#Ndw-K+`gEbVtQg!Ti;4{_EgNN~CAEQ`2a~p#CW$b>+_dxNo8&TJ5 z`97wMPY@nYV)%@hEKz0MWyMB4^_dDu666lfi8GLwcm{8 z$jg66dxlf3#TrO7Ia=XE)m}?A80#`V-sY%+1s=zCUPi4)9wE0>VETv|A)?fIlBS1K z?XTs+Ps4FFqw86kJnt4QFKU&|ynKab4&FGt0Lovi?0>WDvD<;INW~;RE)=1c`XbeS z0y=oPwutYoEl}+eC^^Y5A$&l`6ZM=(V2=5n@35XP)|6mN{Tp}^^n^SPvAo4UKpc2{a4h+Lu`Z2(?%z@6Uf62sYNQ6UaQZcNv}0W8Q&0B!~88HAS%|&+R8{ zFWgti$4?aTnXlMhsLA3JbGki=Ij8vELPy<-SEM5O59WAQ42rq(YHQ}8XGKy(qB~JK z{-*ORkIjc7r7*tR!TyA>T*PG-V1^c^MJe4!edm>7xYl_Ve#zT+A(qWQWPgSdoaV~3 zAHPfA2Yu%y^S-mOIm%@;z+U+lKzhIwV+qSk5KG02zfaOBE!Q{miKiW|k1)i&SCVjw zeBQ{yClH_L(k!1obK5Z}V#*L!9_&ukPt**IVG@*kz$Q|)S3{VWzoS5mPdsuCmB{&@ z2dP(WAL!Xnc0Pv*O1Hp9-HPqI!H)0+Y@(P|?X)7W%hL`5eY8;|YA0;phDY2Eo;vNJ zPSKl7wTnOzeskb@Yl6>IeyHuyk2}UlWbY99Y%}r@oddO1%O@88h_ZU3dJJIv2Arg5 z{8_5Kh+Gj*S@{)jikI}2O6yafhjiPg^tMl4)An`h?K*8wAb_dw>9kScOgc~6`rYzi zt@DCgK3lVQA|wgHn!WD@LPDKeJ}pl;Pu3*Xm}>LJZ2dKR-@RK1#d^(-+Pqhw$1Gok zGz#^mF@EshI6iNT#{fAxB5=2)mZxGHWXrc89Urj{TOI^L;ofWM0#@^Wij%71 zBiyh>0Et;Zg&)`eq{iHf&$(Beixp{_92Zw!jOEAk-Kcf-E?f>klUzz?UI_>0_)vj2 z7Jmbssl}4^eo%Wqp}mi4?c|hqd=^?H$zKTkswoJz`3Y zI+H22xI`nd(yx7{zmL?5*VYj|qrO;#5?3?wKm2pKuiiy@X7i;8GQ{W%Hm7U>=YUq3 zny%1={}Fqr&ckQIWnMPP2v4kQa=Isx-}#=ZdgR)O+>EnMeXGJCPMRvPj?jhH!g+FU z6`Y;4eS1(I1Rs9?;`uG~fcp-8P))b%@EZnv)PP3~IA*{;LtZlACIfCa>dOtf(14Q$ z|KAOmcHz@Ted+J%oB{*#229IYjdl|T|5XD94S3Li-!x#S0goCmt*7l88s84xl*7_6 zvQVcFTm#eX?=kpk`u_?WD)jy)44BrZ9p`7f8pLqtE_X1veEDkMZV0)^=+EG>USmhP zi%Cwm)4E89Y5o-ajO>}xe+|;T_*3jK>?<%}kpVdamKu=MrSEv@BlCWg*B7JEr!Ss# z-5lVw+g3SHil}C~4&U)?BN}^9=eed1-~a!shni<}XLKKg4YVYN0Ezb%bp8}d5~Y4V zV}2ATcHdjEX>UZ?i4$ZO3VoSJa3ysB2D@>!|V^E9~#cm;4q`fePt%35HLPCIslgHlHuXo|PdDLKdP z-MibiVtDNEt;YC7$?vGE(fTPZXJ96;Nta8`;^}fD;O#e||4#jGJ^kB4xnSJ2 zmonBs_US&a1j^WLdi)tYC$Wman*=m?ONmGM`fq3qdRv-a?S9_{Fm>loR{>d|w??DD zFfsHw1lqXkr_Ni!*6#Ej6-$7!R`3o^^K_f2;(vHdI0I)@W@y)h^Xyh;mvWfBEt}bw zW-)u^yo(Gm2y@wF0b|>6u}y1)o>Rd8bp~zdX_%L5X1PnPEVnT$LT$~EwIRsMQRwoE z9y9Q@C@&lIy}&oks3-dV8TBT!iJ4d9B5XkxTd>r|7KrmN7Dm3*O8Vgz_Aq33;%xe3 z&;&JG8Z#P9wk&4DSZo-JtuilS$}zE=t*Ez}nN>7LGFvh3d^5{mn#1xdvl?=!pIe~N z&?^@)W=HNQz%>CuQ-+CUz|W>aD=Tb6Y074W&|O$r5HZ;;@Y!)@Hw~~XlF|H-#d0gN zBDx*0VW|ba$cvB<$ZmSlkZ{_*2>vPJi}>VsDxILaQTC&J2c@8ZvBvpTDAH z%cZGL!q4lu$jZFlHQs1M@CVuhe(!c)s4Xanj1_|)mD;@0u880bgxbU2HX$mB;a%Qf zASyBTRn{SNL?sLF{06P2*mHtzzD& zu-)4p2%^$Ujf$Ap9}cw#w#7uj8}f-_c&FDVZrg!fP;VLV1p~XMs)W#vfEW&S2q9Ro zbe2RoBybNT94ETDCI~D)Wm{83r8)mj#!U zEh*E7b4@*d+m-?y!c2)@nj59y8ec?$?Q6pL_{T4*9}~|L4nGi!tc`{IQXm|v4N2lI zWDyH$)(1oi_2v+|#TN0lr8%U#zOA;?FGNUut>zrYp0unJq))A`%(z>S*2F{+p1^wY z3rM{~mL|A75b{Yvn#B%h)EUp!NWNU*cM9F~>^L;Tv+|?k6?P0OQ7xH5i7$w5M z`AO|{ZS1ZY+@|fK;A;bufiTvWS%)ytCvcqy;lrZ|djb}@ zg{`q|+XOLf$vw4=U#s;vEA=>9NipV+z-M1PzbOx#+0317e^ph}zfz=gcRFwDClakU zO5wBe{BO#JFZBHXd4TrdB`9=OYyb?Qi~)}TCQ*VVI(!Xhq*uW|2iSshN)h-36F8?x z!0CT6XHcF5J_cwm)O8Yk4dprT&jHrqBKRMGdjKas8GVm_IBL+_J zB?BJ;oG@^L7JR!=TPxsplwScSc+lVzeA>VX{t|`8I|&%a*@*CcfH&c6^eWmCbfFNR zV8Fn;0EZXg+=BDU2%s6iKXSkc(r=N3y8$QgO?C%x+$k^-#RVMq3fh^I2To^CIztwq zFM@P_B%C0f{Rr0}&TEeYr*m5=$}fO_wvS2s_Jk|CXZM5iA{Z6d;p0bL#-onT?GMjx zq){tZb#?@~9r)VB7FJ!dq`abp6GHxQTOhQpx}>>jZCO Date: Mon, 5 Jun 2023 15:06:32 +0200 Subject: [PATCH 03/50] Add write support for forwarder symbols. --- .../Builder/ExportAddressTableBuffer.cs | 16 ++++- .../Exports/Builder/ExportDirectoryBuffer.cs | 6 +- .../Exports/ExportDirectoryTest.cs | 72 +++++++++++++++++-- .../Runners/NativePERunner.cs | 11 +++ test/AsmResolver.Tests/Runners/PERunner.cs | 2 +- 5 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs index 8963bb8ce..73043915b 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs @@ -10,6 +10,16 @@ namespace AsmResolver.PE.Exports.Builder public class ExportAddressTableBuffer : SegmentBase { private readonly List _entries = new(); + private readonly NameTableBuffer _nameTableBuffer; + + /// + /// Constructs a new export address table buffer. + /// + /// The name table buffer to use for obtaining the RVAs of forwarder symbols. + public ExportAddressTableBuffer(NameTableBuffer nameTableBuffer) + { + _nameTableBuffer = nameTableBuffer; + } /// /// Adds a single symbol to the address table buffer. @@ -24,7 +34,11 @@ public class ExportAddressTableBuffer : SegmentBase public override void Write(IBinaryStreamWriter writer) { foreach (var entry in _entries) - writer.WriteUInt32(entry.Address.Rva); + { + writer.WriteUInt32(entry.IsForwarder + ? _nameTableBuffer.GetNameRva(entry.ForwarderName) + : entry.Address.Rva); + } } } } diff --git a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs index 8409daf39..a754c7327 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs @@ -40,8 +40,8 @@ public class ExportDirectoryBuffer : SegmentBase public ExportDirectoryBuffer() { // Initialize table buffers. - _addressTableBuffer = new ExportAddressTableBuffer(); _nameTableBuffer = new NameTableBuffer(); + _addressTableBuffer = new ExportAddressTableBuffer(_nameTableBuffer); _ordinalNamePointerTable = new OrdinalNamePointerTableBuffer(_nameTableBuffer); _contentsBuilder = new SegmentBuilder @@ -77,8 +77,12 @@ public void AddDirectory(IExportDirectory exportDirectory) { _addressTableBuffer.AddSymbol(symbol); _ordinalNamePointerTable.AddSymbol(symbol); + if (symbol.IsByName) _nameTableBuffer.AddName(symbol.Name); + + if (symbol.IsForwarder) + _nameTableBuffer.AddName(symbol.ForwarderName); } } diff --git a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs index 17d7fcc85..d6b717f30 100644 --- a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs @@ -4,12 +4,23 @@ using AsmResolver.IO; using AsmResolver.PE.DotNet.Builder; using AsmResolver.PE.Exports; +using AsmResolver.PE.Exports.Builder; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.Tests.Runners; using Xunit; namespace AsmResolver.PE.Tests.Exports { - public class ExportDirectoryTest + public class ExportDirectoryTest : IClassFixture { + private readonly TemporaryDirectoryFixture _fixture; + + public ExportDirectoryTest(TemporaryDirectoryFixture fixture) + { + _fixture = fixture; + } + [Fact] public void ReadName() { @@ -211,14 +222,67 @@ public void PersistentExportedSymbolMany() [Fact] public void ReadForwarderSymbol() { - var image = PEImage.FromBytes(Properties.Resources.ForwarderDlls_ProxyDll); + var exports = PEImage.FromBytes(Properties.Resources.ForwarderDlls_ProxyDll).Exports!; - var bar = image.Exports!.Entries.First(x=>x.Name == "Bar"); - var baz = image.Exports.Entries.First(x=>x.Name == "Baz"); + var bar = exports.Entries.First(x => x.Name == "Bar"); + var baz = exports.Entries.First(x => x.Name == "Baz"); Assert.True(bar.IsForwarder); Assert.Equal("ActualDll.Foo", bar.ForwarderName); Assert.False(baz.IsForwarder); } + + [Fact] + public void RebuildForwarderSymbol() + { + var file = PEFile.FromBytes(Properties.Resources.ForwarderDlls_ProxyDll); + + // Update a forwarder name. + var exports = PEImage.FromFile(file).Exports!; + var bar = exports.Entries.First(x => x.Name == "Bar"); + bar.ForwarderName = "ActualDll.Bar"; + + // Rebuild export directory. + var buffer = new ExportDirectoryBuffer(); + buffer.AddDirectory(exports); + + var section = new PESection( + ".export", + SectionFlags.MemoryRead | SectionFlags.ContentInitializedData, + buffer); + + // Rebuild. + file.Sections.Add(section); + file.UpdateHeaders(); + file.OptionalHeader.SetDataDirectory( + DataDirectoryIndex.ExportDirectory, + new DataDirectory(section.Rva, section.GetPhysicalSize())); + + using var stream = new MemoryStream(); + file.Write(stream); + + // Verify new forwarder symbol is present. + var newExports = PEImage.FromBytes(stream.ToArray()).Exports!; + var newBar = newExports.Entries.First(x => x.Name == "Bar"); + Assert.Equal("ActualDll.Bar", newBar.ForwarderName); + + // Try running it. + var runner = _fixture.GetRunner(); + string basePath = runner.GetTestDirectory(nameof(ExportDirectoryTest), nameof(RebuildForwarderSymbol)); + string exePath = Path.Combine(basePath, "ForwarderTest.exe"); + + System.IO.File.WriteAllBytes( + Path.Combine(basePath, "ActualDll.dll"), + Properties.Resources.ForwarderDlls_ActualDll); + System.IO.File.WriteAllBytes( + exePath, + Properties.Resources.ForwarderDlls_ForwarderTest); + System.IO.File.WriteAllBytes( + Path.Combine(basePath, "ProxyDll.dll"), + stream.ToArray()); + + string output = runner.RunAndCaptureOutput(exePath); + Assert.Equal("ActualDLL::Bar\r\nProxyDll::Baz\r\nHello World!\r\n", output); + } } } diff --git a/test/AsmResolver.Tests/Runners/NativePERunner.cs b/test/AsmResolver.Tests/Runners/NativePERunner.cs index 0edea6cd4..e9e4e8873 100644 --- a/test/AsmResolver.Tests/Runners/NativePERunner.cs +++ b/test/AsmResolver.Tests/Runners/NativePERunner.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; namespace AsmResolver.Tests.Runners @@ -20,6 +21,16 @@ protected override ProcessStartInfo GetStartInfo(string filePath, string[]? argu UseShellExecute = false }; + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + info.FileName = filePath; + } + else + { + info.FileName = "wine"; + info.ArgumentList.Add(filePath); + } + if (arguments is not null) { foreach (string argument in arguments) diff --git a/test/AsmResolver.Tests/Runners/PERunner.cs b/test/AsmResolver.Tests/Runners/PERunner.cs index c2e4ff405..ee8bc60fa 100644 --- a/test/AsmResolver.Tests/Runners/PERunner.cs +++ b/test/AsmResolver.Tests/Runners/PERunner.cs @@ -34,7 +34,7 @@ public void RebuildAndRun(PEFile peFile, string fileName, string expectedOutput, Assert.Equal(expectedOutput, actualOutput); } - protected string GetTestDirectory(string testClass, string testName) + public string GetTestDirectory(string testClass, string testName) { string path = Path.Combine(BasePath, testClass, testName); if (!Directory.Exists(path)) From ed66df6f907b69da44ebff605bb45267bc1558bf Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 5 Jun 2023 15:51:02 +0200 Subject: [PATCH 04/50] Add working directory to search paths of assembly resolver. --- src/AsmResolver.DotNet/AssemblyDefinition.cs | 2 +- src/AsmResolver.DotNet/ModuleDefinition.cs | 3 + .../Serialized/SerializedModuleDefinition.cs | 14 ++++- .../ModuleDefinitionTest.cs | 59 +++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs index e0b488f06..814fe9f9e 100644 --- a/src/AsmResolver.DotNet/AssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs @@ -245,7 +245,7 @@ public virtual bool TryGetTargetFramework(out DotNetRuntimeInfo info) if (ctor?.DeclaringType is not null && ctor.DeclaringType.IsTypeOf("System.Runtime.Versioning", nameof(TargetFrameworkAttribute)) - && CustomAttributes[i].Signature?.FixedArguments[0].Element is string name + && CustomAttributes[i].Signature?.FixedArguments[0].Element?.ToString() is { } name && DotNetRuntimeInfo.TryParse(name, out info)) { return true; diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 439a28e43..ba894c147 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -1195,14 +1195,17 @@ protected IAssemblyResolver CreateAssemblyResolver(IFileService fileService) when string.IsNullOrEmpty(DotNetCorePathProvider.DefaultInstallationPath): resolver = new DotNetFrameworkAssemblyResolver(fileService); break; + case DotNetRuntimeInfo.NetStandard when DotNetCorePathProvider.Default.TryGetLatestStandardCompatibleVersion( runtime.Version, out var coreVersion): resolver = new DotNetCoreAssemblyResolver(fileService, coreVersion); break; + case DotNetRuntimeInfo.NetCoreApp: resolver = new DotNetCoreAssemblyResolver(fileService, runtime.Version); break; + default: resolver = new DotNetFrameworkAssemblyResolver(fileService); break; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index 488090231..464553d67 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -71,10 +71,18 @@ public SerializedModuleDefinition(IPEImage peImage, ModuleReaderParameters reade // Find assembly definition and corlib assembly. Assembly = FindParentAssembly(); CorLibTypeFactory = CreateCorLibTypeFactory(); - OriginalTargetRuntime = DetectTargetRuntime(); - MetadataResolver = new DefaultMetadataResolver(CreateAssemblyResolver( - readerParameters.PEReaderParameters.FileService)); + + // Initialize metadata resolution engines. + var resolver = CreateAssemblyResolver(readerParameters.PEReaderParameters.FileService); + if (!string.IsNullOrEmpty(readerParameters.WorkingDirectory) + && resolver is AssemblyResolverBase resolverBase + && !resolverBase.SearchDirectories.Contains(readerParameters.WorkingDirectory!)) + { + resolverBase.SearchDirectories.Add(readerParameters.WorkingDirectory!); + } + + MetadataResolver = new DefaultMetadataResolver(resolver); // Prepare lazy RID lists. _fieldLists = new LazyRidListRelation(metadata, TableIndex.Field, TableIndex.TypeDef, diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index f8e171460..8d992d293 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.IO; @@ -48,6 +49,64 @@ public void LoadFromDynamicModule() Assert.Equal("ActualLibrary.dll", module.Name); } + [Fact] + public void LoadFromFileShouldPopulateResolverSearchDirectories() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromFile(path); + + Assert.Contains( + Path.GetDirectoryName(path), + ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories); + } + + [Fact] + public void LoadFromBytesShouldLeaveSearchDirectoriesEmpty() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromBytes(File.ReadAllBytes(path)); + + Assert.DoesNotContain( + Path.GetDirectoryName(path), + ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories); + } + + [Fact] + public void LoadFromBytesWithWorkingDirectoryShouldPopulateSearchDirectories() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromBytes( + File.ReadAllBytes(path), + new ModuleReaderParameters(Path.GetDirectoryName(path))); + + Assert.Contains( + Path.GetDirectoryName(path), + ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories); + } + + [Fact] + public void LoadFromFileWithSameWorkingDirectoryShouldNotPopulateSearchDirectoriesTwice() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromFile(path, + new ModuleReaderParameters(Path.GetDirectoryName(path))); + + Assert.Equal(1, ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver) + .SearchDirectories.Count(x => x == Path.GetDirectoryName(path))); + } + + [Fact] + public void LoadFromFileWithDifferentWorkingDirectoryShouldPopulateSearchDirectoriesTwice() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + string otherPath = @"C:\other\path"; + var module = ModuleDefinition.FromFile(path, new ModuleReaderParameters(otherPath)); + + var searchDirectories = ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories; + Assert.Contains(Path.GetDirectoryName(path), searchDirectories); + Assert.Contains(otherPath, searchDirectories); + } + [Fact] public void ReadNameTest() { From bbf57811bdcfdf22a77365a8aef28795d7d7a517 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 5 Jun 2023 15:51:45 +0200 Subject: [PATCH 05/50] BUGFIX: Pass module reader parameters from FromBytes to FromImage properly. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index ba894c147..e539a0197 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -74,7 +74,7 @@ public static ModuleDefinition FromBytes(byte[] buffer) => /// The module. /// Occurs when the image does not contain a valid .NET metadata directory. public static ModuleDefinition FromBytes(byte[] buffer, ModuleReaderParameters readerParameters) => - FromImage(PEImage.FromBytes(buffer, readerParameters.PEReaderParameters)); + FromImage(PEImage.FromBytes(buffer, readerParameters.PEReaderParameters), readerParameters); /// /// Reads a .NET module from the provided input file. From 3b9df57e38026410339b81b20ef22a89a9c066ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 14:59:35 +0000 Subject: [PATCH 06/50] Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.2 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.5.0 to 17.6.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.5.0...v17.6.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../AsmResolver.DotNet.Dynamic.Tests.csproj | 2 +- test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj | 2 +- test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 2 +- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- .../AsmResolver.Symbols.Pdb.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj index b71b3c0a6..7db94fe93 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 40011e503..2a3003aee 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -9,7 +9,7 @@ - + all diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index a540f1edc..187974a87 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -9,7 +9,7 @@ - + all diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index d53d560b6..a7235eb57 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index 9b7323361..d5c7b4467 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -8,7 +8,7 @@ - + all diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 74743b9c8..a4aa8976f 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -8,7 +8,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index b31d8874c..8dc0cd7f2 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,7 +8,7 @@ - + all From 09a5059c50d9722c9fd8fc132ee9919559495bb2 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 9 Jun 2023 20:43:33 +0200 Subject: [PATCH 07/50] Let native symbols provider return original input symbol if not an ImportedSymbol. --- .../Code/Native/NativeSymbolsProvider.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs index b09c1e09f..eeabe48e1 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs @@ -23,13 +23,9 @@ public class NativeSymbolsProvider : INativeSymbolsProvider private readonly List _floatingExportedSymbols = new(); /// - public ISymbol ImportSymbol(ISymbol symbol) - { - if (symbol is ImportedSymbol importedSymbol) - return GetImportedSymbol(importedSymbol); - - throw new NotSupportedException($"Symbols of type {symbol.GetType()} are not supported."); - } + public ISymbol ImportSymbol(ISymbol symbol) => symbol is ImportedSymbol importedSymbol + ? GetImportedSymbol(importedSymbol) + : symbol; private ImportedSymbol GetImportedSymbol(ImportedSymbol symbol) { From 582142876aad038721a3f6f6969f0195f99f332a Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 10 Jun 2023 15:50:05 +0200 Subject: [PATCH 08/50] Add support for CreateReaderAtFileOffset to read from EOF data. --- src/AsmResolver.PE.File/PEFile.cs | 21 ++++++++++++++++++-- test/AsmResolver.PE.File.Tests/PEFileTest.cs | 10 ++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 09d15695b..45e0b1d05 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -282,8 +282,9 @@ public bool TryCreateDataDirectoryReader(DataDirectory dataDirectory, out Binary /// public BinaryStreamReader CreateReaderAtFileOffset(uint fileOffset) { - var section = GetSectionContainingOffset(fileOffset); - return section.CreateReader(fileOffset); + return !TryCreateReaderAtFileOffset(fileOffset, out var reader) + ? throw new ArgumentOutOfRangeException(nameof(fileOffset)) + : reader; } /// @@ -295,6 +296,14 @@ public bool TryCreateReaderAtFileOffset(uint fileOffset, out BinaryStreamReader return true; } + if (EofData is IReadableSegment eofData + && fileOffset >= eofData.Offset + && fileOffset < eofData.Offset + eofData.GetPhysicalSize()) + { + reader = eofData.CreateReader(fileOffset); + return true; + } + reader = default; return false; } @@ -315,6 +324,14 @@ public bool TryCreateReaderAtFileOffset(uint fileOffset, uint size, out BinarySt return true; } + if (EofData is IReadableSegment eofData + && fileOffset >= eofData.Offset + && fileOffset < eofData.Offset + eofData.GetPhysicalSize()) + { + reader = eofData.CreateReader(fileOffset, size); + return true; + } + reader = default; return false; } diff --git a/test/AsmResolver.PE.File.Tests/PEFileTest.cs b/test/AsmResolver.PE.File.Tests/PEFileTest.cs index 7a1c47463..6a2b26281 100644 --- a/test/AsmResolver.PE.File.Tests/PEFileTest.cs +++ b/test/AsmResolver.PE.File.Tests/PEFileTest.cs @@ -185,6 +185,16 @@ public void ReadEofData() Assert.Equal(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"), data); } + [Fact] + public void ReadEofDataFromFileOffset() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_EOF); + Assert.NotNull(file.EofData); + Assert.True(file.TryCreateReaderAtFileOffset((uint) file.EofData.Offset, out var reader)); + byte[] data = reader.ReadToEnd(); + Assert.Equal(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"), data); + } + [Fact] public void AddNewEofData() { From 7d256c18961b1efdadd9a8b6484437243cbd6df9 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 10 Jun 2023 16:14:57 +0200 Subject: [PATCH 09/50] Add PEImage::Certificates. --- .../Certificates/AttributeCertificate.cs | 60 +++++++++++++++ .../Certificates/CertificateCollection.cs | 65 +++++++++++++++++ .../Certificates/CertificateRevision.cs | 18 +++++ .../Certificates/CertificateType.cs | 29 ++++++++ .../CustomAttributeCertificate.cs | 69 ++++++++++++++++++ .../Certificates/DefaultCertificateReader.cs | 21 ++++++ .../Certificates/ICertificateReader.cs | 25 +++++++ src/AsmResolver.PE/IPEImage.cs | 9 +++ src/AsmResolver.PE/PEImage.cs | 23 ++++++ src/AsmResolver.PE/PEReaderParameters.cs | 8 ++ src/AsmResolver.PE/SerializedPEImage.cs | 40 ++++++++++ .../Certificates/CertificateCollectionTest.cs | 17 +++++ .../Properties/Resources.Designer.cs | 7 ++ .../Properties/Resources.resx | 3 + .../Resources/HelloWorld.signed.exe | Bin 0 -> 12168 bytes 15 files changed, 394 insertions(+) create mode 100644 src/AsmResolver.PE/Certificates/AttributeCertificate.cs create mode 100644 src/AsmResolver.PE/Certificates/CertificateCollection.cs create mode 100644 src/AsmResolver.PE/Certificates/CertificateRevision.cs create mode 100644 src/AsmResolver.PE/Certificates/CertificateType.cs create mode 100644 src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs create mode 100644 src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs create mode 100644 src/AsmResolver.PE/Certificates/ICertificateReader.cs create mode 100644 test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.signed.exe diff --git a/src/AsmResolver.PE/Certificates/AttributeCertificate.cs b/src/AsmResolver.PE/Certificates/AttributeCertificate.cs new file mode 100644 index 000000000..c77272386 --- /dev/null +++ b/src/AsmResolver.PE/Certificates/AttributeCertificate.cs @@ -0,0 +1,60 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// When derived from this class, represents a single certificate in the attribute certificate data directory of + /// a signed portable executable file. + /// + public abstract class AttributeCertificate : SegmentBase + { + /// + /// Defines the size of a single header of a certificate. + /// + public const uint HeaderSize = + sizeof(uint) // dwLength + + sizeof(ushort) // wRevision + + sizeof(ushort) // wCertificateType + ; + + /// + /// Gets the revision of the file format that is used for this certificate. + /// + public abstract CertificateRevision Revision + { + get; + } + + /// + /// Gets the type of the certificate. + /// + public abstract CertificateType Type + { + get; + } + + /// + public override uint GetPhysicalSize() => HeaderSize + GetContentsSize(); + + /// + /// Gets the total size in bytes of the certificate contents itself, excluding the header. + /// + /// The number of bytes. + protected abstract uint GetContentsSize(); + + /// + public override void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt32(GetPhysicalSize()); + writer.WriteUInt16((ushort) Revision); + writer.WriteUInt16((ushort) Type); + WriteContents(writer); + } + + /// + /// Writes the contents of the certificate to the provided output stream. + /// + /// The output stream. + protected abstract void WriteContents(IBinaryStreamWriter writer); + } +} diff --git a/src/AsmResolver.PE/Certificates/CertificateCollection.cs b/src/AsmResolver.PE/Certificates/CertificateCollection.cs new file mode 100644 index 000000000..65b07150c --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CertificateCollection.cs @@ -0,0 +1,65 @@ +using System.Collections.ObjectModel; +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Represents the collection of attribute certificates that are stored in a signed portable executable file. + /// + public class CertificateCollection : Collection, ISegment + { + /// + public ulong Offset + { + get; + private set; + } + + /// + public uint Rva + { + get; + private set; + } + + /// + public bool CanUpdateOffsets => true; + + /// + public void UpdateOffsets(in RelocationParameters parameters) + { + Offset = parameters.Offset; + Rva = parameters.Rva; + + var current = parameters; + for (int i = 0; i < Items.Count; i++) + { + var certificate = Items[i]; + certificate.UpdateOffsets(current); + current.Advance(certificate.GetPhysicalSize().Align(8)); + } + } + + /// + public uint GetPhysicalSize() + { + uint size = 0; + for (int i = 0; i < Items.Count; i++) + size += Items[i].GetPhysicalSize().Align(8); + return size; + } + + /// + public uint GetVirtualSize() => GetPhysicalSize(); + + /// + public void Write(IBinaryStreamWriter writer) + { + for (int i = 0; i < Items.Count; i++) + { + Items[i].Write(writer); + writer.Align(8); + } + } + } +} diff --git a/src/AsmResolver.PE/Certificates/CertificateRevision.cs b/src/AsmResolver.PE/Certificates/CertificateRevision.cs new file mode 100644 index 000000000..c004b809b --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CertificateRevision.cs @@ -0,0 +1,18 @@ +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides members describing all possible file format revisions that can be used in an attribute certificate entry. + /// + public enum CertificateRevision : ushort + { + /// + /// Indicates version 1.0 of the file format is used. + /// + Revision_v1_0 = 0x0100, + + /// + /// Indicates version 2.0 of the file format is used. + /// + Revision_v2_0 = 0x0200 + } +} diff --git a/src/AsmResolver.PE/Certificates/CertificateType.cs b/src/AsmResolver.PE/Certificates/CertificateType.cs new file mode 100644 index 000000000..5d3eb83e4 --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CertificateType.cs @@ -0,0 +1,29 @@ +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides members describing all possible certificate types a single attribute certificate in a signed portable + /// executable file can contain. + /// + public enum CertificateType : ushort + { + /// + /// Indicates the contents of the attribute is a X.509 certificate. + /// + X509 = 1, + + /// + /// Indicates the contents of the attribute is a PKCS#7 SignedData structure. + /// + PkcsSignedData = 2, + + /// + /// Reserved. + /// + Reserved1 = 3, + + /// + /// Indicates the contents of the attribute is a Terminal Server Protocol stack certificate. + /// + TsStackSigned = 4 + } +} diff --git a/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs b/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs new file mode 100644 index 000000000..67bcd4cad --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs @@ -0,0 +1,69 @@ +using System.Diagnostics; +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Represents an attribute certificate that contains a custom or unsupported certificate type or file format. + /// + [DebuggerDisplay("{Type}")] + public class CustomAttributeCertificate : AttributeCertificate + { + /// + /// Creates a new custom attribute certificate with the provided contents. + /// + /// The certificate type to store. + /// The certificate data. + public CustomAttributeCertificate(CertificateType type, IReadableSegment? contents) + : this(CertificateRevision.Revision_v2_0, type, contents) + { + } + + /// + /// Creates a new custom attribute certificate with the provided contents. + /// + /// The revision of the file format to use. + /// The certificate type to store. + /// The certificate data. + public CustomAttributeCertificate(CertificateRevision revision, CertificateType type, IReadableSegment? contents) + { + Revision = revision; + Type = type; + Contents = contents; + } + + /// + public override CertificateRevision Revision + { + get; + } + + /// + public override CertificateType Type + { + get; + } + + /// + /// Gets or sets the raw contents of the certificate. + /// + public IReadableSegment? Contents + { + get; + set; + } + + /// + public override void UpdateOffsets(in RelocationParameters parameters) + { + base.UpdateOffsets(in parameters); + Contents?.UpdateOffsets(parameters.WithAdvance(HeaderSize)); + } + + /// + protected override uint GetContentsSize() => Contents?.GetPhysicalSize() ?? 0; + + /// + protected override void WriteContents(IBinaryStreamWriter writer) => Contents?.Write(writer); + } +} diff --git a/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs b/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs new file mode 100644 index 000000000..60992e9ab --- /dev/null +++ b/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs @@ -0,0 +1,21 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides a default implementation of an attribute certificate reader. This reader defaults to instantiating + /// if an unknown or unsupported file format is encountered. + /// + public class DefaultCertificateReader : ICertificateReader + { + /// + public AttributeCertificate ReadCertificate( + PEReaderContext context, + CertificateRevision revision, + CertificateType type, + BinaryStreamReader reader) + { + return new CustomAttributeCertificate(revision, type, reader.ReadSegment(reader.Length)); + } + } +} diff --git a/src/AsmResolver.PE/Certificates/ICertificateReader.cs b/src/AsmResolver.PE/Certificates/ICertificateReader.cs new file mode 100644 index 000000000..1fc1b9053 --- /dev/null +++ b/src/AsmResolver.PE/Certificates/ICertificateReader.cs @@ -0,0 +1,25 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides members for reading and interpreting the contents of an attribute certificate in a signed + /// portable executable file. + /// + public interface ICertificateReader + { + /// + /// Reads a single attribute certificate. + /// + /// The context in which the reader is situated in. + /// The file format revision to use. + /// The type of certificate to read. + /// The reader pointing to the start of the raw data of the certificate. + /// The read attribute certificate. + AttributeCertificate ReadCertificate( + PEReaderContext context, + CertificateRevision revision, + CertificateType type, + BinaryStreamReader reader); + } +} diff --git a/src/AsmResolver.PE/IPEImage.cs b/src/AsmResolver.PE/IPEImage.cs index 4a29efd91..ab54a3150 100644 --- a/src/AsmResolver.PE/IPEImage.cs +++ b/src/AsmResolver.PE/IPEImage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet; using AsmResolver.PE.Exceptions; @@ -182,5 +183,13 @@ IList DebugData get; set; } + + /// + /// Gets a collection of attribute certificates that were added to the executable. + /// + CertificateCollection Certificates + { + get; + } } } diff --git a/src/AsmResolver.PE/PEImage.cs b/src/AsmResolver.PE/PEImage.cs index 7849e172f..7f2b49481 100644 --- a/src/AsmResolver.PE/PEImage.cs +++ b/src/AsmResolver.PE/PEImage.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using AsmResolver.IO; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet; using AsmResolver.PE.Exceptions; @@ -28,6 +29,7 @@ public class PEImage : IPEImage private readonly LazyVariable _dotNetDirectory; private IList? _debugData; private readonly LazyVariable _tlsDirectory; + private CertificateCollection? _certificates; /// /// Opens a PE image from a specific file on the disk. @@ -301,6 +303,17 @@ public ITlsDirectory? TlsDirectory set => _tlsDirectory.SetValue(value); } + /// + public CertificateCollection Certificates + { + get + { + if (_certificates is null) + Interlocked.CompareExchange(ref _certificates, GetCertificates(), null); + return _certificates; + } + } + /// /// Obtains the list of modules that were imported into the PE. /// @@ -372,5 +385,15 @@ public ITlsDirectory? TlsDirectory /// This method is called upon initialization of the property. /// protected virtual ITlsDirectory? GetTlsDirectory() => null; + + /// + /// Obtains the data directory containing the attribute certificates table of the executable. + /// + /// The attribute certificates. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CertificateCollection GetCertificates() => new(); + } } diff --git a/src/AsmResolver.PE/PEReaderParameters.cs b/src/AsmResolver.PE/PEReaderParameters.cs index b8b695046..de8d3d4de 100644 --- a/src/AsmResolver.PE/PEReaderParameters.cs +++ b/src/AsmResolver.PE/PEReaderParameters.cs @@ -1,5 +1,6 @@ using System; using AsmResolver.IO; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet.Metadata; @@ -26,6 +27,7 @@ public PEReaderParameters(IErrorListener errorListener) { MetadataStreamReader = new DefaultMetadataStreamReader(); DebugDataReader = new DefaultDebugDataReader(); + CertificateReader = new DefaultCertificateReader(); ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); } @@ -56,6 +58,12 @@ public IDebugDataReader DebugDataReader set; } + public ICertificateReader CertificateReader + { + get; + set; + } + /// /// Gets the service to use for reading any additional files from the disk while reading the portable executable. /// diff --git a/src/AsmResolver.PE/SerializedPEImage.cs b/src/AsmResolver.PE/SerializedPEImage.cs index 041d5579b..416a17371 100644 --- a/src/AsmResolver.PE/SerializedPEImage.cs +++ b/src/AsmResolver.PE/SerializedPEImage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet; using AsmResolver.PE.Exceptions; @@ -146,5 +147,44 @@ protected override IList GetDebugData() return new SerializedTlsDirectory(ReaderContext, ref reader); } + + /// + protected override CertificateCollection GetCertificates() + { + var result = new CertificateCollection(); + + var dataDirectory = PEFile.OptionalHeader.GetDataDirectory(DataDirectoryIndex.CertificateDirectory); + if (!dataDirectory.IsPresentInPE) + return result; + + // Certificate directory interprets the VirtualAddress of the data directory as a file offset as opposed to + // an RVA. Hence, we cannot use the normal TryCreateDataDirectoryReader method. + if (!PEFile.TryCreateReaderAtFileOffset(dataDirectory.VirtualAddress, dataDirectory.Size, out var reader)) + return result; + + var certificateReader = ReaderContext.Parameters.CertificateReader; + + while (reader.CanRead(AttributeCertificate.HeaderSize)) + { + // Read header. + uint length = reader.ReadUInt32(); + var revision = (CertificateRevision) reader.ReadUInt16(); + var type = (CertificateType) reader.ReadUInt16(); + + // Bound contents reader to just the contents as indicated by the header. + var contentsReader = reader.ForkRelative( + AttributeCertificate.HeaderSize, + length - AttributeCertificate.HeaderSize); + + // Read it. + result.Add(certificateReader.ReadCertificate(ReaderContext, revision, type, contentsReader)); + + // Advance to next certificate in the table. + reader.RelativeOffset += length - AttributeCertificate.HeaderSize; + reader.Align(8); + } + + return result; + } } } diff --git a/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs new file mode 100644 index 000000000..294fbe4d4 --- /dev/null +++ b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs @@ -0,0 +1,17 @@ +using AsmResolver.PE.Certificates; +using Xunit; + +namespace AsmResolver.PE.Tests.Certificates +{ + public class CertificateCollectionTest + { + [Fact] + public void ReadHeader() + { + var image = PEImage.FromBytes(Properties.Resources.HelloWorld_Signed); + var certificate = Assert.Single(image.Certificates); + Assert.Equal(CertificateRevision.Revision_v2_0, certificate.Revision); + Assert.Equal(CertificateType.PkcsSignedData, certificate.Type); + } + } +} diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 4bf9bcf87..bd2e43a40 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -157,6 +157,13 @@ public static byte[] HelloWorld_UPX { } } + public static byte[] HelloWorld_Signed { + get { + object obj = ResourceManager.GetObject("HelloWorld_Signed", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] HelloWorldPortablePdb { get { object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index b7fdd3092..d916130a3 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -66,6 +66,9 @@ ..\Resources\HelloWorld.upx.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.signed.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\HelloWorld.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.signed.exe b/test/AsmResolver.PE.Tests/Resources/HelloWorld.signed.exe new file mode 100644 index 0000000000000000000000000000000000000000..48123c2e6a1bb7a48b455bc0cda9e068909544a5 GIT binary patch literal 12168 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뷛{}e+8D=QkGA~! zvV&JWqhDV8*NvxcIvqRVgXiZSiIb;qUXuL47siM79*#Vw8S=^%`cvN?4bT1E?Ms{Y z@7=NyhPPSV7Y8ehfCWYYgtIA35sjm+idY9>Bp?nb9Y zGU{eb+!i`{C!U*j5sI|v5HTf_5R!{Z8^eT_YMGfM6_sfi*NNW^HSPc6aM|haCLB7D zS^r`D;*U>-jw@!EvJSuIzjPw(^C$V-(-rmW9KEi>xQ(}`{`TR!3+kF1s`s#G%MSP_ zrA~R{zUeQT+V_sVH@)=GTago8-klHCuf1=}fkW?)z2TDy)&8%K4bDz|_VKOhi7Wqf z9Uped7jH-VpGhsfw@C5n*moLVykcx0aplz}Ue(vOt*`HqR~la7#>lsXk3Ez8#K|95 zrB7UyAE|ji@utC*FVEpG9d~6`>66T7*_%6_|6(au^vq?Me<#LG?_E3f`;|@aMSL^A zO8)%7x;4I!R6D0NuK#@Rsr~PrZ2T&?d{`&$dtF|FQmu%Z_@= oKm6|bo~k!DZBuSq_SXXgZ)X4Y&q;B!pYFN*L+$X|Q{P|rzktBYdH?_b literal 0 HcmV?d00001 From 61972f48750edc86e2153b125f915cb204dce4ae Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 10 Jun 2023 16:29:45 +0200 Subject: [PATCH 10/50] BUGFIX: update offsets of cert collection. Add writer test. --- src/AsmResolver.PE/SerializedPEImage.cs | 2 ++ .../Certificates/CertificateCollectionTest.cs | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/AsmResolver.PE/SerializedPEImage.cs b/src/AsmResolver.PE/SerializedPEImage.cs index 416a17371..4be7e1cb2 100644 --- a/src/AsmResolver.PE/SerializedPEImage.cs +++ b/src/AsmResolver.PE/SerializedPEImage.cs @@ -162,6 +162,8 @@ protected override CertificateCollection GetCertificates() if (!PEFile.TryCreateReaderAtFileOffset(dataDirectory.VirtualAddress, dataDirectory.Size, out var reader)) return result; + result.UpdateOffsets(new RelocationParameters(dataDirectory.VirtualAddress, dataDirectory.VirtualAddress)); + var certificateReader = ReaderContext.Parameters.CertificateReader; while (reader.CanRead(AttributeCertificate.HeaderSize)) diff --git a/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs index 294fbe4d4..bed9c5ed1 100644 --- a/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs +++ b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs @@ -1,4 +1,6 @@ +using System.IO; using AsmResolver.PE.Certificates; +using AsmResolver.PE.File; using Xunit; namespace AsmResolver.PE.Tests.Certificates @@ -13,5 +15,24 @@ public void ReadHeader() Assert.Equal(CertificateRevision.Revision_v2_0, certificate.Revision); Assert.Equal(CertificateType.PkcsSignedData, certificate.Type); } + + [Fact] + public void RebuildExistingSignature() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_Signed); + + var image = PEImage.FromFile(file); + var certificate = Assert.Single(image.Certificates); + file.EofData = image.Certificates; + + using var stream = new MemoryStream(); + file.Write(stream); + + var newImage = PEImage.FromBytes(stream.ToArray()); + var newCertificate = Assert.Single(newImage.Certificates); + Assert.Equal(certificate.Revision, newCertificate.Revision); + Assert.Equal(certificate.Type, newCertificate.Type); + Assert.Equal(certificate.WriteIntoArray(), newCertificate.WriteIntoArray()); + } } } From cc1e79472c46c83162067f9bdccbe2b2d6ce613e Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 12 Jun 2023 15:18:32 +0200 Subject: [PATCH 11/50] BUGFIX: Handle type specifications in GetImpliedMemoryLayout --- .../Memory/TypeMemoryLayoutDetector.cs | 6 +++ .../Memory/SequentialStructLayoutTest.cs | 45 +++++++++++-------- .../Memory/SequentialTestStructs.cs | 23 ++++++++-- .../Memory/StructLayoutTestBase.cs | 10 ++--- 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs index 2ae1e422c..588c9dae9 100644 --- a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs +++ b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs @@ -150,10 +150,16 @@ public TypeMemoryLayout VisitTypeDefOrRef(ITypeDefOrRef type) { TableIndex.TypeRef => VisitTypeReference((TypeReference) type), TableIndex.TypeDef => VisitTypeDefinition((TypeDefinition) type), + TableIndex.TypeSpec => VisitTypeSpecification((TypeSpecification) type), _ => throw new ArgumentException("Invalid type.") }; } + private TypeMemoryLayout VisitTypeSpecification(TypeSpecification type) + { + return type.Signature!.AcceptVisitor(this); + } + private TypeMemoryLayout VisitTypeReference(TypeReference type) => VisitTypeDefinition(type.Resolve() ?? throw new ArgumentException( $"Could not resolve type {type.SafeToString()}.")); diff --git a/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs b/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs index 7d15df344..dd2e84ac2 100644 --- a/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs +++ b/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; using AsmResolver.DotNet.Memory; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -51,62 +52,68 @@ public void SingleFieldSequentialStructPack1() => [Fact] public void MultipleFieldsSequentialStructDefaultPack() => VerifySize(); - + [Fact] public void MultipleFieldsSequentialStructPack1() => VerifySize(); - + [Fact] public void LargeAndSmallFieldSequentialDefaultPack() => VerifySize(); - + [Fact] public void NestedStruct1() => VerifySize(); - + [Fact] public void NestedStruct2() => VerifySize(); - + [Fact] public void NestedStructWithEnclosingPack1() => VerifySize(); - + [Fact] public void NestedStructWithNestedPack1() => VerifySize(); - + [Fact] public void NestedStructInNestedStruct() => VerifySize(); - + [Fact] - public void ThreeLevelsNestingSequentialStructDefaultPack() => + public void ThreeLevelsNestingSequentialStructDefaultPack() => VerifySize(); - + [Fact] - public void ThreeLevelsNestingSequentialStructPack1() => + public void ThreeLevelsNestingSequentialStructPack1() => VerifySize(); - + [Fact] public void ExplicitlySizedEmptyStruct() => VerifySize(); - + [Fact] public void ExplicitlySizedSingleField() => VerifySize(); - + [Fact] public void ExplicitlySizedSmallerExplicitSizeThanActualSize() => VerifySize(); - + [Fact] public void StructWithPrimitiveFieldSmallerThanPack() => VerifySize(); - + [Fact] public void StructWithStructFieldSmallerThanPack() => VerifySize(); - + [Fact] public void PackLargerThanLargestField() => VerifySize(); - + [Fact] public void PackLargerThanLargestFieldWithImplicitAlignment() => VerifySize(); + + [Fact] + public void GenericStruct() => VerifySize>(); + + [Fact] + public void GenericNestedStruct() => VerifySize.NestedStruct>(); } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs b/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs index a4b75b9f3..b24d05764 100644 --- a/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs +++ b/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs @@ -9,7 +9,7 @@ public static class SequentialTestStructs [StructLayout(LayoutKind.Sequential)] public struct EmptyStruct { - + } [StructLayout(LayoutKind.Sequential)] @@ -134,7 +134,7 @@ public struct FixedSizeStruct133 public struct PackLargerThanLargestField { public FixedSizeStruct133 Field1; - + public FixedSizeStruct133 Field2; } @@ -148,8 +148,23 @@ public struct FixedSizeStruct133WithField public struct PackLargerThanLargestFieldWithImplicitAlignment { public FixedSizeStruct133WithField Field1; - + public FixedSizeStruct133WithField Field2; } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct GenericStruct + { + public T1 Field1; + public T2 Field2; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct NestedStruct + { + public T1 Field1; + public T2 Field2; + } + } + } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs b/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs index 26a4c5f4e..9f885581a 100644 --- a/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs +++ b/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Runtime.CompilerServices; using AsmResolver.DotNet.Memory; using Xunit; @@ -17,11 +18,8 @@ protected ModuleDefinition Module get; } - protected TypeDefinition FindTestType(Type type) - { - return (TypeDefinition) Module.LookupMember(type.MetadataToken); - } - + private ITypeDescriptor FindTestType(Type type) => Module.DefaultImporter.ImportType(type); + protected void VerifySize() { var type = FindTestType(typeof(T)); @@ -30,4 +28,4 @@ protected void VerifySize() } } -} \ No newline at end of file +} From 998174082adcda0486412ec02856f1b8c266a43d Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 12 Jun 2023 15:46:18 +0200 Subject: [PATCH 12/50] Add unit test for referencing native symbols in sections. --- .../Code/Native/NativeMethodBodyTest.cs | 107 ++++++++++++++---- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index e59af51e6..814799ca8 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -3,16 +3,21 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using AsmResolver.DotNet.Code.Native; using AsmResolver.DotNet.Signatures; +using AsmResolver.Patching; using AsmResolver.PE; using AsmResolver.PE.Code; using AsmResolver.PE.DotNet; +using AsmResolver.PE.DotNet.Builder; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; using AsmResolver.PE.Imports; +using AsmResolver.PE.Platforms; using AsmResolver.Tests.Runners; using Xunit; @@ -33,17 +38,18 @@ private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) { var module = ModuleDefinition.FromBytes(Properties.Resources.TheAnswer_NetFx); - module.Attributes &= ~DotNetDirectoryFlags.ILOnly; + module.IsILOnly = false; if (is32Bit) { module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; - module.Attributes |= DotNetDirectoryFlags.Bit32Required; + module.IsBit32Required = true; } else { module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; + module.IsBit32Required = false; } var method = module @@ -53,7 +59,7 @@ private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) method.ImplAttributes |= MethodImplAttributes.Unmanaged | MethodImplAttributes.Native | MethodImplAttributes.PreserveSig; - method.DeclaringType.Methods.Remove(method); + method.DeclaringType!.Methods.Remove(method); module.GetOrCreateModuleType().Methods.Add(method); return method.NativeMethodBody = new NativeMethodBody(method); @@ -81,7 +87,7 @@ public void NativeMethodBodyShouldResultInRawCodeSegment() }; // Serialize module to PE image. - var module = body.Owner.Module; + var module = body.Owner.Module!; var image = module.ToPEImage(); // Lookup method row. @@ -291,23 +297,79 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint // Fixup address in mov instruction. body.AddressFixups.Add(new AddressFixup(fixupOffset, fixupType, messageSymbol)); + InjectCallToNativeBody(body, messageSymbol, fixupOffset, fixupType); + + // Verify. + _fixture + .GetRunner() + .RebuildAndRun(body.Owner.Module!, "StringPointer.exe", $"Hello, world!{Environment.NewLine}"); + } + + [SkippableTheory] + [InlineData( + true, + new byte[] {0xB8, 0x00, 0x00, 0x00, 0x00}, // mov eax, message + 1u, AddressFixupType.Absolute32BitAddress)] + [InlineData( + false, + new byte[] {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // mov rax, message + 2u, AddressFixupType.Absolute64BitAddress)] + public void NativeBodyWithSectionSymbol(bool is32Bit, byte[] movInstruction, uint fixupOffset, AddressFixupType fixupType) + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), NonWindowsPlatform); + + // Create native body. + var code = new List(movInstruction) + { + 0xc3 // ret + }; + + var body = CreateDummyBody(false, is32Bit); + body.Code = code.ToArray(); + + // Define new symbol. + var messageSegment = new DataSegment(Encoding.ASCII.GetBytes("Hello, world!\0")); + var messageSymbol = new Symbol(messageSegment.ToReference()); + + InjectCallToNativeBody(body, messageSymbol, fixupOffset, fixupType); + + // Add symbol to new section. + var image = body.Owner.Module!.ToPEImage(); + var file = new ManagedPEFileBuilder().CreateFile(image); + file.Sections.Add(new PESection( + ".asmres", + SectionFlags.MemoryRead | SectionFlags.ContentInitializedData, + messageSegment)); + + // Verify. + _fixture + .GetRunner() + .RebuildAndRun(file, "StringPointer.exe", $"Hello, world!{Environment.NewLine}"); + } + + private static void InjectCallToNativeBody(NativeMethodBody body, ISymbol messageSymbol, uint fixupOffset, AddressFixupType fixupType) + { + // Fixup address in mov instruction. + body.AddressFixups.Add(new AddressFixup(fixupOffset, fixupType, messageSymbol)); + // Update main to call native method, convert the returned pointer to a String, and write to stdout. - var module = body.Owner.Module; - body.Owner!.Signature!.ReturnType = body.Owner.Module!.CorLibTypeFactory.IntPtr; - var stringConstructor = new MemberReference( - module!.CorLibTypeFactory.String.Type, - ".ctor", - MethodSignature.CreateInstance( - module.CorLibTypeFactory.Void, - module.CorLibTypeFactory.Char.MakePointerType()) - ); - var writeLine = new MemberReference( - new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Console"), - "WriteLine", - MethodSignature.CreateStatic( - module.CorLibTypeFactory.Void, - module.CorLibTypeFactory.String) - ); + var module = body.Owner.Module!; + body.Owner.Signature!.ReturnType = module.CorLibTypeFactory.IntPtr; + + var stringConstructor = module.CorLibTypeFactory.String.Type + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.Char.MakePointerType() + )) + .ImportWith(module.DefaultImporter); + + var writeLine = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Console") + .CreateMemberReference("WriteLine", MethodSignature.CreateStatic( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String + )) + .ImportWith(module.DefaultImporter); var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); @@ -315,11 +377,6 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint instructions.Add(CilOpCodes.Newobj, stringConstructor); instructions.Add(CilOpCodes.Call, writeLine); instructions.Add(CilOpCodes.Ret); - - // Verify. - _fixture - .GetRunner() - .RebuildAndRun(module, "StringPointer.exe", $"Hello, world!{Environment.NewLine}"); } } } From 07087505ab67757ad3682ee20a9ab8a31a1bdd1f Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 12 Jun 2023 16:17:29 +0200 Subject: [PATCH 13/50] BUGFIX: Use Unicode encoding and do not double fixup the address. --- .../Code/Native/NativeMethodBodyTest.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index 814799ca8..5a12c217c 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -294,9 +294,6 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint // Define local symbol. var messageSymbol = new NativeLocalSymbol(body, symbolOffset); - // Fixup address in mov instruction. - body.AddressFixups.Add(new AddressFixup(fixupOffset, fixupType, messageSymbol)); - InjectCallToNativeBody(body, messageSymbol, fixupOffset, fixupType); // Verify. @@ -314,7 +311,7 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint false, new byte[] {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // mov rax, message 2u, AddressFixupType.Absolute64BitAddress)] - public void NativeBodyWithSectionSymbol(bool is32Bit, byte[] movInstruction, uint fixupOffset, AddressFixupType fixupType) + public void NativeBodyWithGlobalSymbol(bool is32Bit, byte[] movInstruction, uint fixupOffset, AddressFixupType fixupType) { Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), NonWindowsPlatform); @@ -328,7 +325,7 @@ public void NativeBodyWithSectionSymbol(bool is32Bit, byte[] movInstruction, uin body.Code = code.ToArray(); // Define new symbol. - var messageSegment = new DataSegment(Encoding.ASCII.GetBytes("Hello, world!\0")); + var messageSegment = new DataSegment(Encoding.Unicode.GetBytes("Hello, world!\0")); var messageSymbol = new Symbol(messageSegment.ToReference()); InjectCallToNativeBody(body, messageSymbol, fixupOffset, fixupType); From 65ab9363b0b557a5ef8d38790ace71e5bdd010e1 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 13 Jun 2023 11:47:53 +0200 Subject: [PATCH 14/50] Add AttributeCertificate::CreateContentReader --- .../Certificates/AttributeCertificate.cs | 15 +++++++++++++++ .../Certificates/CustomAttributeCertificate.cs | 11 +++++++++++ .../Certificates/CertificateCollectionTest.cs | 2 ++ 3 files changed, 28 insertions(+) diff --git a/src/AsmResolver.PE/Certificates/AttributeCertificate.cs b/src/AsmResolver.PE/Certificates/AttributeCertificate.cs index c77272386..23a0c224a 100644 --- a/src/AsmResolver.PE/Certificates/AttributeCertificate.cs +++ b/src/AsmResolver.PE/Certificates/AttributeCertificate.cs @@ -33,6 +33,21 @@ public abstract CertificateType Type get; } + /// + /// Gets a value indicating whether the raw contents of the signature can be read using a + /// instance. + /// + public abstract bool CanRead + { + get; + } + + /// + /// Creates a binary reader that reads the raw contents of the stored signature. + /// + /// The reader. + public abstract BinaryStreamReader CreateContentReader(); + /// public override uint GetPhysicalSize() => HeaderSize + GetContentsSize(); diff --git a/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs b/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs index 67bcd4cad..3f9ecd92b 100644 --- a/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs +++ b/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs @@ -1,4 +1,6 @@ +using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using AsmResolver.IO; namespace AsmResolver.PE.Certificates @@ -53,6 +55,15 @@ public IReadableSegment? Contents set; } + /// + [MemberNotNullWhen(true, nameof(Contents))] + public override bool CanRead => Contents is not null; + + /// + public override BinaryStreamReader CreateContentReader() => CanRead + ? Contents.CreateReader() + : throw new InvalidOperationException("Signature cannot be read."); + /// public override void UpdateOffsets(in RelocationParameters parameters) { diff --git a/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs index bed9c5ed1..a60efde68 100644 --- a/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs +++ b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs @@ -14,6 +14,7 @@ public void ReadHeader() var certificate = Assert.Single(image.Certificates); Assert.Equal(CertificateRevision.Revision_v2_0, certificate.Revision); Assert.Equal(CertificateType.PkcsSignedData, certificate.Type); + Assert.Equal(0x580u, certificate.CreateContentReader().Length); } [Fact] @@ -32,6 +33,7 @@ public void RebuildExistingSignature() var newCertificate = Assert.Single(newImage.Certificates); Assert.Equal(certificate.Revision, newCertificate.Revision); Assert.Equal(certificate.Type, newCertificate.Type); + Assert.Equal(certificate.CreateContentReader().ReadToEnd(), newCertificate.CreateContentReader().ReadToEnd()); Assert.Equal(certificate.WriteIntoArray(), newCertificate.WriteIntoArray()); } } From 7a161ef791bb82d230f3eea85ee7c5c33d0fadee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 14:59:04 +0000 Subject: [PATCH 15/50] Bump System.Text.Json from 6.0.7 to 6.0.8 Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 6.0.7 to 6.0.8. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.7...v6.0.8) --- updated-dependencies: - dependency-name: System.Text.Json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 344e5b44a..664921ec6 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -28,7 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From b31e0151509615d1b3b48441888037067bb5ed07 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 14 Jun 2023 14:30:38 +0200 Subject: [PATCH 16/50] Bump version. --- Directory.Build.props | 2 +- appveyor.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 614e3282c..9d7fc4c12 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 5.3.0 + 5.4.0 diff --git a/appveyor.yml b/appveyor.yml index 81316e3d6..5597d1b04 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 5.3.0-master-build.{build} + version: 5.4.0-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 5.3.0-dev-build.{build} + version: 5.4.0-dev-build.{build} configuration: Release skip_commits: From 8970f87cc392f3c713231c022f7598a4229b8cec Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 14 Jun 2023 14:30:51 +0200 Subject: [PATCH 17/50] Fix links and some formatting in docs. --- docs/guides/dotnet/cloning.md | 2 +- docs/guides/dotnet/dynamic-methods.md | 2 +- docs/guides/dotnet/managed-method-bodies.md | 2 +- docs/guides/dotnet/unmanaged-method-bodies.md | 2 +- docs/guides/faq.md | 6 +++--- docs/guides/pefile/sections.md | 4 ++-- docs/guides/peimage/basics.md | 4 ++-- docs/guides/peimage/dotnet.md | 2 +- docs/guides/peimage/exports.md | 3 +-- docs/guides/peimage/imports.md | 2 +- 10 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/guides/dotnet/cloning.md b/docs/guides/dotnet/cloning.md index 4ec2d8e7c..913073022 100644 --- a/docs/guides/dotnet/cloning.md +++ b/docs/guides/dotnet/cloning.md @@ -201,7 +201,7 @@ All references to methods defined in the `System.Runtime.CompilerServices` namespace will then be mapped to the appropriate method definitions if they exist in the target module. -See [Common Caveats using the Importer](/guides/dotnet/importing.html#common-caveats-using-the-importer) +See [Common Caveats using the Importer](importing.md#common-caveats-using-the-importer) for more information on reference importing and its caveats. ## Post-processing of cloned members diff --git a/docs/guides/dotnet/dynamic-methods.md b/docs/guides/dotnet/dynamic-methods.md index 290fc4d12..d97ac225c 100644 --- a/docs/guides/dotnet/dynamic-methods.md +++ b/docs/guides/dotnet/dynamic-methods.md @@ -67,6 +67,6 @@ contextModule.GetOrCreateModuleType().Methods.Add(definition); contextModule.Write("Program.patched.dll"); ``` -See [Obtaining methods and fields](/guides/dotnet/member-tree.html#obtaining-methods-and-fields) +See [Obtaining methods and fields](member-tree.md#obtaining-methods-and-fields) and [CIL Method Bodies](managed-method-bodies.md) for more information on how to use `MethodDefinition` objects. diff --git a/docs/guides/dotnet/managed-method-bodies.md b/docs/guides/dotnet/managed-method-bodies.md index b0771a84f..6cda54423 100644 --- a/docs/guides/dotnet/managed-method-bodies.md +++ b/docs/guides/dotnet/managed-method-bodies.md @@ -290,7 +290,7 @@ body.Instructions[i].ReplaceWith(CilOpCodes.Ldc_I4, 1234); ``` Since it is very common to replace instructions with a -[nop]{.title-ref}, AsmResolver also defines a special `ReplaceWithNop` +`nop`, AsmResolver also defines a special `ReplaceWithNop` helper function: ``` csharp diff --git a/docs/guides/dotnet/unmanaged-method-bodies.md b/docs/guides/dotnet/unmanaged-method-bodies.md index 9d931c300..d72caa73c 100644 --- a/docs/guides/dotnet/unmanaged-method-bodies.md +++ b/docs/guides/dotnet/unmanaged-method-bodies.md @@ -54,7 +54,7 @@ module.IsBit32Required = true; As per ECMA-335 specification, a method definition can only represent a native function via Platform Invoke (P/Invoke). While P/Invoke is usually used for importing functions from external libraries (such as -[kernel32.dll]{.title-ref}), it is also needed for implementing native +`kernel32.dll`), it is also needed for implementing native methods that are defined within the current .NET module itself. Therefore, to be able to assign a valid native body to a method, the right flags need to be set in both the `Attributes` and `ImplAttributes` diff --git a/docs/guides/faq.md b/docs/guides/faq.md index dddf2448c..4cdb63368 100644 --- a/docs/guides/faq.md +++ b/docs/guides/faq.md @@ -25,9 +25,9 @@ writing). AsmResolver often can ignore and recover from kinds of errors, but this is not enabled by default. To enable these, please refer to -[Advanced PE Image Reading](/guides/peimage/advanced-pe-reading.html#custom-error-handling) (PE) -or [Advanced Module Reading](/guides/dotnet/advanced-module-reading.html#pe-image-reading-parameters) (.NET), -and [Image Builder Diagnostics](/guides/dotnet/advanced-pe-image-building.html#image-builder-diagnostics) (.NET). +[Advanced PE Image Reading](peimage/advanced-pe-reading.md#custom-error-handling) (PE) +or [Advanced Module Reading](dotnet/advanced-module-reading.md#pe-image-reading-parameters) (.NET), +and [Image Builder Diagnostics](dotnet/advanced-pe-image-building.md#image-builder-diagnostics) (.NET). Be careful with ignoring errors though. Especially for disabling writer verification can cause the output to not work anymore unless you know what you are doing. diff --git a/docs/guides/pefile/sections.md b/docs/guides/pefile/sections.md index d8ddf9530..407239374 100644 --- a/docs/guides/pefile/sections.md +++ b/docs/guides/pefile/sections.md @@ -64,7 +64,7 @@ Some sections (such as `.data` or `.bss`) contain uninitialized data, and might be resized in virtual memory at runtime. As such, the virtual size of the contents might be different than its physical size. To make dynamically sized sections, it is possible to use the `VirtualSegment` -to decorate a normal [ISegment]{.title-ref} with a different virtual +to decorate a normal `ISegment` with a different virtual size. ``` csharp @@ -76,7 +76,7 @@ file.Sections.Add(section); ``` For more advanced section building, see -[Building Sections](/guides/peimage/pe-building.html#building-sections) +[Building Sections](../peimage/pe-building.md#building-sections) [Reading and Writing File Segments](../core/segments.md). ## Updating Section Offsets diff --git a/docs/guides/peimage/basics.md b/docs/guides/peimage/basics.md index 2853fd618..8b0e0bf75 100644 --- a/docs/guides/peimage/basics.md +++ b/docs/guides/peimage/basics.md @@ -17,8 +17,8 @@ var peImage = new PEImage(); ## Opening a PE image -Opening an image can be done through one of the [FromXXX]{.title-ref} -methods from the `PEImage` class: +Opening an image can be done through one of the `FromXXX` methods from the +`PEImage` class: ``` csharp byte[] raw = ... diff --git a/docs/guides/peimage/dotnet.md b/docs/guides/peimage/dotnet.md index fd491a183..f4106ef61 100644 --- a/docs/guides/peimage/dotnet.md +++ b/docs/guides/peimage/dotnet.md @@ -321,7 +321,7 @@ var fieldRva = new FieldRvaRow(myData.ToReference(), 0); ## TypeReference Hash (TRH) -Similar to the :ref:`pe-import-hash`, the TypeReference Hash (TRH) can be used +Similar to the [Import Hash](imports.md#import-hash), the TypeReference Hash (TRH) can be used to help identify malware families written in a .NET language. However, unlike the Import Hash, the TRH is based on the names of all imported type references instead of the symbols specified in the imports directory of the PE. This is a diff --git a/docs/guides/peimage/exports.md b/docs/guides/peimage/exports.md index 3d70c6cd2..a00d3fb75 100644 --- a/docs/guides/peimage/exports.md +++ b/docs/guides/peimage/exports.md @@ -4,8 +4,7 @@ Dynamically linked libraries (DLLs) often expose symbols through defining exports in the exports directory. The `IPEImage` interface exposes the `Exports` property, exposing a -mutable instance of [ExportDirectory]{.title-ref}, which defines the -following properties: +mutable instance of `ExportDirectory`, which defines the following properties: - `Name`: The name of the dynamically linked library. - `BaseOrdinal`: The base ordinal of all exported symbols. diff --git a/docs/guides/peimage/imports.md b/docs/guides/peimage/imports.md index 6f7d28cd7..a0699039b 100644 --- a/docs/guides/peimage/imports.md +++ b/docs/guides/peimage/imports.md @@ -78,4 +78,4 @@ While the Import Hash can be a good identifier for native PE images, for .NET images this is not the case. .NET images usually only import a single external symbol (either `mscoree.dll!_CorExeMain` or `mscoree.dll!_CorDllMain`), and as such they will almost always have the -exact same Import Hash. See [TypeReference Hash (TRH)](/guides/peimage/dotnet.html#typereference-hash-trh) for an alternative for .NET images. +exact same Import Hash. See [TypeReference Hash (TRH)](dotnet.md#typereference-hash-trh) for an alternative for .NET images. From a04f4a61ac9a9354506cf2c75ad32709c008f17f Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 18 Jun 2023 14:45:28 +0200 Subject: [PATCH 18/50] BUGFIX: Exit early when generic parameter cannot be resolved in type memory layout. --- .../Memory/TypeAlignmentDetector.cs | 25 +++++++++---- .../Memory/TypeMemoryLayoutDetector.cs | 37 ++++++++++++------- .../Memory/SequentialStructLayoutTest.cs | 13 +++++++ 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs b/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs index 1d876e020..4accf40db 100644 --- a/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs +++ b/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs @@ -51,8 +51,8 @@ public uint VisitCorLibType(CorLibTypeSignature signature) }; } - public uint VisitCustomModifierType(CustomModifierTypeSignature signature) => - signature.BaseType.AcceptVisitor(this); + public uint VisitCustomModifierType(CustomModifierTypeSignature signature) + => signature.BaseType.AcceptVisitor(this); public uint VisitGenericInstanceType(GenericInstanceTypeSignature signature) { @@ -67,15 +67,24 @@ public uint VisitGenericInstanceType(GenericInstanceTypeSignature signature) return result; } - public uint VisitGenericParameter(GenericParameterSignature signature) => - _currentGenericContext.GetTypeArgument(signature).AcceptVisitor(this); + public uint VisitGenericParameter(GenericParameterSignature signature) + { + var resolved = _currentGenericContext.GetTypeArgument(signature); + + // GenericContext::GetTypeArgument returns the same object if it could not be resolved to a concrete + // type argument. This means the generic type was not instantiated yet, or the type argument is invalid. + if (ReferenceEquals(resolved, signature)) + throw new ArgumentException($"The type parameter {signature} could not be resolved to a concrete type argument in the current context."); + + return resolved.AcceptVisitor(this); + } public uint VisitPinnedType(PinnedTypeSignature signature) => signature.BaseType.AcceptVisitor(this); public uint VisitPointerType(PointerTypeSignature signature) => PointerSize; - public uint VisitSentinelType(SentinelTypeSignature signature) => - throw new ArgumentException("Sentinel types do not have a size."); + public uint VisitSentinelType(SentinelTypeSignature signature) + => throw new ArgumentException("Sentinel types do not have a size."); public uint VisitSzArrayType(SzArrayTypeSignature signature) => PointerSize; @@ -94,8 +103,8 @@ public uint VisitTypeDefOrRef(ITypeDefOrRef type) }; } - private uint VisitTypeReference(TypeReference type) => - VisitTypeDefinition(type.Resolve() ?? throw new ArgumentException($"Could not resolve {type.SafeToString()}.")); + private uint VisitTypeReference(TypeReference type) + => VisitTypeDefinition(type.Resolve() ?? throw new ArgumentException($"Could not resolve {type.SafeToString()}.")); public uint VisitTypeDefinition(TypeDefinition type) { diff --git a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs index 588c9dae9..3dae49f64 100644 --- a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs +++ b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs @@ -112,32 +112,41 @@ public TypeMemoryLayout VisitGenericInstanceType(GenericInstanceTypeSignature si } /// - public TypeMemoryLayout VisitGenericParameter(GenericParameterSignature signature) => - _currentGenericContext.GetTypeArgument(signature).AcceptVisitor(this); + public TypeMemoryLayout VisitGenericParameter(GenericParameterSignature signature) + { + var resolved = _currentGenericContext.GetTypeArgument(signature); + + // GenericContext::GetTypeArgument returns the same object if it could not be resolved to a concrete + // type argument. This means the generic type was not instantiated yet, or the type argument is invalid. + if (ReferenceEquals(resolved, signature)) + throw new ArgumentException($"The type parameter {signature} could not be resolved to a concrete type argument in the current context."); + + return resolved.AcceptVisitor(this); + } /// - public TypeMemoryLayout VisitPinnedType(PinnedTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitPinnedType(PinnedTypeSignature signature) + => CreatePointerLayout(signature); /// - public TypeMemoryLayout VisitPointerType(PointerTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitPointerType(PointerTypeSignature signature) + => CreatePointerLayout(signature); /// - public TypeMemoryLayout VisitSentinelType(SentinelTypeSignature signature) => - throw new ArgumentException("Sentinel types do not have a size."); + public TypeMemoryLayout VisitSentinelType(SentinelTypeSignature signature) + => throw new ArgumentException("Sentinel types do not have a size."); /// - public TypeMemoryLayout VisitSzArrayType(SzArrayTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitSzArrayType(SzArrayTypeSignature signature) + => CreatePointerLayout(signature); /// - public TypeMemoryLayout VisitTypeDefOrRef(TypeDefOrRefSignature signature) => - VisitTypeDefOrRef(signature.Type); + public TypeMemoryLayout VisitTypeDefOrRef(TypeDefOrRefSignature signature) + => VisitTypeDefOrRef(signature.Type); /// - public TypeMemoryLayout VisitFunctionPointerType(FunctionPointerTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitFunctionPointerType(FunctionPointerTypeSignature signature) + => CreatePointerLayout(signature); /// /// Visits an instance of a class. diff --git a/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs b/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs index dd2e84ac2..a8a0d1fb4 100644 --- a/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs +++ b/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs @@ -115,5 +115,18 @@ public void PackLargerThanLargestFieldWithImplicitAlignment() => [Fact] public void GenericNestedStruct() => VerifySize.NestedStruct>(); + + [Fact] + public void UninstantiatedGenericStructShouldThrow() + { + Assert.Throws(() => + { + var type = Module.LookupMember( + typeof(SequentialTestStructs.GenericStruct<,>).MetadataToken + ); + + type.GetImpliedMemoryLayout(IntPtr.Size == 4); + }); + } } } From 7789f7c3d7ddddce87fa2a600749076a1ae7f4e7 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 21 Jun 2023 14:45:22 +0200 Subject: [PATCH 19/50] BUGFIX: Align every table in VersionInfo to 4-byte alignment. --- .../Version/StringTable.cs | 94 +++++++++---------- .../Version/VersionInfoResource.cs | 60 ++++++------ 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs b/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs index 3d88a0b1a..0db9a0e10 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs @@ -73,6 +73,53 @@ public class StringTable : VersionTableEntry, IEnumerable public const string SpecialBuildKey = "SpecialBuild"; + private readonly Dictionary _entries = new(); + + /// + /// Creates a new string table. + /// + /// The language identifier. + /// The code page. + public StringTable(ushort languageIdentifier, ushort codePage) + { + LanguageIdentifier = languageIdentifier; + CodePage = codePage; + } + + /// + public override string Key => $"{LanguageIdentifier:x4}{CodePage:x4}"; + + /// + /// Gets or sets the language identifier of this string table. + /// + public ushort LanguageIdentifier + { + get; + set; + } + + /// + /// Gets or sets the code page of this string table. + /// + public ushort CodePage + { + get; + set; + } + + /// + protected override VersionTableValueType ValueType => VersionTableValueType.Binary; + + /// + /// Gets or sets the value of a single field in the string table. + /// + /// The name of the field in the string table. + public string this[string key] + { + get => _entries[key]; + set => _entries[key] = value; + } + /// /// Reads a single StringTable structure from the provided input stream. /// @@ -122,53 +169,6 @@ private static KeyValuePair ReadEntry(ref BinaryStreamReader rea return new KeyValuePair(header.Key, value); } - private readonly Dictionary _entries = new Dictionary(); - - /// - /// Creates a new string table. - /// - /// The language identifier. - /// The code page. - public StringTable(ushort languageIdentifier, ushort codePage) - { - LanguageIdentifier = languageIdentifier; - CodePage = codePage; - } - - /// - public override string Key => $"{LanguageIdentifier:X4}{CodePage:X4}"; - - /// - /// Gets or sets the language identifier of this string table. - /// - public ushort LanguageIdentifier - { - get; - set; - } - - /// - /// Gets or sets the code page of this string table. - /// - public ushort CodePage - { - get; - set; - } - - /// - protected override VersionTableValueType ValueType => VersionTableValueType.Binary; - - /// - /// Gets or sets the value of a single field in the string table. - /// - /// The name of the field in the string table. - public string this[string key] - { - get => _entries[key]; - set => _entries[key] = value; - } - /// /// Adds (or overrides) a field to the string table. /// diff --git a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs index 666591d7c..f27a38e74 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs @@ -15,6 +15,34 @@ public class VersionInfoResource : VersionTableEntry, IWin32Resource /// public const string VsVersionInfoKey = "VS_VERSION_INFO"; + private FixedVersionInfo _fixedVersionInfo = new FixedVersionInfo(); + private readonly Dictionary _entries = new Dictionary(); + + /// + public override string Key => VsVersionInfoKey; + + /// + protected override VersionTableValueType ValueType => VersionTableValueType.Binary; + + /// + /// Gets the fixed version info stored in this version resource. + /// + public FixedVersionInfo FixedVersionInfo + { + get => _fixedVersionInfo; + set => _fixedVersionInfo = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Gets or sets a version table entry by its name. + /// + /// The name of the child. + public VersionTableEntry this[string name] + { + get => _entries[name]; + set => _entries[name] = value; + } + /// /// Obtains the version info resource from the provided root win32 resources directory. /// @@ -97,34 +125,6 @@ private static VersionTableEntry ReadNextEntry(ref BinaryStreamReader reader) }; } - private FixedVersionInfo _fixedVersionInfo = new FixedVersionInfo(); - private readonly Dictionary _entries = new Dictionary(); - - /// - public override string Key => VsVersionInfoKey; - - /// - protected override VersionTableValueType ValueType => VersionTableValueType.Binary; - - /// - /// Gets the fixed version info stored in this version resource. - /// - public FixedVersionInfo FixedVersionInfo - { - get => _fixedVersionInfo; - set => _fixedVersionInfo = value ?? throw new ArgumentNullException(nameof(value)); - } - - /// - /// Gets or sets a version table entry by its name. - /// - /// The name of the child. - public VersionTableEntry this[string name] - { - get => _entries[name]; - set => _entries[name] = value; - } - /// /// Gets a collection of entries stored in the version resource. /// @@ -181,9 +181,11 @@ public override uint GetPhysicalSize() protected override void WriteValue(IBinaryStreamWriter writer) { FixedVersionInfo.Write(writer); - writer.Align(4); foreach (var entry in _entries.Values) + { + writer.Align(4); entry.Write(writer); + } } /// From 2813e2be58ef52f9eef6c2e6a65a5033685b5283 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 21 Jun 2023 15:44:52 +0200 Subject: [PATCH 20/50] BUGFIX: Filter out invalid UTF-8 characters from section names. --- .../Headers/SectionHeader.cs | 69 ++++++++++-------- test/AsmResolver.PE.File.Tests/PEFileTest.cs | 14 ++++ .../Properties/Resources.Designer.cs | 10 +++ .../Properties/Resources.resx | 9 ++- .../HelloWorld.InvalidSectionName.exe | Bin 0 -> 4608 bytes 5 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 test/AsmResolver.PE.File.Tests/Resources/HelloWorld.InvalidSectionName.exe diff --git a/src/AsmResolver.PE.File/Headers/SectionHeader.cs b/src/AsmResolver.PE.File/Headers/SectionHeader.cs index f2a72e7d0..a5de4f389 100644 --- a/src/AsmResolver.PE.File/Headers/SectionHeader.cs +++ b/src/AsmResolver.PE.File/Headers/SectionHeader.cs @@ -19,35 +19,6 @@ public class SectionHeader : SegmentBase, IOffsetConverter private string _name; - /// - /// Reads a single section header at the current position of the provided input stream. - /// - /// The input stream to read from. - /// The section header that was read. - public static SectionHeader FromReader(ref BinaryStreamReader reader) - { - ulong offset = reader.Offset; - uint rva = reader.Rva; - - var nameBytes = new byte[8]; - reader.ReadBytes(nameBytes, 0, nameBytes.Length); - - return new SectionHeader(Encoding.UTF8.GetString(nameBytes).Replace("\0", ""), 0) - { - Offset = offset, - Rva = rva, - VirtualSize = reader.ReadUInt32(), - VirtualAddress = reader.ReadUInt32(), - SizeOfRawData = reader.ReadUInt32(), - PointerToRawData = reader.ReadUInt32(), - PointerToRelocations = reader.ReadUInt32(), - PointerToLineNumbers = reader.ReadUInt32(), - NumberOfRelocations = reader.ReadUInt16(), - NumberOfLineNumbers = reader.ReadUInt16(), - Characteristics = (SectionFlags) reader.ReadUInt32() - }; - } - /// /// Creates a new section header with the provided name. /// @@ -194,6 +165,46 @@ public SectionFlags Characteristics set; } + /// + /// Reads a single section header at the current position of the provided input stream. + /// + /// The input stream to read from. + /// The section header that was read. + public static SectionHeader FromReader(ref BinaryStreamReader reader) + { + ulong offset = reader.Offset; + uint rva = reader.Rva; + + // Read name field. + byte[] nameBytes = new byte[8]; + reader.ReadBytes(nameBytes, 0, nameBytes.Length); + + // Interpret as UTF-8, discarding all invalid UTF-8 characters. + string name = Encoding.UTF8.GetString(nameBytes).Replace("\xfffd", ""); + + // Trim to last null-byte if it exists. + int index = name.IndexOf('\0'); + if (index >= 0) + name = name.Remove(index); + + return new SectionHeader(name, 0) + { + Offset = offset, + Rva = rva, + + // Read remainder of section header. + VirtualSize = reader.ReadUInt32(), + VirtualAddress = reader.ReadUInt32(), + SizeOfRawData = reader.ReadUInt32(), + PointerToRawData = reader.ReadUInt32(), + PointerToRelocations = reader.ReadUInt32(), + PointerToLineNumbers = reader.ReadUInt32(), + NumberOfRelocations = reader.ReadUInt16(), + NumberOfLineNumbers = reader.ReadUInt16(), + Characteristics = (SectionFlags) reader.ReadUInt32() + }; + } + /// public override uint GetPhysicalSize() => SectionHeaderSize; diff --git a/test/AsmResolver.PE.File.Tests/PEFileTest.cs b/test/AsmResolver.PE.File.Tests/PEFileTest.cs index 7a1c47463..5ce0a2b96 100644 --- a/test/AsmResolver.PE.File.Tests/PEFileTest.cs +++ b/test/AsmResolver.PE.File.Tests/PEFileTest.cs @@ -240,5 +240,19 @@ public void RemoveExistingEofData() var newFile = PEFile.FromBytes(newFileBytes); Assert.Null(newFile.EofData); } + + [Fact] + public void ReadSections() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(new[] {".text", ".rsrc", ".reloc"}, file.Sections.Select(x => x.Name)); + } + + [Fact] + public void ReadInvalidSectionName() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_InvalidSectionName); + Assert.Equal(new[] {".text", ".rsrc", ".reloc"}, file.Sections.Select(x => x.Name)); + } } } diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs index f2f817fd5..57a37ab19 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs @@ -90,6 +90,16 @@ public static byte[] HelloWorld_EOF { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] HelloWorld_InvalidSectionName { + get { + object obj = ResourceManager.GetObject("HelloWorld_InvalidSectionName", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx index c33ee0174..b075ab67e 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx @@ -127,7 +127,10 @@ ..\Resources\HelloWorld.EOF.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\NativeMemoryDemos.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + ..\Resources\HelloWorld.InvalidSectionName.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\NativeMemoryDemos.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.InvalidSectionName.exe b/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.InvalidSectionName.exe new file mode 100644 index 0000000000000000000000000000000000000000..8947b3b639c91fdf2f4c872fc6662b32464f5d02 GIT binary patch literal 4608 zcmeHK-EUk+6+h$HuI*quQD~JENGI#G4S`&*V-uypPS*C?iNPOR@7fVzp}Bi^yuR)I zSnk|)ynw2RFR2osRBEM2eL&(5Co2 zR@!#8tVdxjvVG~;K@i5WqGZ$vq#MZmT2cC8RawKsL&sCog$1Hzv5$T~`rL0?tKFgu z?H5_10dTmZefJRniJn7eATjhLegiZ9^GMgx!RNM*a^EC6VE#H|N+v3F4tCcV&kY+St=Xf5Z4!#*1;} z1~ttZZ{mV}6yw-Nk^TXkw>5TgV=0eu0KWc?={YZ~VC06ai;MRY4KndhF-Z?3`x#DT zCNx;W#EL`7ICr8&k&ONHTY6FS(+R;aOD~8~`V~a|^m*CX61 z;2*>_NRQGrV=}SJQ4sJy6o+WS;04L+K1)xFG0*Ofc2K)#;4md_v06#(B2b`oI z0e+fZ1DvN{0)E!;i-5h?TM>d&rX2!A=-chV?n^nn5>^|YI#0fK!pL(gbV+$$co}$= z=EFdTo}$Z<8>?kEP?T4dMy+O7JT)8R)GLix(X7_WuXxRp8+Wryc2raGBGQYx7Di9) z)n9ZywW%WQhQVIpQm_?9z8ynod-v3WgIjK`5gEOXk@?WCyPjdLE6?6FnC{419v32P z-ih}L*nk~0JA(B_5WBuI{MfCyo*Q>bJ0Y{wuA*X7$I7?TIacaDJ`uu#oNrSNTCv@r zt*y2^cj&8A9hLdfosHmbH*HJn>9V~=H1&IUDv{rc%X3gOr zxkL(Iuty!>>KrnDV}*R+5hefxzlnNr^S)GwfxmzLW*+4cIrmUn*Fc)~j#N$S#eA{g z9KQL-!sglwxvRB}@(-G9BY&=(p0|%9DbJ6UXSKhs@I~#Ys2nRDmrDS1ZeT~Q(&c;@ zud2A**;F~KJXucnR1Q=;mF*H*^=gHRmu63Y>I|i;Kh#7)t3!jy)cWEYQqQAXI#5Wml8r1R|Iifd`kJ#|u=$XQ}Nkw`BaDmo= zE#ZZ>3VI1}F?p@gtG#dkEBUGtTdn$Z!B|OZQ%jPvh~EnWR&C^{MRJj+6l?~xg{;Nx zIB`WQL284mA-7T8T}T5XC;DOU6nqu%DTbB{e(f%n4j7#*-VMobiYmZ}&Y%Jd)Yg!E zs+%>M*pUqi*#)hWNjrIz`RC2NmdAN$YjVu`^*j8#*ZeH9sc8GTiTM#MboMoYTAyuq zUT+lo9GTeY2Jq4A^7L-&SkT->=XL^FS3X9IL;z=8hi6`svdSF zaii{xXlD+`l_;Jk`g7N1eEX-bO<&mcJ&Baf50#k=DoIAFzzM5J>NA;*(&DMJ8L4AC zsM=l_sF_St>CA=m!$ZSE({}nak}whInM@-J&S_^``L;ggyG|79a4SCLg#I~O`_|4x zM*4Q(ZlNl)ilSK$#-wZ;TdFENM&jljK5?nb$N-gcCbQC*II$K0V$J0u-jlTPJj$ YzlQgDlga- Date: Wed, 21 Jun 2023 16:59:52 +0200 Subject: [PATCH 21/50] Add unit test for padded version info. --- ...AsmResolver.PE.Win32Resources.Tests.csproj | 5 +- .../Properties/Resources.Designer.cs | 7 +++ .../Properties/Resources.resx | 7 ++- .../HelloWorld.PaddedVersionInfo.exe | Bin 0 -> 72192 bytes .../Version/VersionInfoResourceTest.cs | 49 +++++++++++++++++- 5 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 test/AsmResolver.PE.Win32Resources.Tests/Resources/HelloWorld.PaddedVersionInfo.exe diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index ff33ce493..ba9f10fa3 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -23,6 +23,7 @@ + @@ -40,8 +41,4 @@ - - - - diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs index 866341677..3c3cd2021 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs @@ -51,5 +51,12 @@ internal static byte[] HelloWorld { return ((byte[])(obj)); } } + + internal static byte[] HelloWorld_PaddedVersionInfo { + get { + object obj = ResourceManager.GetObject("HelloWorld_PaddedVersionInfo", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx index 4a49ff593..e6b342ebd 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx @@ -3,7 +3,7 @@ - + @@ -21,4 +21,7 @@ ..\Resources\HelloWorld.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - \ No newline at end of file + + ..\Resources\HelloWorld.PaddedVersionInfo.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Resources/HelloWorld.PaddedVersionInfo.exe b/test/AsmResolver.PE.Win32Resources.Tests/Resources/HelloWorld.PaddedVersionInfo.exe new file mode 100644 index 0000000000000000000000000000000000000000..4391dfd46fd53c9341063e1f558787412196f6bc GIT binary patch literal 72192 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 literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs index a9699d22b..296f352fd 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs +++ b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs @@ -2,13 +2,24 @@ using System.IO; using AsmResolver.IO; using AsmResolver.PE.DotNet.Builder; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources.Builder; using AsmResolver.PE.Win32Resources.Version; +using AsmResolver.Tests.Runners; using Xunit; namespace AsmResolver.PE.Win32Resources.Tests.Version { - public class VersionInfoSegmentTest + public class VersionInfoResourceTest : IClassFixture { + private readonly TemporaryDirectoryFixture _fixture; + + public VersionInfoResourceTest(TemporaryDirectoryFixture fixture) + { + _fixture = fixture; + } + [Fact] public void ReadFixedVersion() { @@ -192,5 +203,41 @@ public void PersistentVersionResource() Assert.Equal(versionInfo.FixedVersionInfo.ProductVersion, newVersionInfo.FixedVersionInfo.ProductVersion); } + [Fact] + public void VersionInfoAlignment() + { + // https://github.com/Washi1337/AsmResolver/issues/202 + + // Open dummy + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_PaddedVersionInfo); + var image = PEImage.FromFile(file); + + // Update version info. + var versionInfo = VersionInfoResource.FromDirectory(image.Resources!)!; + var info = versionInfo.GetChild(StringFileInfo.StringFileInfoKey); + info.Tables[0][StringTable.FileDescriptionKey] = "This is a test application"; + versionInfo.WriteToDirectory(image.Resources!); + + // Replace section. + var resourceBuffer = new ResourceDirectoryBuffer(); + resourceBuffer.AddDirectory(image.Resources!); + + var section = file.GetSectionContainingRva(file.OptionalHeader.GetDataDirectory(DataDirectoryIndex.ResourceDirectory).VirtualAddress); + section.Contents = resourceBuffer; + + file.UpdateHeaders(); + file.OptionalHeader.SetDataDirectory(DataDirectoryIndex.ResourceDirectory, + new DataDirectory(resourceBuffer.Rva, resourceBuffer.GetPhysicalSize())); + + // Rebuild + using var stream = new MemoryStream(); + file.Write(stream); + + // Reopen and verify. + var newImage = PEImage.FromBytes(stream.ToArray()); + var newVersionInfo = VersionInfoResource.FromDirectory(newImage.Resources!)!; + var newInfo = newVersionInfo.GetChild(StringFileInfo.StringFileInfoKey); + Assert.Equal("This is a test application", newInfo.Tables[0][StringTable.FileDescriptionKey]); + } } } From f39b806b47100fec7b7080e4813355bcef0807bd Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 24 Jun 2023 14:47:03 +0200 Subject: [PATCH 22/50] Default to 1033 language ID for new win32 resources. --- src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs | 2 +- .../Version/VersionInfoResource.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs b/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs index 9904b1a1a..0a97b0fe2 100644 --- a/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs @@ -98,7 +98,7 @@ public void WriteToDirectory(IResourceDirectory rootDirectory) foreach (var (groupEntry, iconEntry) in entry.Value.GetIconEntries()) { newIconDirectory.Entries.Add(new ResourceDirectory(groupEntry.Id) - {Entries = {new ResourceData(0u, iconEntry)}}); + {Entries = {new ResourceData(1033, iconEntry)}}); } } diff --git a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs index f27a38e74..5839d2f07 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs @@ -15,8 +15,8 @@ public class VersionInfoResource : VersionTableEntry, IWin32Resource /// public const string VsVersionInfoKey = "VS_VERSION_INFO"; - private FixedVersionInfo _fixedVersionInfo = new FixedVersionInfo(); - private readonly Dictionary _entries = new Dictionary(); + private FixedVersionInfo _fixedVersionInfo = new(); + private readonly Dictionary _entries = new(); /// public override string Key => VsVersionInfoKey; @@ -198,7 +198,7 @@ public void WriteToDirectory(IResourceDirectory rootDirectory) { new ResourceDirectory(1) { - Entries = {new ResourceData(0u, this)} + Entries = {new ResourceData(1033, this)} } } }; From 9155d463b54c4d089a7b4597ebd13c411a223a1e Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 24 Jun 2023 14:55:34 +0200 Subject: [PATCH 23/50] BUGFIX: Add Culture identifier to AssemblyDescriptor.FullName --- src/AsmResolver.DotNet/AssemblyDescriptor.cs | 8 +++-- .../AssemblyReferenceTest.cs | 30 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index 2447823a7..1a0cf1136 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -54,12 +54,16 @@ public string FullName { get { - var publicKeyToken = GetPublicKeyToken(); + string cultureString = !Utf8String.IsNullOrEmpty(Culture) + ? Culture + : "neutral"; + + byte[]? publicKeyToken = GetPublicKeyToken(); string publicKeyTokenString = publicKeyToken is not null ? string.Join(string.Empty, publicKeyToken.Select(x => x.ToString("x2"))) : "null"; - return $"{Name}, Version={Version}, PublicKeyToken={publicKeyTokenString}"; + return $"{Name}, Version={Version}, Culture={cultureString}, PublicKeyToken={publicKeyTokenString}"; } } diff --git a/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs b/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs index 24a282457..7c524b2d2 100644 --- a/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using System.Runtime.InteropServices; using Xunit; @@ -13,7 +14,7 @@ public void ReadName() var assemblyRef = module.AssemblyReferences[0]; Assert.Equal("mscorlib", assemblyRef.Name); } - + [Fact] public void ReadVersion() { @@ -21,7 +22,7 @@ public void ReadVersion() var assemblyRef = module.AssemblyReferences[0]; Assert.Equal(new Version(4,0,0,0), assemblyRef.Version); } - + [Fact] public void ReadPublicKeyOrToken() { @@ -33,6 +34,29 @@ public void ReadPublicKeyOrToken() Assert.Equal(expectedToken, assemblyRef.GetPublicKeyToken()); } + [Fact] + public void GetFullNameCultureNeutralAssembly() + { + var name = new AssemblyName(KnownCorLibs.MsCorLib_v4_0_0_0.FullName); + Assert.Equal("mscorlib", name.Name); + Assert.Equal(new Version(4,0,0,0), name.Version); + Assert.Equal(string.Empty, name.CultureName); + Assert.Equal(new byte[] {0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89}, name.GetPublicKeyToken()); + } + + [Fact] + public void GetFullNameCultureNonNeutralAssembly() + { + var assembly = new AssemblyReference("SomeAssembly", new Version(1, 2, 3, 4)); + assembly.Culture = "en-US"; + + var name = new AssemblyName(assembly.FullName); + Assert.Equal("SomeAssembly", name.Name); + Assert.Equal(new Version(1,2,3,4), name.Version); + Assert.Equal("en-US", name.CultureName); + Assert.Equal(Array.Empty(), name.GetPublicKeyToken()); + } + [Fact] public void CorLibResolution() { @@ -44,4 +68,4 @@ public void CorLibResolution() Assert.Equal(assemblyDef.Version, assemblyDef.Version); } } -} \ No newline at end of file +} From 9d8fff11fc3907aac55d54d7797d3ad674369b41 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 29 Jun 2023 13:53:05 +0200 Subject: [PATCH 24/50] Add MemberClonerListenerList and AssignTokensClonerListener. --- .../Cloning/AssignTokensClonerListener.cs | 33 ++++++++++++ .../Cloning/MemberCloner.Fields.cs | 4 +- .../Cloning/MemberCloner.Methods.cs | 4 +- .../Cloning/MemberCloner.Semantics.cs | 8 +-- .../Cloning/MemberCloner.cs | 34 ++++++++++-- .../Cloning/MemberClonerListenerList.cs | 52 +++++++++++++++++++ .../Cloning/MetadataClonerTest.cs | 25 +++++++-- 7 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs create mode 100644 src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs diff --git a/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs new file mode 100644 index 000000000..dd8ae4a90 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs @@ -0,0 +1,33 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Provides an implementation of a that pre-emptively assigns new metadata + /// tokens to the cloned metadata members using the target module's . + /// + public class AssignTokensClonerListener : MemberClonerListener + { + /// + /// Creates a new instance of the token allocator listener. + /// + /// The module that will contain the cloned members. + public AssignTokensClonerListener(ModuleDefinition targetModule) + { + TargetModule = targetModule; + } + + /// + /// Gets the module that will contain the cloned members. + /// + public ModuleDefinition TargetModule + { + get; + } + + /// + public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) + { + TargetModule.TokenAllocator.AssignNextAvailableToken((MetadataMember) cloned); + base.OnClonedMember(original, cloned); + } + } +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs index 78df400d3..d96da9443 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs @@ -42,8 +42,8 @@ private void DeepCopyFields(MemberCloneContext context) { DeepCopyField(context, field); var clonedMember = (FieldDefinition)context.ClonedMembers[field]; - _clonerListener.OnClonedMember(field, clonedMember); - _clonerListener.OnClonedField(field, clonedMember); + _listeners.OnClonedMember(field, clonedMember); + _listeners.OnClonedField(field, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs index af6085a14..205ccf9f5 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs @@ -57,8 +57,8 @@ private void DeepCopyMethods(MemberCloneContext context) { DeepCopyMethod(context, method); var clonedMember = (MethodDefinition)context.ClonedMembers[method]; - _clonerListener.OnClonedMember(method, clonedMember); - _clonerListener.OnClonedMethod(method, clonedMember); + _listeners.OnClonedMember(method, clonedMember); + _listeners.OnClonedMethod(method, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs index 7d3e5ea6b..53140cfca 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs @@ -18,8 +18,8 @@ private void DeepCopyProperties(MemberCloneContext context) declaringType.Properties.Add(clonedProperty); } var clonedMember = clonedProperty; - _clonerListener.OnClonedMember(property, clonedMember); - _clonerListener.OnClonedProperty(property, clonedMember); + _listeners.OnClonedMember(property, clonedMember); + _listeners.OnClonedProperty(property, clonedMember); } } @@ -55,8 +55,8 @@ private void DeepCopyEvents(MemberCloneContext context) declaringType.Events.Add(clonedEvent); } var clonedMember = clonedEvent; - _clonerListener.OnClonedMember(@event, clonedMember); - _clonerListener.OnClonedEvent(@event, clonedMember); + _listeners.OnClonedMember(@event, clonedMember); + _listeners.OnClonedEvent(@event, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 64acfe3a5..a6e4bde45 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -17,7 +17,7 @@ namespace AsmResolver.DotNet.Cloning /// public partial class MemberCloner { - private readonly IMemberClonerListener _clonerListener; + private readonly MemberClonerListenerList _listeners; private readonly Func? _importerFactory; private readonly ModuleDefinition _targetModule; @@ -32,7 +32,7 @@ public partial class MemberCloner /// /// The target module to copy the members into. public MemberCloner(ModuleDefinition targetModule) - : this(targetModule, (original, cloned) => { }) + : this(targetModule, null, null) { } @@ -79,7 +79,9 @@ public MemberCloner(ModuleDefinition targetModule, { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerFactory = importerFactory; - _clonerListener = clonerListener ?? CallbackClonerListener.EmptyInstance; + _listeners = new MemberClonerListenerList(); + if (clonerListener is not null) + _listeners.Add(clonerListener); } /// @@ -262,6 +264,28 @@ public MemberCloner Include(EventDefinition @event) return this; } + /// + /// Adds a member cloner listener to the cloner. + /// + /// The listener to add. + /// The metadata cloner that the listener is added to. + public MemberCloner WithListener(Action listener) + { + _listeners.Add(new CallbackClonerListener(listener)); + return this; + } + + /// + /// Adds a member cloner listener to the cloner. + /// + /// The listener to add. + /// The metadata cloner that the listener is added to. + public MemberCloner WithListener(IMemberClonerListener listener) + { + _listeners.Add(listener); + return this; + } + /// /// Clones all included members. /// @@ -313,8 +337,8 @@ private void DeepCopyTypes(MemberCloneContext context) { DeepCopyType(context, type); var clonedMember = (TypeDefinition)context.ClonedMembers[type]; - _clonerListener.OnClonedMember(type, clonedMember); - _clonerListener.OnClonedType(type, clonedMember); + _listeners.OnClonedMember(type, clonedMember); + _listeners.OnClonedType(type, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs new file mode 100644 index 000000000..6385b78a1 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs @@ -0,0 +1,52 @@ +using System.Collections.ObjectModel; + +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Wraps a list of s into a single instance of . + /// + public class MemberClonerListenerList : Collection, IMemberClonerListener + { + /// + public void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedMember(original, cloned); + } + + /// + public void OnClonedType(TypeDefinition original, TypeDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedType(original, cloned); + } + + /// + public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedMethod(original, cloned); + } + + /// + public void OnClonedField(FieldDefinition original, FieldDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedField(original, cloned); + } + + /// + public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedProperty(original, cloned); + } + + /// + public void OnClonedEvent(EventDefinition original, EventDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedEvent(original, cloned); + } + } +} diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index 050c8991a..34a29847b 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -76,7 +76,7 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef return clonedMethod; } - private static FieldDefinition CloneIntializerField(FieldInfo field, out FieldDefinition originalFieldDef) + private static FieldDefinition CloneInitializerField(FieldInfo field, out FieldDefinition originalFieldDef) { var sourceModule = ModuleDefinition.FromFile(field.Module.Assembly.Location); originalFieldDef = (FieldDefinition) sourceModule.LookupMember(field.MetadataToken); @@ -279,10 +279,10 @@ public void CloneConstant() public void CloneFieldRva() { var clonedInitializerField = - CloneIntializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field); + CloneInitializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field); - var originalData = ((IReadableSegment) field.FieldRva).ToArray(); - var newData = ((IReadableSegment) clonedInitializerField.FieldRva).ToArray(); + var originalData = ((IReadableSegment) field.FieldRva!).ToArray(); + var newData = ((IReadableSegment) clonedInitializerField.FieldRva!).ToArray(); Assert.Equal(originalData, newData); } @@ -391,5 +391,22 @@ public void CloneAndInject() Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes)); } + [Fact] + public void CloneAndInjectAndAssignToken() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var targetModule = PrepareTempModule(); + + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var result = new MemberCloner(targetModule) + .Include(type) + .WithListener(new InjectTypeClonerListener(targetModule)) + .WithListener(new AssignTokensClonerListener(targetModule)) + .Clone(); + + Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes)); + Assert.All(result.ClonedMembers, m => Assert.NotEqual(0u, ((IMetadataMember) m).MetadataToken.Rid)); + } } } From 49e2f3c7c7e30d6353d59f70356e955147adf845 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 29 Jun 2023 14:16:18 +0200 Subject: [PATCH 25/50] Rename WithListener -> AddListener. Update docs. --- docs/guides/dotnet/cloning.md | 56 +++++++++++++++---- .../Cloning/AssignTokensClonerListener.cs | 2 +- .../Cloning/MemberCloner.cs | 4 +- .../Cloning/MetadataClonerTest.cs | 4 +- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/docs/guides/dotnet/cloning.md b/docs/guides/dotnet/cloning.md index 913073022..37b54fd21 100644 --- a/docs/guides/dotnet/cloning.md +++ b/docs/guides/dotnet/cloning.md @@ -103,9 +103,8 @@ cloner.Include(rectangleType); cloner.Include(vectorType); ``` -`Include` returns the same `MemberCloner` instance. It is therefore also -possible to create a long method chain of members to include in the -cloning process. +`Include` returns the same `MemberCloner` instance, allowing for more fluent +syntax: ``` csharp cloner @@ -255,7 +254,24 @@ var cloner = new MemberCloner(destinationModule, (original, cloned) => { }); ``` -## Injecting the cloned members +Multiple listeners can also be chained together using the `AddListener` method: + +```csharp +var cloner = new MemberCloner(destinationModule); +cloner.AddListener(new MyListener1()); +cloner.AddListener(new MyListener2()); +``` + +This can also be used in the fluent syntax form: + +```csharp +var cloner = new MemberCloner(destinationModule) + .AddListener(new MyListener1()) + .AddListener(new MyListener2()); +``` + + +## Injecting cloned members The `Clone` method returns a `MemberCloneResult`, which contains a register of all members cloned by the member cloner. @@ -288,17 +304,37 @@ foreach (var clonedType in clonedTypes) destinationModule.TopLevelTypes.Add(clonedType); ``` -However, since injecting the cloned top level types is a very common -use-case for the cloner, AsmResolver defines the -`InjectTypeClonerListener` class that implements a cloner listener that -injects all top-level types automatically into the destination module. -In such a case, the code can be reduced to the following: +Injecting the cloned top level types is a very common use-case for the cloner. +AsmResolver defines the `InjectTypeClonerListener` class that implements a +cloner listener that injects all top-level types automatically into +the destination module. In such a case, the code can be reduced to the following: ``` csharp -new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule)) +new MemberCloner(destinationModule) .Include(rectangleType) .Include(vectorType) + .AddListener(new InjectTypeClonerListener(destinationModule)) .Clone(); // `destinationModule` now contains copies of `rectangleType` and `vectorType`. ``` + +AsmResolver provides another built-in listener `AssignTokensClonerListener` that +also allows for preemptively assigning new metadata tokens automatically, should +this be necessary. + +``` csharp +new MemberCloner(destinationModule) + .Include(rectangleType) + .Include(vectorType) + .AddListener(new InjectTypeClonerListener(destinationModule)) + .AddListener(new AssignTokensClonerListener(destinationModule)) + .Clone(); + +// `destinationModule` now contains copies of `rectangleType` and `vectorType` +// and all its members have been assigned a metadata token preemptively. +``` + +The `AssignTokensClonerListener` uses the target module's `TokenAllocator`. See +[Metadata Token Allocation](token-allocation.md) for more information on how this +class operates. \ No newline at end of file diff --git a/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs index dd8ae4a90..5a1651641 100644 --- a/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs +++ b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs @@ -1,7 +1,7 @@ namespace AsmResolver.DotNet.Cloning { /// - /// Provides an implementation of a that pre-emptively assigns new metadata + /// Provides an implementation of a that preemptively assigns new metadata /// tokens to the cloned metadata members using the target module's . /// public class AssignTokensClonerListener : MemberClonerListener diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index a6e4bde45..cf9862e78 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -269,7 +269,7 @@ public MemberCloner Include(EventDefinition @event) /// /// The listener to add. /// The metadata cloner that the listener is added to. - public MemberCloner WithListener(Action listener) + public MemberCloner AddListener(Action listener) { _listeners.Add(new CallbackClonerListener(listener)); return this; @@ -280,7 +280,7 @@ public MemberCloner WithListener(Action li /// /// The listener to add. /// The metadata cloner that the listener is added to. - public MemberCloner WithListener(IMemberClonerListener listener) + public MemberCloner AddListener(IMemberClonerListener listener) { _listeners.Add(listener); return this; diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index 34a29847b..88b68064c 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -401,8 +401,8 @@ public void CloneAndInjectAndAssignToken() var result = new MemberCloner(targetModule) .Include(type) - .WithListener(new InjectTypeClonerListener(targetModule)) - .WithListener(new AssignTokensClonerListener(targetModule)) + .AddListener(new InjectTypeClonerListener(targetModule)) + .AddListener(new AssignTokensClonerListener(targetModule)) .Clone(); Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes)); From 8e75696e328660d05b261e1613c0f4e90692c2a1 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 29 Jun 2023 14:55:38 +0200 Subject: [PATCH 26/50] Add DotNetRuntimeInfo::GetDefaultCorLib and KnownCorLibs::FromRuntimeInfo. --- src/AsmResolver.DotNet/DotNetRuntimeInfo.cs | 7 ++ src/AsmResolver.DotNet/KnownCorLibs.cs | 133 +++++++++++++++++--- 2 files changed, 123 insertions(+), 17 deletions(-) diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs index d289f45f5..7f26ec033 100644 --- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs +++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs @@ -89,6 +89,13 @@ public static bool TryParse(string frameworkName, out DotNetRuntimeInfo info) return true; } + /// + /// Obtains a reference to the default core lib reference of this runtime. + /// + /// The reference to the default core lib. + /// The runtime information is invalid or unsupported. + public AssemblyReference GetDefaultCorLib() => KnownCorLibs.FromRuntimeInfo(this); + /// public override string ToString() => $"{Name},Version=v{Version}"; } diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index fbc0d3893..ee0873ee6 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -24,7 +24,7 @@ public static class KnownCorLibs /// References mscorlib.dll, Version=2.0.0.0, PublicKeyToken=B77A5C561934E089. This is used by .NET assemblies /// targeting the .NET Framework 2.0, 3.0 and 3.5. /// - public static readonly AssemblyReference MsCorLib_v2_0_0_0 = new AssemblyReference("mscorlib", + public static readonly AssemblyReference MsCorLib_v2_0_0_0 = new("mscorlib", new Version(2, 0, 0, 0), false, new byte[] { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 @@ -34,7 +34,7 @@ public static class KnownCorLibs /// References mscorlib.dll, Version=4.0.0.0, PublicKeyToken=B77A5C561934E089. This is used by .NET assemblies /// targeting the .NET Framework 4.0 and later. /// - public static readonly AssemblyReference MsCorLib_v4_0_0_0 = new AssemblyReference("mscorlib", + public static readonly AssemblyReference MsCorLib_v4_0_0_0 = new("mscorlib", new Version(4, 0, 0, 0), false, new byte[] { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 @@ -44,7 +44,7 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=4.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET Core 1.0 and later. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v4_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v4_0_0_0 = new("System.Private.CoreLib", new Version(4, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E @@ -54,7 +54,7 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=5.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET 5.0. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v5_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v5_0_0_0 = new("System.Private.CoreLib", new Version(5, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E @@ -64,7 +64,7 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=6.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET 6.0. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v6_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v6_0_0_0 = new("System.Private.CoreLib", new Version(6, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E @@ -74,17 +74,37 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=7.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET 7.0. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v7_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v7_0_0_0 = new("System.Private.CoreLib", new Version(7, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E }); + /// + /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET standard 1.0 and 1.1. + /// + public static readonly AssemblyReference SystemRuntime_v4_0_0_0 = new("System.Runtime", + new Version(4, 0, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + + /// + /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET standard 1.2. + /// + public static readonly AssemblyReference SystemRuntime_v4_0_10_0 = new("System.Runtime", + new Version(4, 0, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + /// /// References System.Runtime.dll, Version=4.0.20.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET standard 1.3 and 1.4. /// - public static readonly AssemblyReference SystemRuntime_v4_0_20_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_0_20_0 = new("System.Runtime", new Version(4, 0, 20, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -92,9 +112,9 @@ public static class KnownCorLibs /// /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET - /// assemblies targeting .NET standard 1.5, 1.6 and 1.7. + /// assemblies targeting .NET standard 1.5, 1.6 and 1.7, and .NET Core 1.0 and 1.1. /// - public static readonly AssemblyReference SystemRuntime_v4_1_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_1_0_0 = new("System.Runtime", new Version(4, 1, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -102,9 +122,19 @@ public static class KnownCorLibs /// /// References System.Runtime.dll, Version=4.2.1.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET - /// assemblies targeting .NET Core 2.1. + /// assemblies targeting .NET Core 2.0. + /// + public static readonly AssemblyReference SystemRuntime_v4_2_0_0 = new("System.Runtime", + new Version(4, 2, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + + /// + /// References System.Runtime.dll, Version=4.2.1.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET Core 2.1 and 3.0. /// - public static readonly AssemblyReference SystemRuntime_v4_2_1_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_2_1_0 = new("System.Runtime", new Version(4, 2, 1, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -114,7 +144,7 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=4.2.2.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET Core 3.1. /// - public static readonly AssemblyReference SystemRuntime_v4_2_2_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_2_2_0 = new("System.Runtime", new Version(4, 2, 2, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -124,7 +154,7 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=5.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET 5.0. /// - public static readonly AssemblyReference SystemRuntime_v5_0_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v5_0_0_0 = new("System.Runtime", new Version(5, 0, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -134,7 +164,7 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=6.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET 6.0. /// - public static readonly AssemblyReference SystemRuntime_v6_0_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v6_0_0_0 = new("System.Runtime", new Version(6, 0, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -144,17 +174,27 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=7.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET 7.0. /// - public static readonly AssemblyReference SystemRuntime_v7_0_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v7_0_0_0 = new("System.Runtime", new Version(7, 0, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A }); + /// + /// References System.Runtime.dll, Version=8.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET 8.0. + /// + public static readonly AssemblyReference SystemRuntime_v8_0_0_0 = new("System.Runtime", + new Version(8, 0, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + /// /// References netstandard.dll, Version=2.0.0.0, PublicKeyToken=CC7B13FFCD2DDD51. This is used by .NET /// assemblies targeting .NET standard 2.0. /// - public static readonly AssemblyReference NetStandard_v2_0_0_0 = new AssemblyReference("netstandard", + public static readonly AssemblyReference NetStandard_v2_0_0_0 = new("netstandard", new Version(2, 0, 0, 0), false, new byte[] { 0xCC, 0x7B, 0x13, 0xFF, 0xCD, 0x2D, 0xDD, 0x51 @@ -164,7 +204,7 @@ public static class KnownCorLibs /// References netstandard.dll, Version=2.1.0.0, PublicKeyToken=CC7B13FFCD2DDD51. This is used by .NET /// assemblies targeting .NET standard 2.1. /// - public static readonly AssemblyReference NetStandard_v2_1_0_0 = new AssemblyReference("netstandard", + public static readonly AssemblyReference NetStandard_v2_1_0_0 = new("netstandard", new Version(2, 1, 0, 0), false, new byte[] { 0xCC, 0x7B, 0x13, 0xFF, 0xCD, 0x2D, 0xDD, 0x51 @@ -178,13 +218,17 @@ static KnownCorLibs() NetStandard_v2_1_0_0, MsCorLib_v2_0_0_0, MsCorLib_v4_0_0_0, + SystemRuntime_v4_0_0_0, + SystemRuntime_v4_0_10_0, SystemRuntime_v4_0_20_0, SystemRuntime_v4_1_0_0, + SystemRuntime_v4_2_0_0, SystemRuntime_v4_2_1_0, SystemRuntime_v4_2_2_0, SystemRuntime_v5_0_0_0, SystemRuntime_v6_0_0_0, SystemRuntime_v7_0_0_0, + SystemRuntime_v8_0_0_0, SystemPrivateCoreLib_v4_0_0_0, SystemPrivateCoreLib_v5_0_0_0, SystemPrivateCoreLib_v6_0_0_0, @@ -193,5 +237,60 @@ static KnownCorLibs() KnownCorLibNames = new HashSet(KnownCorLibReferences.Select(r => r.Name!.Value)); } + + /// + /// Obtains a reference to the default core lib reference for the provided .NET target runtime. + /// + /// The runtime to target. + /// The reference to the default core lib. + /// The runtime information is invalid or unsupported. + public static AssemblyReference FromRuntimeInfo(DotNetRuntimeInfo runtimeInfo) + { + if (runtimeInfo.IsNetFramework) + return SelectFrameworkCorLib(runtimeInfo.Version); + + if (runtimeInfo.IsNetStandard) + return SelectNetStandardCorLib(runtimeInfo.Version); + + if (runtimeInfo.IsNetCoreApp) + return SelectNetCoreCorLib(runtimeInfo.Version); + + throw new ArgumentException($"Invalid or unsupported runtime version {runtimeInfo}."); + } + + private static AssemblyReference SelectFrameworkCorLib(Version version) => version.Major < 4 + ? MsCorLib_v2_0_0_0 + : MsCorLib_v4_0_0_0; + + private static AssemblyReference SelectNetStandardCorLib(Version version) + { + return (version.Major, version.Minor) switch + { + (1, 0 or 1) => SystemRuntime_v4_0_0_0, + (1, 2) => SystemRuntime_v4_0_10_0, + (1, 3 or 4) => SystemRuntime_v4_0_20_0, + (1, 5 or 6 or 7) => SystemRuntime_v4_1_0_0, + (2, 0) => NetStandard_v2_0_0_0, + (2, 1) => NetStandard_v2_1_0_0, + _ => throw new ArgumentException($"Invalid or unsupported .NET standard version {version}.") + }; + } + + private static AssemblyReference SelectNetCoreCorLib(Version version) + { + return (version.Major, version.Minor) switch + { + (1, 0 or 1) => SystemRuntime_v4_1_0_0, + (2, 0) => SystemRuntime_v4_2_0_0, + (2, 1) or (3, 0) => SystemRuntime_v4_2_1_0, + (3, 1) => SystemRuntime_v4_2_2_0, + (5, 0) => SystemRuntime_v5_0_0_0, + (6, 0) => SystemRuntime_v6_0_0_0, + (7, 0) => SystemRuntime_v7_0_0_0, + (8, 0) => SystemRuntime_v8_0_0_0, + _ => throw new ArgumentException($"Invalid or unsupported .NET or .NET Core version {version}.") + }; + } + } } From 46d786caba89119ae448589c34049d07cd32c65d Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 29 Jun 2023 15:10:40 +0200 Subject: [PATCH 27/50] Add single param ReflectionAssemblyDescriptor constructor. Add DotNetRuntimeInfo::Parse. --- src/AsmResolver.DotNet/DotNetRuntimeInfo.cs | 10 ++++++++ .../ReflectionAssemblyDescriptor.cs | 16 ++++++++++-- .../DotNetRuntimeInfoTest.cs | 25 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs index 7f26ec033..00d733f67 100644 --- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs +++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs @@ -68,6 +68,16 @@ public Version Version /// public bool IsNetStandard => Name == NetStandard; + /// + /// Parses the framework name as provided in . + /// + /// The full runtime name. + /// The parsed version info. + public static DotNetRuntimeInfo Parse(string frameworkName) + { + return TryParse(frameworkName, out var info) ? info : throw new FormatException(); + } + /// /// Attempts to parse the framework name as provided in . /// diff --git a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs index 082c7f483..e339755a0 100644 --- a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs @@ -10,9 +10,21 @@ namespace AsmResolver.DotNet /// public class ReflectionAssemblyDescriptor : AssemblyDescriptor { - private readonly ModuleDefinition _parentModule; + private readonly ModuleDefinition? _parentModule; private readonly AssemblyName _assemblyName; + /// + /// Creates a new instance of the class. + /// + /// The assembly name to import. + public ReflectionAssemblyDescriptor(AssemblyName assemblyName) + : base(new MetadataToken(TableIndex.AssemblyRef, 0)) + { + _parentModule = null; + _assemblyName = assemblyName; + Version = assemblyName.Version ?? new Version(); + } + /// /// Creates a new instance of the class. /// @@ -46,6 +58,6 @@ public override AssemblyReference ImportWith(ReferenceImporter importer) => public override byte[]? GetPublicKeyToken() => _assemblyName.GetPublicKeyToken(); /// - public override AssemblyDefinition? Resolve() => _parentModule.MetadataResolver.AssemblyResolver.Resolve(this); + public override AssemblyDefinition? Resolve() => _parentModule?.MetadataResolver.AssemblyResolver.Resolve(this); } } diff --git a/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs b/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs new file mode 100644 index 000000000..206a808cf --- /dev/null +++ b/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using AsmResolver.DotNet.Signatures; +using Xunit; + +namespace AsmResolver.DotNet.Tests +{ + public class DotNetRuntimeInfoTest + { + [Theory] + [InlineData(".NETFramework,Version=v2.0", "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [InlineData(".NETFramework,Version=v3.5", "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [InlineData(".NETFramework,Version=v4.0", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [InlineData(".NETStandard,Version=v1.0", "System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [InlineData(".NETStandard,Version=v2.0", "netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")] + [InlineData(".NETCoreApp,Version=v2.0", "System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [InlineData(".NETCoreApp,Version=v5.0", "System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + public void DefaultCorLib(string name, string expectedCorLib) + { + Assert.Equal( + new ReflectionAssemblyDescriptor(new AssemblyName(expectedCorLib)), + (AssemblyDescriptor) DotNetRuntimeInfo.Parse(name).GetDefaultCorLib(), + SignatureComparer.Default); + } + } +} From 755e059c95fd09abf898d620e243a6d78a2bf6f3 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 29 Jun 2023 15:15:51 +0200 Subject: [PATCH 28/50] Update docs. --- docs/guides/dotnet/basics.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/guides/dotnet/basics.md b/docs/guides/dotnet/basics.md index a1e79b54a..3e145a98a 100644 --- a/docs/guides/dotnet/basics.md +++ b/docs/guides/dotnet/basics.md @@ -24,7 +24,16 @@ references in the `KnownCorLibs` class to target another version of the library. ``` csharp -var module = new ModuleDefinition("MyModule.exe", KnownCorLibs.SystemRuntime_v4_2_2_0); +var module = new ModuleDefinition("MyModule.dll", KnownCorLibs.SystemRuntime_v4_2_2_0); +``` + +If you have a .NET runtime identifier as specified in the +`TargetFrameworkAttribute` of an assembly, you can use the `DotNetRuntimeInfo` +structure to get the corresponding default corlib: + +``` csharp +var runtime = DotNetRuntimeInfo.Parse(".NETCoreApp,Version=v3.1"); +var module = new ModuleDefinition("MyModule.dll", runtime.GetDefaultCorLib()); ``` ## Opening a .NET module From 4fb99caa8bb1b39605cc35f319b6e734736c909b Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 29 Jun 2023 15:24:42 +0200 Subject: [PATCH 29/50] Typo in version. --- src/AsmResolver.DotNet/KnownCorLibs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index ee0873ee6..295c7cc7d 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -121,7 +121,7 @@ public static class KnownCorLibs }); /// - /// References System.Runtime.dll, Version=4.2.1.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// References System.Runtime.dll, Version=4.2.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET Core 2.0. /// public static readonly AssemblyReference SystemRuntime_v4_2_0_0 = new("System.Runtime", From 95c3f948317ac2e193e5fa762b4e9a57541cfebe Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 1 Jul 2023 13:09:59 +0200 Subject: [PATCH 30/50] Add System.Private.CoreLib 8.0 --- src/AsmResolver.DotNet/KnownCorLibs.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index 295c7cc7d..c8343284a 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -80,6 +80,16 @@ public static class KnownCorLibs 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E }); + /// + /// References System.Private.CoreLib.dll, Version=8.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET + /// assemblies targeting .NET 8.0. + /// + public static readonly AssemblyReference SystemPrivateCoreLib_v8_0_0_0 = new("System.Private.CoreLib", + new Version(8, 0, 0, 0), false, new byte[] + { + 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E + }); + /// /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET standard 1.0 and 1.1. @@ -232,7 +242,8 @@ static KnownCorLibs() SystemPrivateCoreLib_v4_0_0_0, SystemPrivateCoreLib_v5_0_0_0, SystemPrivateCoreLib_v6_0_0_0, - SystemPrivateCoreLib_v7_0_0_0 + SystemPrivateCoreLib_v7_0_0_0, + SystemPrivateCoreLib_v8_0_0_0, }; KnownCorLibNames = new HashSet(KnownCorLibReferences.Select(r => r.Name!.Value)); From 54deb6918802063268ba4c2b8ce52bba64e30b6a Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 1 Jul 2023 17:35:06 +0200 Subject: [PATCH 31/50] BUGFIX: Assign correct version to System.Runtime v4.0.10.0 known corlib. --- src/AsmResolver.DotNet/KnownCorLibs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index c8343284a..8492d7bfe 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -105,7 +105,7 @@ public static class KnownCorLibs /// assemblies targeting .NET standard 1.2. /// public static readonly AssemblyReference SystemRuntime_v4_0_10_0 = new("System.Runtime", - new Version(4, 0, 0, 0), false, new byte[] + new Version(4, 0, 10, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A }); From 72af0dfeaccf92646c826781ec3806a59c9671ed Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 1 Jul 2023 17:39:11 +0200 Subject: [PATCH 32/50] BUGFIX: Add language ID retainment of version info resources. --- .../Version/VersionInfoResource.cs | 118 +++++++++++++++--- .../Version/VersionInfoResourceTest.cs | 1 + 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs index 5839d2f07..24a2d32c1 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using AsmResolver.IO; @@ -18,6 +19,31 @@ public class VersionInfoResource : VersionTableEntry, IWin32Resource private FixedVersionInfo _fixedVersionInfo = new(); private readonly Dictionary _entries = new(); + /// + /// Creates a new empty version info resource targeting the English (United States) language identifier. + /// + public VersionInfoResource() + : this(0) + { + } + + /// + /// Creates a new empty version info resource. + /// + /// The language identifier the resource version info is targeting. + public VersionInfoResource(int lcid) + { + Lcid = lcid; + } + + /// + /// Gets the language identifier the resource version info is targeting. + /// + public int Lcid + { + get; + } + /// public override string Key => VsVersionInfoKey; @@ -44,11 +70,45 @@ public VersionTableEntry this[string name] } /// - /// Obtains the version info resource from the provided root win32 resources directory. + /// Obtains all version info resources from the provided root win32 resources directory. + /// + /// The root resources directory to extract the version info from. + /// The version info resource, or null if none was found. + public static IEnumerable FindAllFromDirectory(IResourceDirectory rootDirectory) + { + if (!rootDirectory.TryGetDirectory(ResourceType.Version, out var versionDirectory)) + return Enumerable.Empty(); + + var categoryDirectory = versionDirectory + .Entries + .OfType() + .FirstOrDefault(); + + if (categoryDirectory is null) + return Enumerable.Empty(); + + return categoryDirectory.Entries + .OfType() + .Select(FromResourceData); + } + + /// + /// Obtains the first version info resource from the provided root win32 resources directory. /// /// The root resources directory to extract the version info from. /// The version info resource, or null if none was found. public static VersionInfoResource? FromDirectory(IResourceDirectory rootDirectory) + { + return FindAllFromDirectory(rootDirectory).FirstOrDefault(); + } + + /// + /// Obtains the version info resource from the provided root win32 resources directory. + /// + /// The root resources directory to extract the version info from. + /// The language identifier to get the version info from. + /// The version info resource, or null if none was found. + public static VersionInfoResource? FromDirectory(IResourceDirectory rootDirectory, int lcid) { if (!rootDirectory.TryGetDirectory(ResourceType.Version, out var versionDirectory)) return null; @@ -61,19 +121,30 @@ public VersionTableEntry this[string name] var dataEntry = categoryDirectory ?.Entries .OfType() - .FirstOrDefault(); + .FirstOrDefault(x => x.Id == lcid); if (dataEntry is null) return null; + return FromResourceData(dataEntry); + } + + /// + /// Obtains the version info resource from the provided resource data entry. + /// + /// The data entry to extract the version info from. + /// The extracted version info resource. + public static VersionInfoResource FromResourceData(IResourceData dataEntry) + { if (dataEntry.CanRead) { var dataReader = dataEntry.CreateReader(); - return FromReader(ref dataReader); + return FromReader((int) dataEntry.Id, ref dataReader); } if (dataEntry.Contents is VersionInfoResource resource) return resource; + throw new ArgumentException("Version resource data is not readable."); } @@ -85,7 +156,18 @@ public VersionTableEntry this[string name] /// /// Occurs when the input stream does not point to a valid version resource. /// - public static VersionInfoResource FromReader(ref BinaryStreamReader reader) + public static VersionInfoResource FromReader(ref BinaryStreamReader reader) => FromReader(0, ref reader); + + /// + /// Reads a version resource from an input stream. + /// + /// The language identifier to get the version info from. + /// The input stream. + /// The parsed version resource. + /// + /// Occurs when the input stream does not point to a valid version resource. + /// + public static VersionInfoResource FromReader(int lcid, ref BinaryStreamReader reader) { ulong start = reader.Offset; @@ -94,7 +176,7 @@ public static VersionInfoResource FromReader(ref BinaryStreamReader reader) if (header.Key != VsVersionInfoKey) throw new FormatException($"Input stream does not point to a {VsVersionInfoKey} entry."); - var result = new VersionInfoResource(); + var result = new VersionInfoResource(lcid); // Read fixed version info. reader.Align(4); @@ -191,20 +273,22 @@ protected override void WriteValue(IBinaryStreamWriter writer) /// public void WriteToDirectory(IResourceDirectory rootDirectory) { - // Construct new directory. - var newVersionDirectory = new ResourceDirectory(ResourceType.Version) + // Add version directory if it doesn't exist yet. + if (!rootDirectory.TryGetDirectory(ResourceType.Version, out var versionDirectory)) { - Entries = - { - new ResourceDirectory(1) - { - Entries = {new ResourceData(1033, this)} - } - } - }; + versionDirectory = new ResourceDirectory(ResourceType.Version); + rootDirectory.Entries.Add(versionDirectory); + } + + // Add category directory if it doesn't exist yet. + if (!versionDirectory.TryGetDirectory(1, out var categoryDirectory)) + { + categoryDirectory = new ResourceDirectory(1); + versionDirectory.Entries.Add(categoryDirectory); + } - // Insert. - rootDirectory.AddOrReplaceEntry(newVersionDirectory); + // Insert / replace data entry. + categoryDirectory.AddOrReplaceEntry(new ResourceData((uint) Lcid, this)); } } } diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs index 296f352fd..3d52a5ad2 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs +++ b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs @@ -238,6 +238,7 @@ public void VersionInfoAlignment() var newVersionInfo = VersionInfoResource.FromDirectory(newImage.Resources!)!; var newInfo = newVersionInfo.GetChild(StringFileInfo.StringFileInfoKey); Assert.Equal("This is a test application", newInfo.Tables[0][StringTable.FileDescriptionKey]); + Assert.Equal(versionInfo.Lcid, newVersionInfo.Lcid); } } } From f4a83a4cba3778f64b0d7d47fbea882817a20f25 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Jul 2023 13:25:54 +0200 Subject: [PATCH 33/50] BUGFIX: Update offsets of PEFile::ExtraSectionData. --- src/AsmResolver.PE.File/PEFile.cs | 40 ++++++++++------- test/AsmResolver.PE.File.Tests/PEFileTest.cs | 41 ++++++++++++++++++ .../Properties/Resources.Designer.cs | 10 +++++ .../Properties/Resources.resx | 3 ++ .../Resources/HelloWorld.ExtraSectionData.exe | Bin 0 -> 4608 bytes 5 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 test/AsmResolver.PE.File.Tests/Resources/HelloWorld.ExtraSectionData.exe diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 45e0b1d05..f41e15716 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -406,33 +406,41 @@ public bool TryCreateReaderAtRva(uint rva, uint size, out BinaryStreamReader rea /// public void UpdateHeaders() { - var oldSections = Sections.Select(x => x.CreateHeader()).ToList(); - - FileHeader.NumberOfSections = (ushort) Sections.Count; - var relocation = new RelocationParameters(OptionalHeader.ImageBase, 0, 0, OptionalHeader.Magic == OptionalHeaderMagic.PE32); - FileHeader.UpdateOffsets(relocation.WithOffsetRva( - DosHeader.NextHeaderOffset + 4, - DosHeader.NextHeaderOffset + 4)); - OptionalHeader.UpdateOffsets(relocation.WithOffsetRva( - FileHeader.Offset + FileHeader.GetPhysicalSize(), - FileHeader.Rva + FileHeader.GetVirtualSize())); + // Update offsets of PE headers. + FileHeader.UpdateOffsets( + relocation.WithAdvance(DosHeader.NextHeaderOffset + sizeof(uint)) + ); + OptionalHeader.UpdateOffsets( + relocation.WithAdvance((uint) FileHeader.Offset + FileHeader.GetPhysicalSize()) + ); + // Sync file header fields with actual observed values. + FileHeader.NumberOfSections = (ushort) Sections.Count; FileHeader.SizeOfOptionalHeader = (ushort) OptionalHeader.GetPhysicalSize(); - OptionalHeader.SizeOfHeaders = (uint) (OptionalHeader.Offset - + FileHeader.SizeOfOptionalHeader - + SectionHeader.SectionHeaderSize * (uint) Sections.Count) - .Align(OptionalHeader.FileAlignment); + // Compute headers size, and update offsets of extra data. + uint peHeadersSize = (uint) OptionalHeader.Offset + + FileHeader.SizeOfOptionalHeader + + SectionHeader.SectionHeaderSize * (uint) Sections.Count; + ExtraSectionData?.UpdateOffsets(relocation.WithAdvance(peHeadersSize)); + + uint totalHeadersSize = peHeadersSize + (ExtraSectionData?.GetPhysicalSize() ?? 0); + OptionalHeader.SizeOfHeaders = totalHeadersSize.Align(OptionalHeader.FileAlignment); + + // Re-align sections and directories. + var oldSections = Sections.Select(x => x.CreateHeader()).ToList(); AlignSections(); - AlignDataDirectoryEntries(oldSections); + AlignDataDirectoryEntries(oldSections);; + // Determine full size of image. var lastSection = Sections[Sections.Count - 1]; OptionalHeader.SizeOfImage = lastSection.Rva - + lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment); + + lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment); + // Update EOF data offsets. EofData?.UpdateOffsets(relocation.WithOffsetRva( lastSection.Offset + lastSection.GetPhysicalSize(), OptionalHeader.SizeOfImage)); diff --git a/test/AsmResolver.PE.File.Tests/PEFileTest.cs b/test/AsmResolver.PE.File.Tests/PEFileTest.cs index 001adbf15..b1410df05 100644 --- a/test/AsmResolver.PE.File.Tests/PEFileTest.cs +++ b/test/AsmResolver.PE.File.Tests/PEFileTest.cs @@ -264,5 +264,46 @@ public void ReadInvalidSectionName() var file = PEFile.FromBytes(Properties.Resources.HelloWorld_InvalidSectionName); Assert.Equal(new[] {".text", ".rsrc", ".reloc"}, file.Sections.Select(x => x.Name)); } + + [Fact] + public void ReadExtraSectionData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_ExtraSectionData); + var reader = Assert.IsAssignableFrom(file.ExtraSectionData).CreateReader(); + Assert.Equal("Hello, world", reader.ReadAsciiString()); + } + + [Fact] + public void PersistExtraSectionData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + file.ExtraSectionData = new DataSegment(Encoding.ASCII.GetBytes("Hello, mars")); + + using var stream = new MemoryStream(); + file.Write(stream); + + var newFile = PEFile.FromBytes(stream.ToArray()); + var reader = Assert.IsAssignableFrom(newFile.ExtraSectionData).CreateReader(); + Assert.Equal("Hello, mars", reader.ReadAsciiString()); + } + + [Fact] + public void PersistLargeExtraSectionData() + { + byte[] data = Enumerable.Range(0, 255).Select(x => (byte) x).ToArray(); + + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + file.ExtraSectionData = new DataSegment(data); + + using var stream = new MemoryStream(); + file.Write(stream); + + var newFile = PEFile.FromBytes(stream.ToArray()); + var reader = Assert.IsAssignableFrom(newFile.ExtraSectionData).CreateReader(); + + byte[] actualBytes = new byte[data.Length]; + Assert.Equal(data.Length, reader.ReadBytes(actualBytes, 0, actualBytes.Length)); + Assert.Equal(data, actualBytes); + } } } diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs index 57a37ab19..7923bfd05 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs @@ -100,6 +100,16 @@ public static byte[] HelloWorld_InvalidSectionName { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] HelloWorld_ExtraSectionData { + get { + object obj = ResourceManager.GetObject("HelloWorld_ExtraSectionData", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx index b075ab67e..aaeaece36 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx @@ -130,6 +130,9 @@ ..\Resources\HelloWorld.InvalidSectionName.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.ExtraSectionData.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\NativeMemoryDemos.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.ExtraSectionData.exe b/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.ExtraSectionData.exe new file mode 100644 index 0000000000000000000000000000000000000000..1bf9c8360805ef7ba2253af0ac1027ba49294d54 GIT binary patch literal 4608 zcmeHK|8HDJ5ubHz*Kx3&D6~onq?>cvhR{5pV-uypPR{n(iNRm4zOy63LG$k2`uy7W z<#_wf@d2tLzEr9ND3w|%Qhz||AJ8I%+6pNB9Z>{Qg+OYh2nitpQuzrXejt2i-@7lV z6Iy;i^oP!QXLojHXJ%*j&8(L%U#C4p)QfiO7SU_y8FB{yXLJqh;Rk*)Oh4ZH=F!*0 z(wj$1TdtP%D6B=cFC9Aw!dO<6j2eM-1DRhfN7E^Y1s7ZP$(D0O*TdYphu9#Gs$+2LjLRGxZyk0ZDYlige_F zGQQ<7VEI9`F37=qD^gwvLH3odps_A&y&G~wvlo@;g^$RqVdPbr7jRqw>{}1@QB@M% z#AOhm(b(F51y+Pr_2c{C-EeAV%m{VD;0NXdN>n;#*$Ptaq) zqjVYgGxQ?x1pNs3bMyxA9Q_jb3ue9u+U8>=NZP?T4dMy+O7JT(*J)GLix(Tvv0uXxRp8+T8a?5L*V z1*8{+EBxec`32Wg8!FOn80=;)2Ag5z+cB87cULJmxarm!k&){tnG5~8>zT9T>u)g2Vx6 zTCF?*wdU{-UnGSu*rSjnT>_3UuF_F3Cyu?I`NiIcS6?~t^Ql+9_ouH=uM}cnk0jBD z!GQxz4)+q!z^h*?ZycVy`Ai?EPYn-YZ&!yCJ-=)Z|uY|$;j-%?lo6=SkUey9} z{FM-05Olw_GGA(6j7QS=OmnV*is6s9S$S92J-fMrE3}{2$Tmk#jtCm;#2pzz&^f%yMcOfj=z8YW*+7qIrm^%*FYM0 zPpYQ1V!l{#4&C@;VPo}$+?CpT`Q;|-$e%5z=k4Q2%5$UT8SSqrd{NsfD#uF4-$|X1kbHy;`B-#hFu|Jx!_g2U^y#!!Q5f%H&(EBUGtU9I|b-e^f`Q;U;M5x*A%wA#o~3*;hC zDd-Gn6IqMl7;!}_foo${gKwj{yWj?fPxQmyN%$(@Qw%8=^R+u@+HYjCcsC@!NvdE( zbQ%>{ptgqOQ{C8TVn;S8WEVgulXmhb&!02vS{~;it;sR=>$mxLxAj?MQ_=Qw1M4Gb z=)f!>(n$J*Juk`$s~&f zV`nv;=V-E>);^g^BJ+OslDHP2Cq(6GAU1KAw|9?g7VkNMTi(6yz0v8!hUkom;|yY_ zu{R&5>S0$BH|q9?cGhrQiQ+k;KX+ZmcYpfE)cGCXlStY8P?^r4l4PU`oUn?dKAl-F zEu1`)kvg`6s_lh=n$9$p&YV9tG&nRkWv5Rg2^E2!&NQOntai4PZ|jr3>qMarH{+8| z=%2NncECS_aMVpZWW5;t%2iA!BZ2B?(NndRn8z3#b=dG=U# zy`C9MG{jLu$NaMTfc~7yn02tNmD9i@u$j`BiPTpc@KaR_k-LqTTutc@sCA;HRRTJC zP->h8zZjO(w(_LMcsgV2#b7&pQbifraAzF6auCK%+tVuDlu Date: Sat, 8 Jul 2023 14:33:24 +0200 Subject: [PATCH 34/50] Add typeref scope tests. --- .../MetadataResolverTest.cs | 68 +++++++-- .../Properties/Resources.Designer.cs | 21 +++ .../Properties/Resources.resx | 9 ++ .../Resources/TypeRefModuleScope.exe | Bin 0 -> 2048 bytes .../TypeRefNullScope_CurrentModule.exe | Bin 0 -> 2048 bytes .../TypeRefNullScope_ExportedType.exe | Bin 0 -> 2048 bytes .../TypeReferenceTest.cs | 143 +++++++++++++++++- .../DotNet/HelloWorld/HelloWorld.csproj | 4 + .../TestBinaries/DotNet/HelloWorld/Program.cs | 5 +- 9 files changed, 230 insertions(+), 20 deletions(-) create mode 100644 test/AsmResolver.DotNet.Tests/Resources/TypeRefModuleScope.exe create mode 100644 test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_CurrentModule.exe create mode 100644 test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_ExportedType.exe diff --git a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs index 06d4d5ca7..e2dd55a28 100644 --- a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs @@ -4,6 +4,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; using Xunit; namespace AsmResolver.DotNet.Tests @@ -52,6 +53,7 @@ public void ResolveSystemObjectNetCore() var reference = new TypeReference(module.CorLibTypeFactory.CorLibScope, "System", "Object"); var definition = _coreResolver.ResolveType(reference); + Assert.NotNull(definition); Assert.True(definition.IsTypeOf(reference.Namespace, reference.Name)); } @@ -69,8 +71,8 @@ public void ResolveType() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly), - typeof(TopLevelClass1).Namespace, typeof(TopLevelClass1).Name); + var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly!), + typeof(TopLevelClass1).Namespace, nameof(TopLevelClass1)); var definition = _coreResolver.ResolveType(topLevelClass1); Assert.Equal((ITypeDefOrRef) topLevelClass1, definition, _comparer); @@ -104,7 +106,7 @@ public void ResolveTypeReferenceThenChangeDefAndResolveAgain() ITypeDefOrRef expected = new TypeReference(module.CorLibTypeFactory.CorLibScope, "System", "Object"); var reference = new TypeReference(module.CorLibTypeFactory.CorLibScope, "System", "Object"); - var definition = _fwResolver.ResolveType(reference); + var definition = _fwResolver.ResolveType(reference)!; Assert.Equal(expected, definition, _comparer); definition.Name = "String"; Assert.NotEqual(expected, _fwResolver.ResolveType(reference), _comparer); @@ -115,9 +117,9 @@ public void ResolveNestedType() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly), - typeof(TopLevelClass1).Namespace, typeof(TopLevelClass1).Name); - var nested1 = new TypeReference(topLevelClass1,null, typeof(TopLevelClass1.Nested1).Name); + var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly!), + typeof(TopLevelClass1).Namespace, nameof(TopLevelClass1)); + var nested1 = new TypeReference(topLevelClass1,null, nameof(TopLevelClass1.Nested1)); var definition = _coreResolver.ResolveType(nested1); @@ -129,16 +131,52 @@ public void ResolveNestedNestedType() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly), - typeof(TopLevelClass1).Namespace, typeof(TopLevelClass1).Name); - var nested1 = new TypeReference(topLevelClass1,null, typeof(TopLevelClass1.Nested1).Name); - var nested1nested1 = new TypeReference(nested1,null, typeof(TopLevelClass1.Nested1.Nested1Nested1).Name); + var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly!), + typeof(TopLevelClass1).Namespace, nameof(TopLevelClass1)); + var nested1 = new TypeReference(topLevelClass1,null, nameof(TopLevelClass1.Nested1)); + var nested1nested1 = new TypeReference(nested1,null, nameof(TopLevelClass1.Nested1.Nested1Nested1)); var definition = _fwResolver.ResolveType(nested1nested1); Assert.Equal((ITypeDefOrRef) nested1nested1, definition, _comparer); } + [Fact] + public void ResolveTypeWithModuleScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefModuleScope); + var reference = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + var definition = reference.Resolve(); + + Assert.NotNull(definition); + Assert.Same(module, definition.Module); + } + + [Fact] + public void ResolveTypeWithNullScopeCurrentModule() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var reference = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + var definition = reference.Resolve(); + + Assert.NotNull(definition); + Assert.Same(module, definition.Module); + } + + [Fact] + public void ResolveTypeWithNullScopeExportedType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_ExportedType); + var reference = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 1)); + + var definition = reference.Resolve(); + + Assert.NotNull(definition); + Assert.Equal("mscorlib", definition.Module!.Assembly!.Name); + } + [Fact] public void ResolveConsoleWriteLineMethod() { @@ -187,13 +225,13 @@ public void ResolveExportedMemberReference() var resolver = (AssemblyResolverBase) module.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); - resolver = (AssemblyResolverBase) assembly1.ManifestModule.MetadataResolver.AssemblyResolver; + resolver = (AssemblyResolverBase) assembly1.ManifestModule!.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); // Resolve - var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; - Assert.NotNull(((IMethodDescriptor) instructions[0].Operand).Resolve()); + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; + Assert.NotNull(((IMethodDescriptor) instructions[0].Operand!).Resolve()); } [Fact] @@ -208,10 +246,10 @@ public void MaliciousExportedTypeLoop() var resolver = (AssemblyResolverBase) module.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); - resolver = (AssemblyResolverBase) assembly1.ManifestModule.MetadataResolver.AssemblyResolver; + resolver = (AssemblyResolverBase) assembly1.ManifestModule!.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); - resolver = (AssemblyResolverBase) assembly2.ManifestModule.MetadataResolver.AssemblyResolver; + resolver = (AssemblyResolverBase) assembly2.ManifestModule!.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index e67584486..5f2f0400f 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -387,5 +387,26 @@ public static byte[] ArgListTest { return ((byte[])(obj)); } } + + public static byte[] TypeRefModuleScope { + get { + object obj = ResourceManager.GetObject("TypeRefModuleScope", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] TypeRefNullScope_CurrentModule { + get { + object obj = ResourceManager.GetObject("TypeRefNullScope_CurrentModule", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] TypeRefNullScope_ExportedType { + get { + object obj = ResourceManager.GetObject("TypeRefNullScope_ExportedType", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index 8ebc736a8..5edb05e41 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -165,4 +165,13 @@ ..\Resources\ArgListTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\TypeRefModuleScope.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\TypeRefNullScope_CurrentModule.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\TypeRefNullScope_ExportedType.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.DotNet.Tests/Resources/TypeRefModuleScope.exe b/test/AsmResolver.DotNet.Tests/Resources/TypeRefModuleScope.exe new file mode 100644 index 0000000000000000000000000000000000000000..e9d3163596fb48fa733bbec2dd761cd51619d9cd GIT binary patch literal 2048 zcmeHG&ubGw6#h0zLlSMR3euwJB&`q&F45#7sKm4-T9USqswf4MO@_L%Kf>--S`Xqy z(3>am>Phh|SUia*5&A#$=KtU&zBjvRf>Z@Bx%4sf-uHgY%=czz>-S$F4j@6Z*8_IQ zvXqtnyLv|HiF2PP@G-rc-O*~h*|mqJFS?$);k9hhZaIz{2tx{Q(-Ed47FHU|#&suyk`;@vM3KvVam<+dXZ z<)|jvuLv!LY5bq8QU5H=Erv041n@-zXz={)BntjRbe~pb^3+LY?rbbiPb)K5X)IMV zwPV=FuzV$lGkDTZP&9* z0}a=f%Pm{_-Bw#--gSJ}k}4XO?ib*$X9lunI+We^18Jk)G9Bpc!1b_VJd*7|)!y)Z zX&crypD2Eue*OLJ#rnHrU+c2-A;A}<#Ras~rzh)orb~M-6VS9XsO9YM|}e#{3pAYM@{7L{88M%FZ^eE2mk;8 literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_CurrentModule.exe b/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_CurrentModule.exe new file mode 100644 index 0000000000000000000000000000000000000000..3ade50d67f118b8b33f91d7ec3042618c51b51f5 GIT binary patch literal 2048 zcmeHG&1(}u6#s3Kh9uhh1=6DEB&`q&F45#7sH|yAv?OgIt)djn*HBmXBkXRa^&nmZ zy?GL^o)pi5#gljvq5nf~{tsT_d$XG+NLBEXOCK}ye(!7M_hx5n_g^6nAVIU&19nKW z7;63d@{Gb0=RZ&2V|q8cqg8jas}D_IbUk;&YucjSbR0JjEh)TBN0^S7U#<(=?MQum zJTnt*y|MsQwHT0gUi44fLk>r@0s?X$jhs%7fS_s6w(+~$mSkZFWf-HyPqb2hVDtrZATo8 zC@0y^2sOEB{GY5+|18WchB0&m@I?b?@civ0a{fbfpQ>LfFJE|#aKm6 z$RsvPDzdJu&kSVMbSS#*2hv8ZX*$r`f$L$p^+>h@m3!0orQNc& z`B3rW%n&_aMM3o*So|M)@!+`^ul@mE;+xssCP?i;PlY~a-h1DBZ)UzXvs-=q76|}Jnw<`? zO;(Ai^}*^TrH9Ua9m1#dPIjABcCrtiT0Vc~$yKlJ@Mhh0WxyMPdu^9nE}xmNaYwd< zF*ukxA8oxb3shJikal0kr|lw#<1CLU3J*lVTRCz#O_QcloDAg^ZzZwC3IU2MNx*ys z4C~Rg5e!T4GYga>e_vyEIqaMNTaW{(o?MFXy&h}?Vm%QYvm%-JB9q8 zPZ)WlkS|=nt#(N2r}{|V_zPf!mOWB_E$}RN)mIg36rrxY&QvO!h} z9mMA7?zR;8#R?b=xl=suL0>B^-NV3{h!s#oo|-&KT8XMsnqh$}Zx}ZPa_kjv{h&HhmpyuY_)AM1;=kn&$b$vmaZ>tftqwgn8cE21)^fP z6x{Fw;hAEYY@q$U{N~z^%b!0a@2g*$B{*2> z%jWXp>51K~Bp4gRXs%1_s`7ak_j>VH0X&(O-t4;23^ZpUjFxSqa{=7!U2^0f&Kuz2 Qzj^1z;iP}W`u}d=H|2zF@&Et; literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs index a0a5ae2e5..0c2618196 100644 --- a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Metadata.Tables; @@ -15,15 +16,146 @@ public class TypeReferenceTest public void ReadAssemblyRefScope() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); - Assert.Equal("mscorlib", typeRef.Scope.Name); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("mscorlib", scope.Name); + } + + [Fact] + public void WriteAssemblyRefScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + new AssemblyReference("SomeAssembly", new Version(1, 0, 0, 0)), + "SomeNamespace", "SomeName") + ).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("SomeAssembly", scope.Name); + } + + [Fact] + public void ReadTypeRefScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 4)); + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("DebuggableAttribute", scope.Name); + } + + [Fact] + public void WriteTypeRefScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + new TypeReference( + new AssemblyReference("SomeAssembly", new Version(1, 0, 0, 0)), + "SomeNamespace", "SomeName"), + null, + "SomeNestedType" + )).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("SomeName", scope.Name); + } + + [Fact] + public void ReadModuleScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefModuleScope); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Same(module, scope); + } + + [Fact] + public void WriteModuleScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + module, + "SomeNamepace", "SomeName") + ).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal(module.Name, scope.Name); + } + + [Fact] + public void WriteNullScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + null, + "SomeNamespace", "SomeName") + ).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + Assert.Null(typeRef.Scope); + } + + [Fact] + public void ReadNullScopeCurrentModule() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + Assert.Null(typeRef.Scope); + } + + [Fact] + public void ReadNullScopeExportedType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_ExportedType); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 1)); + + Assert.Null(typeRef.Scope); } [Fact] public void ReadName() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + Assert.Equal("Console", typeRef.Name); } @@ -31,7 +163,8 @@ public void ReadName() public void ReadNamespace() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + Assert.Equal("System", typeRef.Namespace); } @@ -41,6 +174,7 @@ public void CorLibTypeToTypeSignatureShouldReturnCorLibTypeSignature() var module = new ModuleDefinition("SomeModule"); var reference = new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Object"); var signature = Assert.IsAssignableFrom(reference.ToTypeSignature()); + Assert.Equal(ElementType.Object, signature.ElementType); } @@ -50,6 +184,7 @@ public void NonCorLibTypeToTypeSignatureShouldReturnTypeDefOrRef() var module = new ModuleDefinition("SomeModule"); var reference = new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Array"); var signature = Assert.IsAssignableFrom(reference.ToTypeSignature()); + Assert.Equal(signature.Type, reference, Comparer); } } diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index 329c82d3e..53d2dbb55 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -9,6 +9,10 @@ AnyCPU + + + none + diff --git a/test/TestBinaries/DotNet/HelloWorld/Program.cs b/test/TestBinaries/DotNet/HelloWorld/Program.cs index 575ec4cc4..c16dc7682 100644 --- a/test/TestBinaries/DotNet/HelloWorld/Program.cs +++ b/test/TestBinaries/DotNet/HelloWorld/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace HelloWorld { @@ -6,7 +7,9 @@ public class Program { private static void Main(string[] args) { - Console.WriteLine("Hello, World!"); + var list = args.ToList(); + foreach (var item in list) + Console.WriteLine(item); } } } From b36fda7d07315ce995c9cd73e363d8a1af49937a Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Jul 2023 14:37:20 +0200 Subject: [PATCH 35/50] BUGFIX: Read null scope. If scope is a module, force-reference module row 1. --- .../Builder/DotNetDirectoryBuffer.CodedIndices.cs | 2 +- src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs index 52e6c3df8..09a9f3afa 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs @@ -41,7 +41,7 @@ private uint AddResolutionScope(IResolutionScope? scope, bool allowDuplicates, b TableIndex.AssemblyRef => AddAssemblyReference(scope as AssemblyReference, allowDuplicates, preserveRid), TableIndex.TypeRef => AddTypeReference(scope as TypeReference, allowDuplicates, preserveRid), TableIndex.ModuleRef => AddModuleReference(scope as ModuleReference, allowDuplicates, preserveRid), - TableIndex.Module => 0, + TableIndex.Module => new MetadataToken(TableIndex.Module, 1), _ => throw new ArgumentOutOfRangeException(nameof(scope)) }; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs index 4e0cb839b..75bf2122a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs @@ -41,7 +41,7 @@ public SerializedTypeReference(ModuleReaderContext context, MetadataToken token, protected override IResolutionScope? GetScope() { if (_row.ResolutionScope == 0) - return _context.ParentModule; + return null; var tablesStream = _context.TablesStream; var decoder = tablesStream.GetIndexEncoder(CodedIndex.ResolutionScope); From 9ee3ba2549968bded774c52d6deed86caa4528dd Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Jul 2023 14:48:50 +0200 Subject: [PATCH 36/50] BUGFIX: Allow for typerefs with null scopes to be imported. --- src/AsmResolver.DotNet/ReferenceImporter.cs | 33 ++++++++++--------- .../TypeReferenceTest.cs | 12 ++++--- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 1f0821c10..c9c2b26c7 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -30,20 +30,12 @@ public ModuleDefinition TargetModule get; } - private static void AssertTypeIsValid(ITypeDefOrRef? type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - if (type.Scope is null) - throw new ArgumentException("Cannot import types that are not added to a module."); - } - /// /// Imports a resolution scope. /// /// The resolution scope to import. /// The imported resolution scope. - public IResolutionScope ImportScope(IResolutionScope? scope) + public IResolutionScope ImportScope(IResolutionScope scope) { if (scope is null) throw new ArgumentNullException(nameof(scope)); @@ -181,14 +173,16 @@ public ITypeDefOrRef ImportType(ITypeDefOrRef type) /// The imported type. protected virtual ITypeDefOrRef ImportType(TypeDefinition type) { - AssertTypeIsValid(type); - + if (type is null) + throw new ArgumentNullException(nameof(type)); if (type.IsImportedInModule(TargetModule)) return type; + if (((ITypeDescriptor) type).Scope is not { } scope) + throw new ArgumentException("Cannot import a type that has not been added to a module."); return new TypeReference( TargetModule, - ImportScope(((ITypeDescriptor) type).Scope), + ImportScope(scope), type.Namespace, type.Name); } @@ -200,12 +194,18 @@ protected virtual ITypeDefOrRef ImportType(TypeDefinition type) /// The imported type. protected virtual ITypeDefOrRef ImportType(TypeReference type) { - AssertTypeIsValid(type); - + if (type is null) + throw new ArgumentNullException(nameof(type)); if (type.IsImportedInModule(TargetModule)) return type; - return new TypeReference(TargetModule, ImportScope(type.Scope!), type.Namespace, type.Name); + return new TypeReference( + TargetModule, + type.Scope is not null + ? ImportScope(type.Scope) + : null, + type.Namespace, + type.Name); } /// @@ -215,7 +215,8 @@ protected virtual ITypeDefOrRef ImportType(TypeReference type) /// The imported type. protected virtual ITypeDefOrRef ImportType(TypeSpecification type) { - AssertTypeIsValid(type); + if (type is null) + throw new ArgumentNullException(nameof(type)); if (type.Signature is null) throw new ArgumentNullException(nameof(type)); diff --git a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs index 0c2618196..002801db9 100644 --- a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs @@ -31,7 +31,8 @@ public void WriteAssemblyRefScope() FieldAttributes.Static, new TypeDefOrRefSignature(new TypeReference( new AssemblyReference("SomeAssembly", new Version(1, 0, 0, 0)), - "SomeNamespace", "SomeName") + "SomeNamespace", + "SomeName") ).ImportWith(module.DefaultImporter) )); @@ -64,7 +65,8 @@ public void WriteTypeRefScope() new TypeDefOrRefSignature(new TypeReference( new TypeReference( new AssemblyReference("SomeAssembly", new Version(1, 0, 0, 0)), - "SomeNamespace", "SomeName"), + "SomeNamespace", + "SomeName"), null, "SomeNestedType" )).ImportWith(module.DefaultImporter) @@ -98,7 +100,8 @@ public void WriteModuleScope() FieldAttributes.Static, new TypeDefOrRefSignature(new TypeReference( module, - "SomeNamepace", "SomeName") + "SomeNamepace", + "SomeName") ).ImportWith(module.DefaultImporter) )); @@ -120,7 +123,8 @@ public void WriteNullScope() FieldAttributes.Static, new TypeDefOrRefSignature(new TypeReference( null, - "SomeNamespace", "SomeName") + "SomeNamespace", + "SomeName") ).ImportWith(module.DefaultImporter) )); From 07539155208731e7c276a5346b20e6d5736efa54 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Jul 2023 14:51:19 +0200 Subject: [PATCH 37/50] BUGFIX: Default to parent module if the to-be-resolved typeref's scope is null. --- .../DefaultMetadataResolver.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index 0d625ed02..e80d5cae4 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -166,8 +166,11 @@ public TypeResolution(IAssemblyResolver resolver) public TypeDefinition? ResolveTypeReference(TypeReference? reference) { - var scope = reference?.Scope; - if (reference?.Name is null || scope is null || _scopeStack.Contains(scope)) + if (reference is null) + return null; + + var scope = reference.Scope ?? reference.Module; + if (reference.Name is null || scope is null || _scopeStack.Contains(scope)) return null; _scopeStack.Push(scope); @@ -241,13 +244,6 @@ public TypeResolution(IAssemblyResolver resolver) private TypeDefinition? FindTypeInModule(ModuleDefinition module, Utf8String? ns, Utf8String name) { - for (int i = 0; i < module.ExportedTypes.Count; i++) - { - var exportedType = module.ExportedTypes[i]; - if (exportedType.IsTypeOfUtf8(ns, name)) - return ResolveExportedType(exportedType); - } - for (int i = 0; i < module.TopLevelTypes.Count; i++) { var type = module.TopLevelTypes[i]; @@ -255,6 +251,13 @@ public TypeResolution(IAssemblyResolver resolver) return type; } + for (int i = 0; i < module.ExportedTypes.Count; i++) + { + var exportedType = module.ExportedTypes[i]; + if (exportedType.IsTypeOfUtf8(ns, name)) + return ResolveExportedType(exportedType); + } + return null; } From a3864361012d21bbff1f9edbde89d9567742457b Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Jul 2023 14:59:00 +0200 Subject: [PATCH 38/50] Add sanity tests in typesig for new nullable scopes. --- .../Signatures/TypeSignatureTest.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 5c26a3518..d3afe8f78 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -4,6 +4,7 @@ using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Generics; using AsmResolver.DotNet.TestCases.Types; +using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -619,5 +620,38 @@ public void IgnorePinnedModifiers() Assert.True(type1.IsCompatibleWith(type2)); Assert.True(type2.IsCompatibleWith(type1)); } + + [Fact] + public void GetModuleOfTypeDefOrRef() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var signature = module.GetOrCreateModuleType().ToTypeSignature(); + Assert.Same(module, signature.Module); + } + + [Fact] + public void GetModuleOfTypeDefOrRefWithNullScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var signature = module + .LookupMember(new MetadataToken(TableIndex.TypeRef, 2)) + .ToTypeSignature(); + + Assert.Null(signature.Scope); + Assert.Same(module, signature.Module); + } + + [Fact] + public void GetModuleOfSpecificationTypeWithNullScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var signature = module + .LookupMember(new MetadataToken(TableIndex.TypeRef, 2)) + .ToTypeSignature() + .MakeSzArrayType(); + + Assert.Null(signature.Scope); + Assert.Same(module, signature.Module); + } } } From f3828bf921ded6002a4e2f633527ee2cf89cd89c Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 11 Jul 2023 22:20:18 +0200 Subject: [PATCH 39/50] Add failing FieldRVA test. --- .../Builder/ManagedPEFileBuilderTest.cs | 49 +++++++++++++----- .../Properties/Resources.Designer.cs | 7 +++ .../Properties/Resources.resx | 5 +- .../Resources/FieldRvaTest.exe | Bin 0 -> 2048 bytes 4 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 test/AsmResolver.PE.Tests/Resources/FieldRvaTest.exe diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs index d9678ea24..1d2ccda32 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.IO; using AsmResolver.PE.Code; using AsmResolver.PE.DotNet; using AsmResolver.PE.DotNet.Builder; @@ -21,33 +22,33 @@ public ManagedPEFileBuilderTest(TemporaryDirectoryFixture fixture) { _fixture = fixture; } - + [Fact] public void HelloWorldRebuild32BitNoChange() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld); - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() .RebuildAndRun(peFile, "HelloWorld", "Hello World!" + Environment.NewLine); } - + [Fact] public void HelloWorldRebuild64BitNoChange() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld_X64); - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() @@ -59,15 +60,15 @@ public void HelloWorld32BitTo64Bit() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld); - + // Change machine type and pe kind to 64-bit image.MachineType = MachineType.Amd64; image.PEKind = OptionalHeaderMagic.PE32Plus; - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() @@ -79,20 +80,40 @@ public void HelloWorld64BitTo32Bit() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld_X64); - + // Change machine type and pe kind to 32-bit image.MachineType = MachineType.I386; image.PEKind = OptionalHeaderMagic.PE32; - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() .RebuildAndRun(peFile, "HelloWorld", "Hello World!" + Environment.NewLine); } - + + [Fact] + public void UpdateFieldRvaRowsUnchanged() + { + var image = PEImage.FromBytes(Properties.Resources.FieldRvaTest); + + using var stream = new MemoryStream(); + var file = new ManagedPEFileBuilder(EmptyErrorListener.Instance).CreateFile(image); + file.Write(stream); + + var newImage = PEImage.FromBytes(stream.ToArray()); + var table = newImage.DotNetDirectory!.Metadata! + .GetStream() + .GetTable(); + + byte[] data = new byte[16]; + table[0].Data.CreateReader().ReadBytes(data, 0, data.Length); + Assert.Equal(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, data); + Assert.Equal(0x12345678u, table[1].Data.Rva); + } + } -} \ No newline at end of file +} diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index bd2e43a40..1ab8eda78 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -268,5 +268,12 @@ public static byte[] ForwarderDlls_ForwarderTest { return ((byte[])(obj)); } } + + public static byte[] FieldRvaTest { + get { + object obj = ResourceManager.GetObject("FieldRvaTest", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index d916130a3..ea2add3ab 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -111,7 +111,10 @@ ..\Resources\ProxyDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + ..\Resources\ForwarderTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\FieldRvaTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.PE.Tests/Resources/FieldRvaTest.exe b/test/AsmResolver.PE.Tests/Resources/FieldRvaTest.exe new file mode 100644 index 0000000000000000000000000000000000000000..d3b4ff03e8d8717a0f1890f65e21676327a65dba GIT binary patch literal 2048 zcmeHG%}*0S6#q?IN?Sf`i66iLhA)G}Y#`-gLQH8vfq)WPAyE@q+6k`N?l!yIhH@|- zG|@z({so>0cVi;)#MLN?S3U6;c<|^2-|TKF0&4U~eA#*L{oeb{%zLvlb2ncg2p~jb zZx7faD<$RX->V0dZtZ^8iZ{`no-H=J)04Za*}P<#tCm{idDSpXhpz~3l?|>LeByeR z7tMlDT3TZL{?hZ4z$^;@QR8uKw_Wt2i49|j3TyU*{e9$c8Yvo5aWXVb)-9!Gu}pyC zQi4c50^;shw?6bJ_|gl^`2MjbRj=XL{I7x>h#bg82u~friX+w>!m>xV7ukScca6q@ zWyKP@nfGf*`J8Z#;aLjP_iqxD=Sm`L3!ni{IdF~YV1#KL+KGaH5*7%C8p4f{XsoHZ zrS%jJ;d|ZjXL9+0PqcbykW{Sw$fth3@gdu!W(BDSi)f>|jWEH|(;;(c#>3^)gKIU%CECcINtd`J z#W6-Xh7LSO8%D5=PRXE50Q1CPSX&%zlNEaPy3Rh@zx@=6V!WQbq6xjQP*HQjb}%+) z7RtJ~3_7!N-oc`(YlRvE(?;bdJf~^~igw<#bZtd)%OY1T31q9bBZ^2@9f5_i;b=uM zCG?W8Y(J&=Q6*y*OPVgMtgtFtUf8sS=4iCZx)`@CwF*!BbhnTx)hOr!8Pl*$vP+ic zh*`}L)Y0a9HS>A%^z_DsuiLLbD4hs~6A^knEb?~!_F~8A?vq9YSX&}-CIrSJ2>3T! y9!9(Lx%7@o?F9gLGNv`TChCm~p%io-djsI9qZ3a?gnMu8eDY+p^S_I~JAogpd! Date: Tue, 11 Jul 2023 22:21:41 +0200 Subject: [PATCH 40/50] BUGFIX: Update FieldRVA rows explicitly after adding to data table. --- .../DotNet/Builder/ManagedPEFileBuilder.cs | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index 8475d8805..f2c32be92 100644 --- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs +++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs @@ -154,6 +154,31 @@ public IFieldRvaDataReader FieldRvaDataReader } } + /// + /// Creates a new managed PE file builder with default settings. + /// + public ManagedPEFileBuilder() + : this(ThrowErrorListener.Instance) + { + } + + /// + /// Creates a new managed PE file builder with the provided error listener. + /// + public ManagedPEFileBuilder(IErrorListener errorListener) + { + ErrorListener = errorListener; + } + + /// + /// Gets or sets the object responsible for recording diagnostic information during the building process. + /// + public IErrorListener ErrorListener + { + get; + set; + } + /// protected override ManagedPEBuilderContext CreateContext(IPEImage image) => new(image); @@ -436,7 +461,7 @@ protected override uint GetSectionAlignment(PEFile peFile, IPEImage image, Manag protected override uint GetImageBase(PEFile peFile, IPEImage image, ManagedPEBuilderContext context) => (uint) image.ImageBase; - private static void ProcessRvasInMetadataTables(ManagedPEBuilderContext context) + private void ProcessRvasInMetadataTables(ManagedPEBuilderContext context) { var dotNetSegment = context.DotNetSegment; var tablesStream = dotNetSegment.DotNetDirectory.Metadata?.GetStream(); @@ -447,7 +472,7 @@ private static void ProcessRvasInMetadataTables(ManagedPEBuilderContext context) AddFieldRvasToTable(context); } - private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesStream tablesStream) + private void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesStream tablesStream) { var methodTable = tablesStream.GetTable(); for (int i = 0; i < methodTable.Count; i++) @@ -472,7 +497,7 @@ private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesSt } } - private static ISegment? GetMethodBodySegment(MethodDefinitionRow methodRow) + private ISegment? GetMethodBodySegment(MethodDefinitionRow methodRow) { if (methodRow.Body.IsBounded) return methodRow.Body.GetSegment(); @@ -485,13 +510,13 @@ private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesSt return CilRawMethodBody.FromReader(ThrowErrorListener.Instance, ref reader); } - throw new NotImplementedException("Native unbounded method bodies cannot be reassembled yet."); + ErrorListener.NotSupported("Native unbounded method bodies cannot be reassembled yet."); } return null; } - private static void AddFieldRvasToTable(ManagedPEBuilderContext context) + private void AddFieldRvasToTable(ManagedPEBuilderContext context) { var directory = context.DotNetSegment.DotNetDirectory; var fieldRvaTable = directory.Metadata! @@ -504,18 +529,21 @@ private static void AddFieldRvasToTable(ManagedPEBuilderContext context) var table = context.DotNetSegment.FieldRvaTable; var reader = context.FieldRvaDataReader; - for (int i = 0; i < fieldRvaTable.Count; i++) + for (uint rid = 1; rid <= fieldRvaTable.Count; rid++) { + ref var row = ref fieldRvaTable.GetRowRef(rid); var data = reader.ResolveFieldData( - ThrowErrorListener.Instance, + ErrorListener, context.Platform, directory, - fieldRvaTable[i]); + row + ); if (data is null) continue; table.Add(data); + row.Data = data.ToReference(); } } } From 0fc8c452171707b80ed807e3c264db0f6cd11b49 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 11 Jul 2023 23:27:57 +0200 Subject: [PATCH 41/50] Fix unit test binary path. --- test/AsmResolver.PE.Tests/Properties/Resources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index ea2add3ab..987d82cad 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -111,7 +111,7 @@ ..\Resources\ProxyDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + ..\Resources\ForwarderTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 From f3b78feeab3a06edf984a88eaee461b03ebcb77a Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:41:11 -0400 Subject: [PATCH 42/50] Add package source links for GitHub --- .../AsmResolver.DotNet.Dynamic.csproj | 4 ++++ src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 4 ++++ src/AsmResolver.PE.File/AsmResolver.PE.File.csproj | 4 ++++ .../AsmResolver.PE.Win32Resources.csproj | 7 +++++++ src/AsmResolver.PE/AsmResolver.PE.csproj | 4 ++++ src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj | 4 ++++ src/AsmResolver/AsmResolver.csproj | 4 ++++ 7 files changed, 31 insertions(+) diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj index 5519f663e..f3e66f4b9 100644 --- a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj +++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj @@ -23,6 +23,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 664921ec6..79a63a67f 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -24,6 +24,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 98b14d222..b89b4cfe0 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -25,6 +25,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj index 219f54c0d..c32f0f81b 100644 --- a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj +++ b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj @@ -18,6 +18,13 @@ bin\Release\AsmResolver.PE.Win32Resources.xml + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index a008308e1..b867a61e3 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -24,6 +24,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj index b2802c3ce..d597389f5 100644 --- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -26,6 +26,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index b7685bc8a..ea9a1dcc6 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -22,6 +22,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive From 21bfdf4618e7493b7d5830ad4e9b45fc2dcc33ce Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 14 Jul 2023 14:43:55 +0200 Subject: [PATCH 43/50] Revert hw changes. --- test/TestBinaries/DotNet/HelloWorld/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/TestBinaries/DotNet/HelloWorld/Program.cs b/test/TestBinaries/DotNet/HelloWorld/Program.cs index c16dc7682..6159d9786 100644 --- a/test/TestBinaries/DotNet/HelloWorld/Program.cs +++ b/test/TestBinaries/DotNet/HelloWorld/Program.cs @@ -7,9 +7,7 @@ public class Program { private static void Main(string[] args) { - var list = args.ToList(); - foreach (var item in list) - Console.WriteLine(item); + Console.WriteLine("Hello, World!"); } } } From 4e3f618ccbc1d80b3ee821993e7e3da59e94d7df Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 14 Jul 2023 16:19:20 +0200 Subject: [PATCH 44/50] Update nuget dependencies. --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj | 2 +- .../AsmResolver.DotNet.Dynamic.Tests.csproj | 8 ++++---- .../AsmResolver.DotNet.Tests.csproj | 7 ++++--- test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs | 4 ++-- .../AsmResolver.PE.File.Tests.csproj | 7 ++++--- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 8 ++++---- .../AsmResolver.PE.Win32Resources.Tests.csproj | 7 ++++--- .../AsmResolver.Symbols.Pdb.Tests.csproj | 7 ++++--- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 7 ++++--- .../AsmResolver.DotNet.TestCases.Resources.csproj | 4 ++-- 11 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 664921ec6..1509099a8 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -28,7 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index 5ce11d3bf..9357b967b 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj index 7db94fe93..816b9b8fa 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -8,10 +8,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 2a3003aee..1f798a757 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -9,9 +9,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs index ae0e5db79..352ddb998 100644 --- a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs +++ b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs @@ -140,7 +140,7 @@ public void FixedStringArgument(bool rebuild, bool access) Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; - Assert.Equal("String fixed arg", argument.Element); + Assert.Equal("String fixed arg", Assert.IsAssignableFrom(argument.Element)); } [Theory] @@ -232,7 +232,7 @@ public void NamedStringArgument(bool rebuild, bool access) var argument = attribute.Signature.NamedArguments[0]; Assert.Equal(nameof(TestCaseAttribute.StringValue), argument.MemberName); - Assert.Equal("String named arg", argument.Argument.Element); + Assert.Equal("String named arg", Assert.IsAssignableFrom(argument.Argument.Element)); } [Theory] diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index 187974a87..b8eb13864 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -9,9 +9,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index a7235eb57..85bad2a64 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -16,10 +16,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index ba9f10fa3..65d46286d 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -8,9 +8,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index b88044f8f..2d4002a19 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -8,9 +8,10 @@ - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 8dc0cd7f2..f6ba92e6a 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,9 +8,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj index da037df75..b4ceea1e3 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj @@ -24,8 +24,8 @@ - - + + From 1279fc6b8552a8c3f7fa6c4aca0bd1f88558f1e3 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 16 Jul 2023 16:08:14 +0200 Subject: [PATCH 45/50] Fix layout of .net bundles docs. --- docs/guides/dotnet/bundles.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/guides/dotnet/bundles.md b/docs/guides/dotnet/bundles.md index c4243cc2b..3e2e4da49 100644 --- a/docs/guides/dotnet/bundles.md +++ b/docs/guides/dotnet/bundles.md @@ -29,15 +29,11 @@ The major version number refers to the file format that should be used when saving the manifest. Below an overview of the values that are recognized by the CLR: - --------------------------------------------------- - .NET Version Number Bundle File Format Version - ---------------------- ---------------------------- - .NET Core 3.1 1 - - .NET 5.0 2 - - .NET 6.0 6 - --------------------------------------------------- +| Minimum .NET Version Number | Bundle File Format Version | +|-----------------------------|-----------------------------| +| .NET Core 3.1 | 1 | +| .NET 5.0 | 2 | +| .NET 6.0 | 6 | To create a new bundle with a specific bundle identifier, use the overloaded constructor @@ -108,8 +104,12 @@ var manifest = BundleManifest.FromDataSource(contents, bundleAddress); Constructing new bundled executable files requires a template file that AsmResolver can base the final output on. This is similar how .NET -compilers themselves do this as well. By default, the .NET SDK installs -template binaries in one of the following directories: +compilers themselves do this as well. + +### Using the .NET SDK's template + +By default, the .NET SDK installs template apphost binaries in one of the +following directories: - `/sdk//AppHostTemplate` - `/packs/Microsoft.NETCore.App.Host.//runtimes//native` @@ -150,6 +150,15 @@ manifest.WriteUsingTemplate( imagePathToCopyHeadersFrom: @"C:\Path\To\Original\HelloWorld.exe")); ``` +> [!NOTE] +> `BundleManifest` and `BundlerParameters` also define overloads of the +> `WriteUsingTemplate` and `FromTemplate` / `FromExistingBundle` +> respectively, taking `byte[]`, `IDataSource` or `IPEImage` instances +> instead of file paths. + + +### Using an input binary as template + If you do not have access to a template file (e.g., if the SDK is not installed) but have another existing PE file that was packaged in a similar fashion, it is then possible to use this file as a template @@ -179,10 +188,6 @@ manifest.WriteUsingTemplate( > the input file to determine the parameters for patching the input file. > As heuristics are not perfect, this is not guaranteed to always work. -`BundleManifest` and `BundlerParameters` also define overloads of the -`WriteUsingTemplate` and `FromTemplate` / `FromExistingBundle` -respectively, taking `byte[]`, `IDataSource` or `IPEImage` instances -instead of file paths. ## Managing Files From 52793c916d5acee65ff420a0bc036333a9fb6245 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 16 Jul 2023 16:08:27 +0200 Subject: [PATCH 46/50] Downgrade to json 6.x.x for better compatibility. --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 1509099a8..664921ec6 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -28,7 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 0ff29f466696a254c5c95486188123f0feac2f11 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 16 Jul 2023 18:50:03 +0200 Subject: [PATCH 47/50] Fix more typos in knowncorlibs. --- src/AsmResolver.DotNet/KnownCorLibs.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index 8492d7bfe..8cbf51825 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -101,7 +101,7 @@ public static class KnownCorLibs }); /// - /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// References System.Runtime.dll, Version=4.0.10.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET standard 1.2. /// public static readonly AssemblyReference SystemRuntime_v4_0_10_0 = new("System.Runtime", @@ -121,7 +121,7 @@ public static class KnownCorLibs }); /// - /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// References System.Runtime.dll, Version=4.1.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET standard 1.5, 1.6 and 1.7, and .NET Core 1.0 and 1.1. /// public static readonly AssemblyReference SystemRuntime_v4_1_0_0 = new("System.Runtime", From 967c6cb21c2543f97bacfd60b293fc2ce8da0d8b Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 16 Jul 2023 18:50:22 +0200 Subject: [PATCH 48/50] Update encrypted nuget deploy key. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5597d1b04..a550e2f6d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,7 @@ deploy: provider: NuGet api_key: - secure: L3fXsS7umzD8zwAvTsdGxOg/E6tQ4IR4MfwBAcO8elE7ZwjZ8HO8UPwjiWbp4RMw + secure: orcP0C1iuBVKxnv/uAUehgU1KEI/lCpbSxDqbckd3sZ7XxcuENj6PrExs6SdJIf1 skip_symbols: false artifact: /.*\.nupkg/ From 5991eda2b6f97942ff718a7321f5ee05b7ee8b69 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Sun, 16 Jul 2023 13:22:46 -0400 Subject: [PATCH 49/50] Deterministic builds --- Directory.Build.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9d7fc4c12..cd1dada47 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,8 @@ https://github.com/Washi1337/AsmResolver git 10 - 5.4.0 + 5.4.0 + true From 276c6324b6d32241e8cee3f6e14f41ee71f9e26b Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 16 Jul 2023 20:27:23 +0200 Subject: [PATCH 50/50] Resolve compiler warnings. --- src/AsmResolver.PE/PEReaderParameters.cs | 4 ++++ .../AsmResolver.PE.File.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs | 4 ++-- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.PE/PEReaderParameters.cs b/src/AsmResolver.PE/PEReaderParameters.cs index de8d3d4de..cb8ee0573 100644 --- a/src/AsmResolver.PE/PEReaderParameters.cs +++ b/src/AsmResolver.PE/PEReaderParameters.cs @@ -58,6 +58,10 @@ public IDebugDataReader DebugDataReader set; } + /// + /// Gets or sets the object responsible for reading certificates (such as authenticode signatures) in the + /// security data directory of the input PE file. + /// public ICertificateReader CertificateReader { get; diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index b8eb13864..5da60fb15 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index 85bad2a64..ba76c1c56 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false true enable diff --git a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs index d6b717f30..3622d8b5f 100644 --- a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs @@ -138,7 +138,7 @@ public void PersistentExportedSymbol() var newImage = RebuildAndReloadManagedPE(image); // Verify. - Assert.Equal(1, newImage.Exports!.Entries.Count); + Assert.Single(newImage.Exports!.Entries); var newExportedSymbol = newImage.Exports.Entries[0]; Assert.Equal(exportedSymbol.Name, newExportedSymbol.Name); Assert.Equal(exportedSymbol.Ordinal, newExportedSymbol.Ordinal); @@ -160,7 +160,7 @@ public void PersistentExportedSymbolByOrdinal() var newImage = RebuildAndReloadManagedPE(image); // Verify. - Assert.Equal(1, newImage.Exports!.Entries.Count); + Assert.Single(newImage.Exports!.Entries); var newExportedSymbol = newImage.Exports.Entries[0]; Assert.True(exportedSymbol.IsByOrdinal); Assert.Equal(exportedSymbol.Ordinal, newExportedSymbol.Ordinal); diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index 65d46286d..76a8733e6 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false AsmResolver.PE.Win32Resources.Tests warnings diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index f6ba92e6a..5d726da8c 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false true enable