\documentclass{ctexbeamer} \usepackage{hyperref} \newcommand{\uref}[2]{\href{#1}{\uline{#2}}} \title{逆向工程和DRM的破解} \author[vimacs]{vimacs <\url{https://vimacs.wehack.space}>} \institute[LCPU]{Linux Club of Peking University} \date{Nov 24th, 2018} \begin{document} \begin{frame} \titlepage \end{frame} \begin{frame}{Index} \tableofcontents \end{frame} \section{逆向工程概念} \frame{\tableofcontents[currentsection]} \begin{frame}{逆向工程} 逆向工程是一个解构人造客体,用于揭露它的设计、架构,或从这个客体提取知识的过程。 逆向工程可以用于机械工程,电子工程,软件工程,化学工程,系统生物学等领域。 \end{frame} \begin{frame}{逆向工程的用途} \begin{itemize} \item 军事和商业用途:学习对手的产品 \item 生产替代品 \item 安全分析 \item 寻找接口,重用产品,\ldots \end{itemize} \end{frame} \begin{frame}{计算机软件逆向工程实例} \begin{itemize} \item \uref{http://www.clifford.at/icestorm/}{Project IceStorm} \item \uref{https://github.com/freedreno/freedreno}{Freedreno}, \uref{https://gitlab.freedesktop.org/panfrost}{Panfrost}, \uref{https://gitlab.freedesktop.org/lima}{Lima}, \uref{https://nouveau.freedesktop.org}{Nouveau} \item \uref{https://review.coreboot.org/c/coreboot/+/5786}{Sandy Bridge native RAM init} \item \uref{http://zmatt.net/unlocking-my-lenovo-laptop-part-1/}{Unlocking my Lenovo laptop} \item \uref{https://en.wikipedia.org/wiki/Skype_protocol}{Skype protocol} \end{itemize} \end{frame} \section{软件逆向工程基础} \frame{\tableofcontents[currentsection]} \begin{frame}{从程序员的角度了解计算机系统} \uref{http://csapp.cs.cmu.edu/}{Computer Systems: A Programmer's Perspective}(CSAPP)是课程《计算机系统导论》(ICS)的教材,内容包括: \begin{itemize} \item 数据表示 \item 程序的机器级别表示:汇编语言 \item 处理器体系结构:流水线,高速缓存,虚拟内存 \item 链接和装载:\uref{https://linker.iecc.com/}{Linkers and Loaders}, \uref{https://book.douban.com/subject/3652388/}{《程序员的自我修养:链接、装载与库》} \item 操作系统:内存管理(malloc的实现),信号,多线程,网络和POSIX编程 \end{itemize} \end{frame} %\begin{frame}{程序构建的过程} %\end{frame} \begin{frame}{指令集架构} 指令集架构(Instruction Set Architecture)是软件和硬件的接口,实现相同ISA的硬件,可以运行相同的软件,而无需修改或重新编译这个软件。 一个ISA的设计包含多种特性,例如: \begin{itemize} \item 指令长度是否定长 \item 是否只有load/store指令能访问内存 \item 寻址模式 \item 非对齐内存访问的支持 \item 特殊用途的寄存器 \item 是否使用条件码,是否有cmov类指令 \item 浮点操作 \item 中断和异常 \item 虚拟化支持 \end{itemize} \end{frame} \begin{frame}{寄存器} 寄存器是处理器中的一组存储单元,一个ISA定义了固定数量的寄存器。寄存器是访问速度最快的存储单元,因此优化的程序会尽量将常用的数据放入寄存器中,减少内存的访问。 IA32有8个通用寄存器,AMD64增加至16个。MIPS,RISC-V,ARMv8有31个通用寄存器,并有一个始终为0的寄存器。 \end{frame} \begin{frame}{栈} 栈是一个重要的数据结构。在程序中,栈常用于存放一个函数的局部变量、返回地址、临时保存的寄存器。 一个ISA通常会约定一个存放栈顶指针的寄存器,在汇编中通常记为sp. 在x86中,push和pop等指令会改变栈顶指针sp/esp/rsp的值,并在寄存器和栈顶指针指向的内存区域之间传送数据。 \end{frame} \begin{frame}{汇编语言} 汇编语言是一种低层次的程序设计语言,它包含对应于处理器指令的助记符,还有标签、宏和一些简单的汇编命令。汇编器将汇编语言编写的程序直接翻译成处理器可以执行的机器指令。 \begin{figure}[htbp] \centering \includegraphics[scale=0.3]{asm.pdf} \end{figure} 反汇编是将指令序列还原为一系列汇编语言指令的过程。而反编译则是将指令序列还原为更高层次的语言,通常是C语言。 \end{frame} \begin{frame}{x86基础} CSAPP讲解了一些基本的x86指令,微机原理和计算机组成的教材会讲解8086指令系统。此外可以参考以下8086汇编语言教程: \begin{itemize} \item \uref{https://book.douban.com/subject/1215178/}{王爽《汇编语言》} \item \uref{https://book.douban.com/subject/20492528/}{李忠/王晓波/余洁《x86汇编语言——从实模式到保护模式》} \end{itemize} 遇到不认识的指令,需要参考指令手册: \begin{itemize} \item \uref{https://software.intel.com/en-us/articles/intel-sdm}{Intel Software Developer Manuals} \item \uref{https://www.amd.com/en/support/tech-docs}{AMD Architecture Programmer's Manual} \end{itemize} \end{frame} \begin{frame}{C/C++的逆向} 参考 \uref{https://beginners.re/}{Reverse Engineering for Beginners}. 中文书名《逆向工程权威指南》。 逆向C/C++程序,需要了解C/C++中各种语言特性的实现: \begin{itemize} \item 控制结构:顺序、分支、循环 \item switch-case和间接转移 \item 数组和结构体 \item 函数和ABI \item C++: this指针,虚函数 \item 常见算术优化技巧:\uref{https://www.hackersdelight.org/}{Hacker's Delight} \end{itemize} \end{frame} \section{逆向工具} \frame{\tableofcontents[currentsection]} \begin{frame}{静态分析和动态分析} \uref{https://en.wikipedia.org/wiki/Static_program_analysis}{静态分析}指在不执行程序的条件下分析程序的行为。而动态分析则是在一个真实或虚拟的处理器上执行程序,通常需要足够的测试输入。 调试器是一个测试和调试目标程序的工具,主要功能包括单步执行、断点、跟踪变量等功能。 \end{frame} \begin{frame}{radare2} \uref{https://radare.org}{radare2}是一个逆向工程框架,具有以下功能: \begin{itemize} \item 反汇编和汇编 \item 二进制编辑 \item 二进制分析 \item 脚本 \item 调试 \end{itemize} radare2项目还带有几个实用工具: \begin{itemize} \item rax2: 计算器和数值转换器 \item rabin2: 类似readelf的文件信息显示工具,支持多种架构和多种目标文件格式 \item radiff2: 二进制diff工具 \item rasm2: 汇编和反汇编器 \end{itemize} \end{frame} \begin{frame}{学习使用radare2} 首先安装radare2.考虑到大多数发行版中radare2的版本太老,建议从GitHub获取源码后执行 sys/user.sh 安装。 radare2主要由命令行控制。学习radare2可以参考\uref{https://github.com/radare/radare2book}{radare2book}. 此外,radare2还有多个GUI前端实现,目前开发较活跃的是\uref{https://github.com/radareorg/cutter}{Cutter}.radare2还可以自身启动一个HTTP服务器,通过浏览器控制radare2. \end{frame} \begin{frame}{radare2命令:分析} \begin{itemize} \item 分析目标文件:aa, aaa, aaaa \item 函数标记:af. 用于手动标记函数,在自动分析没分析出一个地址指向函数的时候使用。 \item 反汇编:pd, pdf, pi, pif \item 打印:p, x(即px) \item 搜索:/ \item 交叉引用:axt \end{itemize} \end{frame} \begin{frame}{radare2命令:标记} \begin{itemize} \item 标记全局变量:f \item 重命名函数:afn \item 注释:CC \item 工程:Ps. 为了在下次启动radare2时能使用以前的分析和标记信息,可以把当前的工作保存为工程。用 \textbf{r2 -p} 加载工程。工程文件为 \textit{\~{}/.local/share/radare2/projects//rc},用 \textbf{r2 -i} 可以加载此文件。 \end{itemize} \end{frame} \begin{frame}{radare2二进制编辑} 用radare2的\textit{-w}参数打开文件,或在radare2中执行\textit{oo+}命令,可以用可写的模式打开文件,此时可以对文件进行编辑。也可以执行\textit{r2 malloc://size}打开一个可读写的缓冲区。 \begin{itemize} \item 写入一串十六进制表示的字符:wx \item 写入汇编指令:wa \item 保存缓冲区中内容至文件:wtf \end{itemize} \end{frame} \begin{frame}{radare2其他特性} \begin{itemize} \item 类UNIX特性:重定向、管道和类似grep的\~{}命令 \item 脚本 \item 扩展:如反编译器r2dec,可以用radare2的包管理器r2pm安装 \end{itemize} \end{frame} \begin{frame}{调试器} 常用的自由软件调试器: \begin{itemize} \item GDB: GNU调试器,支持多种平台,支持源码级调试 \item \uref{https://x64dbg.com/}{x64dbg}: Windows平台下用于调试PE文件的调试器,支持IA32和AMD64架构,界面和功能和OllyDbg高度相似 \item winedbg: wine的调试器,可以在非Windows平台下调试Windows程序 \end{itemize} 基本用法:断点,单步执行(步过和步进),查看寄存器和内存 \end{frame} \section{DRM破解实例} \frame{\tableofcontents[currentsection]} \begin{frame}{什么是DRM} DRM(Digital Rights/Restrictions Management)是指用于限制数字媒体的使用的技术。 例子: \begin{itemize} \item \uref{https://en.wikipedia.org/wiki/Sony_BMG_copy_protection_rootkit_scandal}{Sony BMG} \item \uref{https://www.defectivebydesign.org/amazon-kindle-swindle}{Amazon} \item \uref{https://en.wikipedia.org/wiki/DeCSS}{DVD Content Scramble System} \end{itemize} DRM的危害:\uref{https://fsfs-zh.readthedocs.io/zh/latest/right-to-read/}{《阅读的权利》} \end{frame} \begin{frame}{如何破解DRM} \uref{http://esec-lab.sogeti.com/static/publications/10-hitbkl-drm.pdf}{Digital content protection - How to crack DRM and make them more resistant} 破解DRM的方法: \begin{itemize} \item 找出解密后的内容 \item 找出解密函数,重用解密函数 \end{itemize} \center{DRM = security through obscurity} \end{frame} \begin{frame}{实例:北大图书馆学位论文DRM} 从\uref{http://thesis.lib.pku.edu.cn/}{北京大学学位论文库}下载的学位论文需要使用一个专用的PDF阅读器才能打开。 \begin{itemize} \item 对于有DRM的文档,不能打印,不能选择文档内容 \item 阅读期限(可能还有阅读次数)限制 \end{itemize} \end{frame} \begin{frame}{PDF文件结构} 参考Adobe的\uref{https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf}{PDF Reference},我们可以了解PDF文件的结构。 PDF文件结构如下: \begin{itemize} \item PDF文件头:一行 \%PDF-x.y 注释,通常再增加一行二进制注释 \item 多个PDF对象 \item 交叉引用表 \item 文件结尾 \end{itemize} PDF对象是PDF文档的主要内容,有8种类型:布尔、数值、字符串、名字、数组、字典、流、空对象。 可以用文本编辑器打开PDF文件学习PDF文档的结构。 \end{frame} \begin{frame}{从字符串中寻找信息} strings(1)可以打印出一个文件中可打印的字符串,但是无法显示UTF-16字符串。 在radare2中先用aaa分析,在分析函数的过程中可以找到函数引用的数据,如果是字符串,则会进行标记。之后用f命令可以看到程序中被引用的字符串,包括UTF-16字符串。 \end{frame} \begin{frame} \begin{figure}[htbp] \centering \includegraphics[scale=0.23]{strings.png} \end{figure} 从程序中的assert语句泄漏的信息,可以推断程序静态链接了PDF库pdfium的3100版本。我们可以下一份pdfium的源码并检出chromium/3100分支。 \end{frame} \begin{frame}{静态分析:标记函数} assert语句不但泄漏了源码路径,还有assert语句的行号和assert所断言的条件,因此可以很容易地找到assert语句对应的函数。 此外,还可以对比汇编和源码,找出汇编里面的函数调用对应的源码中的函数。在这个过程中,还可以找出哪些代码被修改过。最终,可以发现pdfium中的函数\textbf{CPDF\_Parser::SetEncryptHandler}被修改,由于这是解密相关的函数,可以推断里面调用了PDF DRM相关的解密过程。 \end{frame} \begin{frame}{调试:从程序执行路径中找关键函数} 由于C++的虚函数是间接调用,r2的静态分析不能直接找到虚函数,也不能提供引用信息。此时可以借助调试器。 在静态分析的过程中发现,程序中使用了\textbf{IsDebuggerPresent}这个API检测调试器,我们在x64dbg中隐藏调试器即可。 跟踪此前提到被修改的\textbf{CPDF\_Parser::SetEncryptHandler}函数,可以发现在阅读器打开有DRM的PDF文件时,会调用位于0x0040b080(radare2中的地址,由于重定位,x64dbg中地址的高16位不同)的虚函数,因此我们可以对这个函数进行分析。 \end{frame} \begin{frame}{隐藏调试器} \begin{figure}[htbp] \centering \includegraphics[scale=1.3]{hide-debug.png} \end{figure} \end{frame} \begin{frame}{调试:找到关键循环} x64dbg的追踪工具可以自动执行步进/步过操作,通过使用自动步过功能,可以找到执行次数比较多的循环,这样的循环很可能和DRM的解密相关。 \begin{figure}[htbp] \centering \includegraphics[scale=2]{auto.png} \end{figure} \end{frame} \begin{frame}{静态分析:分析结构体成员位置} 通过对比汇编和源代码,可以分析出一个结构体/类中每个成员的偏移地址。利用这个信息,可以更快地找到程序中哪些代码修改了我们关心的数据。 \begin{figure}[htbp] \centering \includegraphics{class.png} \end{figure} 在这里,我们分析 \textbf{CPDF\_CryptoHandler::Init} 等函数,可以判断 \textbf{CPDF\_SecurityHandler} 类中加密密钥的偏移,再通过这个信息,可以找到哪些代码改写了加密密钥。 \end{frame} \begin{frame}{函数识别:通过特殊常数识别算法} 在分析某些函数时,可以找到一些常数,通过它可以识别出使用的算法。 在这个阅读器中,可以找到以下常数: \begin{itemize} \item 0x6a09e667,0xbb67ae85: SHA256 \item 0x6c078965: MT19937 \end{itemize} \end{frame} \begin{frame}{在MuPDF中实现PDF文件的解密} 在前面的分析中,我们可以发现阅读器打开有DRM的PDF时,用自身的加密处理函数算出一个密钥,解密过程则是使用PDF文档的标准解密过程,用算出来的密钥进行解密。 MuPDF是一个用C语言编写的简单高效的PDF阅读器和库,我们修改MuPDF使它能读取有DRM的文档。 开始的时候,我用x64dbg在内存中提取计算出的解密密钥,然后硬编码至MuPDF,它可以打开相应的文档。 在完全找出解密算法之后,在MuPDF中实现这个算法,然后测试,发现可以打开图书馆中有DRM的PDF文档,于是完成了DRM的破解。 最终代码已上传至 \uref{https://git.wehack.space/mupdf/log/?h=xjc-rebase}{git.wehack.space/mupdf}. \end{frame} \section{总结} \frame{\tableofcontents[currentsection]} \begin{frame}{总结} 现在我们已经初步地了解了逆向工程,并且看到了怎样使用这项技术破解简单的软件限制。 如果你想更深入地了解逆向工程,提高自己的逆向水平,可以参加CTF竞赛,观看逆向工程相关的会议资料。 \begin{itemize} \item \uref{https://ctf-wiki.github.io/ctf-wiki/reverse/introduction/}{CTF Wiki: 软件逆向工程简介} \item \uref{https://recon.cx/}{REcon} \item \uref{https://www.defcon.org/}{DEF CON} \item \uref{https://radare.org/con/}{r2con} \end{itemize} \end{frame} \end{document}