This is the sixteenth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack
I was recently asked if it’s possible to hijack the new operator in Linux. We’ve already seen that we can do it in both .NET Framework and .NET Core, in both x86_32 and x86_64, now it’s time to do it in .NET 5 on Linux.
Table of Contents
Docker configuration
I’m going to use Ubuntu 20.04 on WSL2 running on Windows 10:
1 2 |
afish@ubuntu:/home/af$ uname -r 4.19.128-microsoft-standard |
I’m going to use Docker for installing .NET and everything around. So I do this to create a container based on .NET 5:
1 |
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --security-opt apparmor=unconfined --rm -i -v /home/afish/makeref:/makeref mcr.microsoft.com/dotnet/sdk:5.0 bash |
You can see I’m mapping directory /home/afish/makeref
and enabling some security flags to be able to debug application and modify page protection.
lldb and SOS
First thing I want to do is to install lldb and SOS:
1 2 3 4 5 6 7 8 9 10 |
apt-get update Get:1 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB] Get:2 http://deb.debian.org/debian buster InRelease [121 kB] Get:3 http://deb.debian.org/debian buster-updates InRelease [51.9 kB] Get:4 http://security.debian.org/debian-security buster/updates/main amd64 Packages [258 kB] Get:5 http://deb.debian.org/debian buster/main amd64 Packages [7907 kB] Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [7860 B] Fetched 8412 kB in 2s (3467 kB/s) Reading package lists... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
apt-get install -y lldb Reading package lists... Building dependency tree... Reading state information... The following additional packages will be installed: binfmt-support bzip2 file libbsd0 libc-dev-bin libc6-dev libedit2 libffi-dev libgpm2 liblldb-7 libllvm7 libmagic-mgc libmagic1 libncurses-dev libncurses6 libpipeline1 libpython-stdlib libpython2-stdlib libpython2.7 libpython2.7-minimal libpython2.7-stdlib libreadline7 libsqlite3-0 libtinfo-dev linux-libc-dev lldb-7 llvm-7 llvm-7-dev llvm-7-runtime lsb-base manpages manpages-dev mime-support python python-lldb-7 python-minimal python-six python2 python2-minimal python2.7 python2.7-minimal readline-common xz-utils Suggested packages: bzip2-doc glibc-doc gpm ncurses-doc llvm-7-doc man-browser python-doc python-tk python2-doc python2.7-doc binutils readline-doc The following NEW packages will be installed: binfmt-support bzip2 file libbsd0 libc-dev-bin libc6-dev libedit2 libffi-dev libgpm2 liblldb-7 libllvm7 libmagic-mgc libmagic1 libncurses-dev libncurses6 libpipeline1 libpython-stdlib libpython2-stdlib libpython2.7 libpython2.7-minimal libpython2.7-stdlib libreadline7 libsqlite3-0 libtinfo-dev linux-libc-dev lldb lldb-7 llvm-7 llvm-7-dev llvm-7-runtime lsb-base manpages manpages-dev mime-support python python-lldb-7 python-minimal python-six python2 python2-minimal python2.7 python2.7-minimal readline-common xz-utils 0 upgraded, 44 newly installed, 0 to remove and 1 not upgraded. Need to get 71.3 MB of archives. After this operation, 369 MB of additional disk space will be used. Get:1 http://deb.debian.org/debian buster/main amd64 libpython2.7-minimal amd64 2.7.16-2+deb10u1 [395 kB] Get:2 http://deb.debian.org/debian buster/main amd64 python2.7-minimal amd64 2.7.16-2+deb10u1 [1369 kB] Get:3 http://deb.debian.org/debian buster/main amd64 python2-minimal amd64 2.7.16-1 [41.4 kB] Get:4 http://deb.debian.org/debian buster/main amd64 python-minimal amd64 2.7.16-1 [21.0 kB] Get:5 http://deb.debian.org/debian buster/main amd64 mime-support all 3.62 [37.2 kB] Get:6 http://deb.debian.org/debian buster/main amd64 readline-common all 7.0-5 [70.6 kB] Get:7 http://deb.debian.org/debian buster/main amd64 libreadline7 amd64 7.0-5 [151 kB] Get:8 http://deb.debian.org/debian buster/main amd64 libsqlite3-0 amd64 3.27.2-3+deb10u1 [641 kB] Get:9 http://deb.debian.org/debian buster/main amd64 libpython2.7-stdlib amd64 2.7.16-2+deb10u1 [1912 kB] Get:10 http://deb.debian.org/debian buster/main amd64 python2.7 amd64 2.7.16-2+deb10u1 [305 kB] Get:11 http://deb.debian.org/debian buster/main amd64 libpython2-stdlib amd64 2.7.16-1 [20.8 kB] Get:12 http://deb.debian.org/debian buster/main amd64 libpython-stdlib amd64 2.7.16-1 [20.8 kB] Get:13 http://deb.debian.org/debian buster/main amd64 python2 amd64 2.7.16-1 [41.6 kB] Get:14 http://deb.debian.org/debian buster/main amd64 python amd64 2.7.16-1 [22.8 kB] Get:15 http://deb.debian.org/debian buster/main amd64 bzip2 amd64 1.0.6-9.2~deb10u1 [48.4 kB] Get:16 http://deb.debian.org/debian buster/main amd64 libmagic-mgc amd64 1:5.35-4+deb10u1 [242 kB] Get:17 http://deb.debian.org/debian buster/main amd64 libmagic1 amd64 1:5.35-4+deb10u1 [117 kB] Get:18 http://deb.debian.org/debian buster/main amd64 file amd64 1:5.35-4+deb10u1 [66.4 kB] Get:19 http://deb.debian.org/debian buster/main amd64 manpages all 4.16-2 [1295 kB] Get:20 http://deb.debian.org/debian buster/main amd64 xz-utils amd64 5.2.4-1 [183 kB] Get:21 http://deb.debian.org/debian buster/main amd64 libpipeline1 amd64 1.5.1-2 [31.2 kB] Get:22 http://deb.debian.org/debian buster/main amd64 lsb-base all 10.2019051400 [28.4 kB] Get:23 http://deb.debian.org/debian buster/main amd64 binfmt-support amd64 2.2.0-2 [70.0 kB] Get:24 http://deb.debian.org/debian buster/main amd64 libbsd0 amd64 0.9.1-2 [99.5 kB] Get:25 http://deb.debian.org/debian buster/main amd64 libc-dev-bin amd64 2.28-10 [275 kB] Get:26 http://deb.debian.org/debian buster/main amd64 linux-libc-dev amd64 4.19.160-2 [1416 kB] Get:27 http://deb.debian.org/debian buster/main amd64 libc6-dev amd64 2.28-10 [2691 kB] Get:28 http://deb.debian.org/debian buster/main amd64 libedit2 amd64 3.1-20181209-1 [94.0 kB] Get:29 http://deb.debian.org/debian buster/main amd64 libffi-dev amd64 3.2.1-9 [156 kB] Get:30 http://deb.debian.org/debian buster/main amd64 libgpm2 amd64 1.20.7-5 [35.1 kB] Get:31 http://deb.debian.org/debian buster/main amd64 libllvm7 amd64 1:7.0.1-8+deb10u2 [13.1 MB] Get:32 http://deb.debian.org/debian buster/main amd64 libncurses6 amd64 6.1+20181013-2+deb10u2 [102 kB] Get:33 http://deb.debian.org/debian buster/main amd64 libpython2.7 amd64 2.7.16-2+deb10u1 [1036 kB] Get:34 http://deb.debian.org/debian buster/main amd64 liblldb-7 amd64 1:7.0.1-8+deb10u2 [7938 kB] Get:35 http://deb.debian.org/debian buster/main amd64 libncurses-dev amd64 6.1+20181013-2+deb10u2 [333 kB] Get:36 http://deb.debian.org/debian buster/main amd64 libtinfo-dev amd64 6.1+20181013-2+deb10u2 [940 B] Get:37 http://deb.debian.org/debian buster/main amd64 llvm-7-runtime amd64 1:7.0.1-8+deb10u2 [190 kB] Get:38 http://deb.debian.org/debian buster/main amd64 llvm-7 amd64 1:7.0.1-8+deb10u2 [4554 kB] Get:39 http://deb.debian.org/debian buster/main amd64 llvm-7-dev amd64 1:7.0.1-8+deb10u2 [21.3 MB] Get:40 http://deb.debian.org/debian buster/main amd64 python-six all 1.12.0-1 [15.7 kB] Get:41 http://deb.debian.org/debian buster/main amd64 python-lldb-7 amd64 1:7.0.1-8+deb10u2 [122 kB] Get:42 http://deb.debian.org/debian buster/main amd64 lldb-7 amd64 1:7.0.1-8+deb10u2 [8459 kB] Get:43 http://deb.debian.org/debian buster/main amd64 lldb amd64 1:7.0-47 [7176 B] Get:44 http://deb.debian.org/debian buster/main amd64 manpages-dev all 4.16-2 [2232 kB] debconf: delaying package configuration, since apt-utils is not installed Fetched 71.3 MB in 2s (29.9 MB/s) Selecting previously unselected package libpython2.7-minimal:amd64. (Reading database ... 9877 files and directories currently installed.) Preparing to unpack .../00-libpython2.7-minimal_2.7.16-2+deb10u1_amd64.deb ... Unpacking libpython2.7-minimal:amd64 (2.7.16-2+deb10u1) ... Selecting previously unselected package python2.7-minimal. Preparing to unpack .../01-python2.7-minimal_2.7.16-2+deb10u1_amd64.deb ... Unpacking python2.7-minimal (2.7.16-2+deb10u1) ... Selecting previously unselected package python2-minimal. Preparing to unpack .../02-python2-minimal_2.7.16-1_amd64.deb ... Unpacking python2-minimal (2.7.16-1) ... Selecting previously unselected package python-minimal. Preparing to unpack .../03-python-minimal_2.7.16-1_amd64.deb ... Unpacking python-minimal (2.7.16-1) ... Selecting previously unselected package mime-support. Preparing to unpack .../04-mime-support_3.62_all.deb ... Unpacking mime-support (3.62) ... Selecting previously unselected package readline-common. Preparing to unpack .../05-readline-common_7.0-5_all.deb ... Unpacking readline-common (7.0-5) ... Selecting previously unselected package libreadline7:amd64. Preparing to unpack .../06-libreadline7_7.0-5_amd64.deb ... Unpacking libreadline7:amd64 (7.0-5) ... Selecting previously unselected package libsqlite3-0:amd64. Preparing to unpack .../07-libsqlite3-0_3.27.2-3+deb10u1_amd64.deb ... Unpacking libsqlite3-0:amd64 (3.27.2-3+deb10u1) ... Selecting previously unselected package libpython2.7-stdlib:amd64. Preparing to unpack .../08-libpython2.7-stdlib_2.7.16-2+deb10u1_amd64.deb ... Unpacking libpython2.7-stdlib:amd64 (2.7.16-2+deb10u1) ... Selecting previously unselected package python2.7. Preparing to unpack .../09-python2.7_2.7.16-2+deb10u1_amd64.deb ... Unpacking python2.7 (2.7.16-2+deb10u1) ... Selecting previously unselected package libpython2-stdlib:amd64. Preparing to unpack .../10-libpython2-stdlib_2.7.16-1_amd64.deb ... Unpacking libpython2-stdlib:amd64 (2.7.16-1) ... Selecting previously unselected package libpython-stdlib:amd64. Preparing to unpack .../11-libpython-stdlib_2.7.16-1_amd64.deb ... Unpacking libpython-stdlib:amd64 (2.7.16-1) ... Setting up libpython2.7-minimal:amd64 (2.7.16-2+deb10u1) ... Setting up python2.7-minimal (2.7.16-2+deb10u1) ... Linking and byte-compiling packages for runtime python2.7... Setting up python2-minimal (2.7.16-1) ... Selecting previously unselected package python2. (Reading database ... 10694 files and directories currently installed.) Preparing to unpack .../python2_2.7.16-1_amd64.deb ... Unpacking python2 (2.7.16-1) ... Setting up python-minimal (2.7.16-1) ... Selecting previously unselected package python. (Reading database ... 10727 files and directories currently installed.) Preparing to unpack .../00-python_2.7.16-1_amd64.deb ... Unpacking python (2.7.16-1) ... Selecting previously unselected package bzip2. Preparing to unpack .../01-bzip2_1.0.6-9.2~deb10u1_amd64.deb ... Unpacking bzip2 (1.0.6-9.2~deb10u1) ... Selecting previously unselected package libmagic-mgc. Preparing to unpack .../02-libmagic-mgc_1%3a5.35-4+deb10u1_amd64.deb ... Unpacking libmagic-mgc (1:5.35-4+deb10u1) ... Selecting previously unselected package libmagic1:amd64. Preparing to unpack .../03-libmagic1_1%3a5.35-4+deb10u1_amd64.deb ... Unpacking libmagic1:amd64 (1:5.35-4+deb10u1) ... Selecting previously unselected package file. Preparing to unpack .../04-file_1%3a5.35-4+deb10u1_amd64.deb ... Unpacking file (1:5.35-4+deb10u1) ... Selecting previously unselected package manpages. Preparing to unpack .../05-manpages_4.16-2_all.deb ... Unpacking manpages (4.16-2) ... Selecting previously unselected package xz-utils. Preparing to unpack .../06-xz-utils_5.2.4-1_amd64.deb ... Unpacking xz-utils (5.2.4-1) ... Selecting previously unselected package libpipeline1:amd64. Preparing to unpack .../07-libpipeline1_1.5.1-2_amd64.deb ... Unpacking libpipeline1:amd64 (1.5.1-2) ... Selecting previously unselected package lsb-base. Preparing to unpack .../08-lsb-base_10.2019051400_all.deb ... Unpacking lsb-base (10.2019051400) ... Selecting previously unselected package binfmt-support. Preparing to unpack .../09-binfmt-support_2.2.0-2_amd64.deb ... Unpacking binfmt-support (2.2.0-2) ... Selecting previously unselected package libbsd0:amd64. Preparing to unpack .../10-libbsd0_0.9.1-2_amd64.deb ... Unpacking libbsd0:amd64 (0.9.1-2) ... Selecting previously unselected package libc-dev-bin. Preparing to unpack .../11-libc-dev-bin_2.28-10_amd64.deb ... Unpacking libc-dev-bin (2.28-10) ... Selecting previously unselected package linux-libc-dev:amd64. Preparing to unpack .../12-linux-libc-dev_4.19.160-2_amd64.deb ... Unpacking linux-libc-dev:amd64 (4.19.160-2) ... Selecting previously unselected package libc6-dev:amd64. Preparing to unpack .../13-libc6-dev_2.28-10_amd64.deb ... Unpacking libc6-dev:amd64 (2.28-10) ... Selecting previously unselected package libedit2:amd64. Preparing to unpack .../14-libedit2_3.1-20181209-1_amd64.deb ... Unpacking libedit2:amd64 (3.1-20181209-1) ... Selecting previously unselected package libffi-dev:amd64. Preparing to unpack .../15-libffi-dev_3.2.1-9_amd64.deb ... Unpacking libffi-dev:amd64 (3.2.1-9) ... Selecting previously unselected package libgpm2:amd64. Preparing to unpack .../16-libgpm2_1.20.7-5_amd64.deb ... Unpacking libgpm2:amd64 (1.20.7-5) ... Selecting previously unselected package libllvm7:amd64. Preparing to unpack .../17-libllvm7_1%3a7.0.1-8+deb10u2_amd64.deb ... Unpacking libllvm7:amd64 (1:7.0.1-8+deb10u2) ... Selecting previously unselected package libncurses6:amd64. Preparing to unpack .../18-libncurses6_6.1+20181013-2+deb10u2_amd64.deb ... Unpacking libncurses6:amd64 (6.1+20181013-2+deb10u2) ... dSelecting previously unselected package libpython2.7:amd64. Preparing to unpack .../19-libpython2.7_2.7.16-2+deb10u1_amd64.deb ... Unpacking libpython2.7:amd64 (2.7.16-2+deb10u1) ... irSelecting previously unselected package liblldb-7. Preparing to unpack .../20-liblldb-7_1%3a7.0.1-8+deb10u2_amd64.deb ... Unpacking liblldb-7 (1:7.0.1-8+deb10u2) ... Selecting previously unselected package libncurses-dev:amd64. Preparing to unpack .../21-libncurses-dev_6.1+20181013-2+deb10u2_amd64.deb ... Unpacking libncurses-dev:amd64 (6.1+20181013-2+deb10u2) ... Selecting previously unselected package libtinfo-dev:amd64. Preparing to unpack .../22-libtinfo-dev_6.1+20181013-2+deb10u2_amd64.deb ... Unpacking libtinfo-dev:amd64 (6.1+20181013-2+deb10u2) ... Selecting previously unselected package llvm-7-runtime. Preparing to unpack .../23-llvm-7-runtime_1%3a7.0.1-8+deb10u2_amd64.deb ... Unpacking llvm-7-runtime (1:7.0.1-8+deb10u2) ... Selecting previously unselected package llvm-7. Preparing to unpack .../24-llvm-7_1%3a7.0.1-8+deb10u2_amd64.deb ... Unpacking llvm-7 (1:7.0.1-8+deb10u2) ... Selecting previously unselected package llvm-7-dev. Preparing to unpack .../25-llvm-7-dev_1%3a7.0.1-8+deb10u2_amd64.deb ... Unpacking llvm-7-dev (1:7.0.1-8+deb10u2) ... Selecting previously unselected package python-six. Preparing to unpack .../26-python-six_1.12.0-1_all.deb ... Unpacking python-six (1.12.0-1) ... Selecting previously unselected package python-lldb-7. Preparing to unpack .../27-python-lldb-7_1%3a7.0.1-8+deb10u2_amd64.deb ... Unpacking python-lldb-7 (1:7.0.1-8+deb10u2) ... Selecting previously unselected package lldb-7. Preparing to unpack .../28-lldb-7_1%3a7.0.1-8+deb10u2_amd64.deb ... Unpacking lldb-7 (1:7.0.1-8+deb10u2) ... Selecting previously unselected package lldb. Preparing to unpack .../29-lldb_1%3a7.0-47_amd64.deb ... Unpacking lldb (1:7.0-47) ... Selecting previously unselected package manpages-dev. Preparing to unpack .../30-manpages-dev_4.16-2_all.deb ... Unpacking manpages-dev (4.16-2) ... Setting up libpipeline1:amd64 (1.5.1-2) ... Setting up lsb-base (10.2019051400) ... Setting up libgpm2:amd64 (1.20.7-5) ... Setting up mime-support (3.62) ... Setting up libmagic-mgc (1:5.35-4+deb10u1) ... Setting up manpages (4.16-2) ... Setting up libsqlite3-0:amd64 (3.27.2-3+deb10u1) ... Setting up libmagic1:amd64 (1:5.35-4+deb10u1) ... Setting up linux-libc-dev:amd64 (4.19.160-2) ... Setting up file (1:5.35-4+deb10u1) ... Setting up bzip2 (1.0.6-9.2~deb10u1) ... Setting up libffi-dev:amd64 (3.2.1-9) ... Setting up libncurses6:amd64 (6.1+20181013-2+deb10u2) ... Setting up xz-utils (5.2.4-1) ... update-alternatives: using /usr/bin/xz to provide /usr/bin/lzma (lzma) in auto mode update-alternatives: warning: skip creation of /usr/share/man/man1/lzma.1.gz because associated file /usr/share/man/man1/xz.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/unlzma.1.gz because associated file /usr/share/man/man1/unxz.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzcat.1.gz because associated file /usr/share/man/man1/xzcat.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzmore.1.gz because associated file /usr/share/man/man1/xzmore.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzless.1.gz because associated file /usr/share/man/man1/xzless.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzdiff.1.gz because associated file /usr/share/man/man1/xzdiff.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzcmp.1.gz because associated file /usr/share/man/man1/xzcmp.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzgrep.1.gz because associated file /usr/share/man/man1/xzgrep.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzegrep.1.gz because associated file /usr/share/man/man1/xzegrep.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzfgrep.1.gz because associated file /usr/share/man/man1/xzfgrep.1.gz (of link group lzma) doesn't exist Setting up binfmt-support (2.2.0-2) ... invoke-rc.d: could not determine current runlevel invoke-rc.d: policy-rc.d denied execution of start. Setting up libc-dev-bin (2.28-10) ... Setting up libbsd0:amd64 (0.9.1-2) ... Setting up readline-common (7.0-5) ... Setting up libreadline7:amd64 (7.0-5) ... Setting up manpages-dev (4.16-2) ... Setting up libedit2:amd64 (3.1-20181209-1) ... Setting up libpython2.7-stdlib:amd64 (2.7.16-2+deb10u1) ... Setting up libllvm7:amd64 (1:7.0.1-8+deb10u2) ... Setting up libc6-dev:amd64 (2.28-10) ... Setting up libpython2.7:amd64 (2.7.16-2+deb10u1) ... Setting up libncurses-dev:amd64 (6.1+20181013-2+deb10u2) ... Setting up llvm-7-runtime (1:7.0.1-8+deb10u2) ... Setting up python2.7 (2.7.16-2+deb10u1) ... Setting up llvm-7 (1:7.0.1-8+deb10u2) ... Setting up libpython2-stdlib:amd64 (2.7.16-1) ... Setting up python2 (2.7.16-1) ... Setting up libpython-stdlib:amd64 (2.7.16-1) ... Setting up python (2.7.16-1) ... Setting up libtinfo-dev:amd64 (6.1+20181013-2+deb10u2) ... Setting up liblldb-7 (1:7.0.1-8+deb10u2) ... Setting up llvm-7-dev (1:7.0.1-8+deb10u2) ... Setting up python-six (1.12.0-1) ... Setting up python-lldb-7 (1:7.0.1-8+deb10u2) ... Setting up lldb-7 (1:7.0.1-8+deb10u2) ... Setting up lldb (1:7.0-47) ... Processing triggers for libc-bin (2.28-10) ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
dotnet tool install --global dotnet-sos Tools directory '/root/.dotnet/tools' is not currently on the PATH environment variable. If you are using bash, you can add it to your profile by running the following command: cat << \EOF >> ~/.bash_profile # Add .NET Core SDK tools export PATH="$PATH:/root/.dotnet/tools" EOF You can add it to the current session by running the following command: export PATH="$PATH:/root/.dotnet/tools" You can invoke the tool using the following command: dotnet-sos Tool 'dotnet-sos' (version '5.0.160202') was successfully installed. |
1 2 3 4 5 6 7 |
/root/.dotnet/tools/dotnet-sos install Installing SOS to /root/.dotnet/sos from /root/.dotnet/tools/.store/dotnet-sos/5.0.160202/dotnet-sos/5.0.160202/tools/netcoreapp2.1/any/linux-x64 Creating installation directory... Copying files... Creating new /root/.lldbinit file - LLDB will load SOS automatically at startup SOS install succeeded |
Project configuration
Okay. Now we can create new project with dotnet new console -lang C# makeref
, enter the directory and modify the csproj file:
1 2 3 4 5 6 7 8 9 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> </Project> |
Notice that I added AllowUnsafeBlocks
to enable unsafe code (which we already know is not needed but just to keep it simple).
Application
Finally, the application code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Reflection; namespace HijackingNewOperatorNetCore { class Program { static void Main(string[] args) { var allocator = new GenericMemoryAllocator(); // Allocate object through allocator var customlyAlocated = allocator.Allocate<TestClass>(); // Allocate ordinary object var ordinary = new object(); // Hijack method and allocate object HijackNew(); System.Diagnostics.Debugger.Break(); var hijacked = new object(); // Observe that hijacked objects are in generation 2 Console.WriteLine($"Object customly allocated by hand: {GC.GetGeneration(customlyAlocated)}"); Console.WriteLine($"Object created normally: {GC.GetGeneration(ordinary)}"); Console.WriteLine($"Object with hijacked newobj: {GC.GetGeneration(hijacked)}"); } public static void HijackNew() { var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(GenericMemoryAllocator.RawAllocate)).MethodHandle; RuntimeHelpers.PrepareMethod(methodHandle); var myAllocAddress = Marshal.ReadIntPtr(methodHandle.Value, 8); var defaultAllocAddress = GenericMemoryAllocator.GetAllocMethodAddress(); int offset = (int)((long)myAllocAddress - defaultAllocAddress - 4 - 1); // 4 bytes for relative address and one byte for opcode byte[] instruction = { 0xE9, // Long jump instruction (byte)(offset & 0xFF), (byte)((offset >> 8) & 0xFF), (byte)((offset >> 16) & 0xFF), (byte)((offset >> 24) & 0xFF) }; GenericMemoryAllocator.UnlockPage((IntPtr)defaultAllocAddress); Marshal.Copy(instruction, 0, (IntPtr)defaultAllocAddress, instruction.Length); } } class TestClass { public int a, b, c, d; } } namespace HijackingNewOperatorNetCore { class GenericMemoryAllocator { public T Allocate<T>() { var methodTable = typeof(T).TypeHandle.Value; // Get handle to the method table RawAllocate(methodTable); // Allocate the object and set the field, also JIT-compile the method return (T)Dummy; } // Method needs to be static in order to maintain the calling convention public static unsafe IntPtr RawAllocate(IntPtr methodTable) { // Calculate the object size by extracting it from method table and dividing by int size. // We assume that the size starts 4 bytes after the beginning of method table (works from .NET 3.5 to .NET Core 3.1) int objectSize = Marshal.ReadInt32(methodTable, 4) / sizeof(int); // Skip sizeof(int) bytes for syncblock _currentOffset++; // Write the address to method table Memory[_currentOffset] = methodTable; // Get the handle for the newly created object TypedReference newObjectReference = __makeref(Dummy); // Get the handle for the memory TypedReference memoryReference = __makeref(Memory); // Calculate the address of the spawned object. We need to add 2 since we need to skip the method table of the array and the array size var spawnedObjectAddress = *(IntPtr*)*(IntPtr*)&memoryReference + (_currentOffset + 2) * sizeof(IntPtr); // Modify the handle for the new object using the address of the existing memory *(IntPtr*)*(IntPtr*)&newObjectReference = spawnedObjectAddress; // Move within the memory _currentOffset += objectSize; return *(IntPtr*)*(IntPtr*)&newObjectReference; } // Fields needs to be static in order to be accessible from RawAllocate private static bool Is64 = IntPtr.Size == sizeof(long); // Array big enough to be stored in Generation 2 private static IntPtr[] Memory = new IntPtr[102400]; private static int _currentOffset; private static object Dummy = new object(); // This method is used to find the address of the CLR allocation function [MethodImpl(MethodImplOptions.NoOptimization)] private void CreateObject() { new object(); } public static long GetAllocMethodAddress() { // Get the handle to the method creating the object var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(CreateObject), BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle; // JIT-compile methods RuntimeHelpers.PrepareMethod(methodHandle); // Get the address of the jitted method IntPtr methodAddress = Marshal.ReadIntPtr(methodHandle.Value, 16); // Call to internal function differs between architectures, builds etc int offset = 51; // Read the jump offset int jumpOffset = 0; for (int i = 1; i < 5; ++i) { jumpOffset = jumpOffset + (Marshal.ReadByte(methodAddress, offset + i) << (i - 1) * 8); } // Calculate the absolute address long absoluteAddress = (long)methodAddress + offset + jumpOffset + 1 + 4; // 1 byte for jmp instruction, 4 bytes for relative address return absoluteAddress; } // Method to unlock the page for executing [DllImport("libc", SetLastError = true)] static extern int mprotect(IntPtr lpAddress, uint dwSize, uint flags); // Unlocks the page for executing public static void UnlockPage(IntPtr address) { long newAddress = ((long)address) & (long)(~0 << 12); IntPtr na = (IntPtr)newAddress; long length = ((long)address) + 6 - newAddress; // 1 for read, 2 for write, 4 for execute mprotect(na, (uint)length, 1 | 2 | 4); } } } |
This should look familiar to you. There are three main differences from Windows solution.
First, the CreateObject
method in line 107 is now assembled differently. Machine code looks like this (and let’s see lldb in action at the same time):
1 2 3 |
dotnet --version 5.0.101 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
dotnet build Microsoft (R) Build Engine version 16.8.0+126527ff1 for .NET Copyright (C) Microsoft Corporation. All rights reserved. Determining projects to restore... All projects are up-to-date for restore. /makeref/makeref/Program.cs(56,26): warning CS0649: Field 'TestClass.c' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] /makeref/makeref/Program.cs(56,23): warning CS0649: Field 'TestClass.b' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] /makeref/makeref/Program.cs(56,20): warning CS0649: Field 'TestClass.a' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] /makeref/makeref/Program.cs(56,29): warning CS0649: Field 'TestClass.d' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] makeref -> /makeref/makeref/bin/Debug/net5.0/makeref.dll Build succeeded. /makeref/makeref/Program.cs(56,26): warning CS0649: Field 'TestClass.c' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] /makeref/makeref/Program.cs(56,23): warning CS0649: Field 'TestClass.b' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] /makeref/makeref/Program.cs(56,20): warning CS0649: Field 'TestClass.a' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] /makeref/makeref/Program.cs(56,29): warning CS0649: Field 'TestClass.d' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj] 4 Warning(s) 0 Error(s) Time Elapsed 00:00:04.77 |
1 2 3 4 |
lldb bin/Debug/net5.0/makeref (lldb) target create "bin/Debug/net5.0/makeref" Current executable set to 'bin/Debug/net5.0/makeref' (x86_64). |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
r (lldb) r Process 1181 launched: '/makeref/makeref/bin/Debug/net5.0/makeref' (x86_64) Process 1181 stopped * thread #1, name = 'makeref', stop reason = signal SIGTRAP frame #0: 0x00007ffff73ee1ed libcoreclr.so`___lldb_unnamed_symbol15306$$libcoreclr.so + 1 libcoreclr.so`___lldb_unnamed_symbol15306$$libcoreclr.so: -> 0x7ffff73ee1ed <+1>: retq 0x7ffff73ee1ee <+2>: nop libcoreclr.so`___lldb_unnamed_symbol15307$$libcoreclr.so: 0x7ffff73ee1f0 <+0>: pushq %rbp 0x7ffff73ee1f1 <+1>: movq 0xd8(%rdi), %r12 |
1 2 3 4 5 6 7 8 9 |
sos Name2EE makeref.dll HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject (lldb) sos Name2EE makeref.dll HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject Module: 00007fff7de42788 Assembly: makeref.dll Token: 0000000006000007 MethodDesc: 00007fff7deb6ea0 Name: HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject() JITTED Code Address: 00007fff7dda9030 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
sos u 00007fff7dda9030 (lldb) sos u 00007fff7dda9030 Normal JIT generated code HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject() ilAddr is 00007FFFF3D4F463 pImport is 00000000014B3BF0 Begin 00007FFF7DDA9030, size 4d /makeref/makeref/Program.cs @ 113: >>> 00007fff7dda9030 55 push rbp 00007fff7dda9031 4883ec10 sub rsp, 0x10 00007fff7dda9035 488d6c2410 lea rbp, [rsp + 0x10] 00007fff7dda903a 33c0 xor eax, eax 00007fff7dda903c 488945f0 mov qword ptr [rbp - 0x10], rax 00007fff7dda9040 48897df8 mov qword ptr [rbp - 0x8], rdi 00007fff7dda9044 48b8082ce47dff7f0000 movabs rax, 0x7fff7de42c08 00007fff7dda904e 833800 cmp dword ptr [rax], 0x0 00007fff7dda9051 7405 je 0x7fff7dda9058 00007fff7dda9053 e828213879 call 0x7ffff712b180 (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE) 00007fff7dda9058 90 nop /makeref/makeref/Program.cs @ 114: 00007fff7dda9059 48bf000cd67dff7f0000 movabs rdi, 0x7fff7dd60c00 00007fff7dda9063 e8c8953779 call 0x7ffff7122630 (HijackingNewOperatorNetCore.GenericMemoryAllocator.RawAllocate(IntPtr), mdToken: 0000000006000006) 00007fff7dda9068 488945f0 mov qword ptr [rbp - 0x10], rax 00007fff7dda906c 488b7df0 mov rdi, qword ptr [rbp - 0x10] 00007fff7dda9070 e81370feff call 0x7fff7dd90088 (System.Object..ctor(), mdToken: 000000000600045E) 00007fff7dda9075 90 nop /makeref/makeref/Program.cs @ 115: 00007fff7dda9076 90 nop 00007fff7dda9077 488d6500 lea rsp, [rbp] 00007fff7dda907b 5d pop rbp 00007fff7dda907c c3 ret |
If you count all bytes you’ll find out that the offset is now 51.
Second difference is the method descriptor. Function address used to be 8 bytes from the beginning, now it’s 16 (in line 121):
1 2 3 4 5 6 7 |
memory read -count 64 00007fff7deb6ea0 (lldb) memory read -count 64 00007fff7deb6ea0 0x7fff7deb6ea0: 07 00 08 03 08 00 28 00 28 5b da 7d ff 7f 00 00 ......(.([.}.... 0x7fff7deb6eb0: 30 90 da 7d ff 7f 00 00 08 00 0b 03 09 00 a8 00 0..}............ 0x7fff7deb6ec0: 30 5b da 7d ff 7f 00 00 80 8a da 7d ff 7f 00 00 0[.}.......}.... 0x7fff7deb6ed0: 09 00 0e 03 0a 00 8a 00 00 00 00 00 00 00 00 00 ................ |
Finally, we cannot use VirtualProtectEx anymore as we’re on Linux. We need to go with mprotect:
1 2 3 4 5 6 7 8 9 10 11 |
[DllImport("libc", SetLastError = true)] static extern int mprotect(IntPtr lpAddress, uint dwSize, uint flags); public static void UnlockPage(IntPtr address) { long newAddress = ((long)address) & (long)(~0 << 12); IntPtr na = (IntPtr)newAddress; long length = ((long)address) + 6 - newAddress; // 1 for read, 2 for write, 4 for execute mprotect(na, (uint)length, 1 | 2 | 4); } |
mprotect
requires the address to be aligned to a page boundary (which is 4096 bytes on my machine) so I clear lowest 12 bits (line 6 in the listing above). Next, I calculate new offset of the method (I’m actually not sure if that’s needed). Finally, I enable all permissions for the page in line 10.
And just for the sake of completeness, final output:
1 2 3 4 5 |
dotnet run Object customly allocated by hand: 2 Object created normally: 0 Object with hijacked newobj: 2 |
Final notes
As you can see, there is no magic in this approach, it’s just a bunch of bytes which we can modify in the same way as long as we’re on the same architecture. However, keep in mind the following:
- I do not recommend using this in production code. I do use things like these in real applications but this is always risky and requires good understanding of all internals
- This is just one of multiple allocation methods provided by .NET. If you want it to be “production ready” then you need to update all of them
- Since you override the method globally, you can’t control easily when it’s called. In other words, .NET will use your logic as well so you need to take care of all memory management (or do some fancy juggling to call .NET methods when you actually need to allocate some new memory)
- Keep in mind that .NET scans the heap and requires it to be parseable. Be careful with what you allocate and how. Also, make sure your objects are pinned or that you have good concurrency management (since GC can kick in anytime and move objects around)
Have fun!