x64 参数传递

近日某青年似乎研究到x64(AMD64 in history)的参数传递调用约定,这玩意确实和x86上面不一样,于是我决定写点字介绍一下。

x64首先把x86原有的寄存器扩展到了64位,然后更增加了8个通用寄存器:R8~~R15,嗯,确实有点RISC的味道。

  • x64上面默认的函数调用约定是fast call,也就是ABI是fast call。
  • 前四个参数传递顺序是RCX,RDX,R8,R9,其余的参数通过压栈传递。注意这里有一个细节:前四个参数也是占用栈空间的,或者说,栈需要为前四个参数保留32个字节。
  • 小于64位的参数传递时高位并不填充零,大于64位需要按照地址传递。
  • 返回值在RAX
  • 被调用函数不负责清栈
  • RAX,RCX,RDX,R8,R9,R10,R11是“易挥发”的,其余寄存器需要保护。
  • 栈需要16字节对齐。

OK,以上是理论上的东西,实际上编译器会把一切都搞糟,具体可以看一下两篇文章:

The history of calling conventions, part 5: amd64

Although the x64 calling convention reserves spill space for parameters, you don’t have to use them as such

这一切看起来非常的令人迷惑,个人推荐的解决办法有三种:

  • 使用ICC或者GCC,这样可以用内联汇编,于是绝大多数问题都解决了。
  • 使用编译器 intrinsics 指令,这玩意好像应该翻译成内省指令,还是内醒,总之不管了,知道是这玩意就行。
  • 手工代码发射,不过这个最好还是用在API hook的时候,写算法真还不如用MASM,不过MASM免不了还得和参数调用约定捉迷藏。

让 UV4 支持STC 单片机

Keil 本身并没有自带 STC 8051 单片机的数据,这样用起来就非常令人不爽,好在国内有个青年已经整理了一个补丁

这个补丁包含三个修正:

  • 汉字bug修正,这个是老生常谈了,很奇怪Keil十年来都没有修正这个,说明其对中国市场完全漠视的态度。
  • STC头文件
  • STC器件数据库

OK,就一般应用来说这个补丁已经足够了,但是对于追求完美的人来说还略有不足,因为这个补丁会覆盖掉UV4自带的器件数据库UV4.cdb。打上补丁就丢失了最新的UV4数据库。

怎么解决这个问题呢,其实自UV2以来,就支持用户自定义器件数据库的功能,我们可以按照以下方法来修改:

  • 备份UV4.cdb
  • 安装补丁
  • 修改 C:\Keil\TOOLS.INI,在[UV2]一节下面增加

    CDB0=uv4\custom.cdb("STC 8051")

  • 将补丁安装的UV4.cdb改名为custom.cdb
  • 将备份的UV4.cdb恢复

这样下次我们选择器件数据库的时候,UV4就会问我们要从哪个库里面选取了。

    用户模式驱动模型(UMDF)简介

    微软在新一代Windows驱动开发包WDK中提供了一种新的驱动模型:用户模式驱动模型(UMDF)。

    UMDF和传统的驱动程序差异非常大,简要说来,UMDF是这样的:

    • UMDF是基于COM思想的,运行于用户模式(RING3)的驱动程序模块。

    那么,这种驱动模型带来什么变化呢?

    首先基于COM思想,引入接口机制,可以把相关联的函数分门别类进行组织,使得驱动代码清晰明了;其次,运行在RING3的驱动,大幅度降低了驱动程序在稳定性和安全性上面的风险,UMDF驱动崩溃不会导致bugcheck(蓝屏),并且UMDF驱动的宿主进程是在受限的用户身份下运行的,不是受信任的系统内核模块。可以在UMDF里面使用Win32 API。

    运行于RING3的UMDF对于程序员开说至少带来两个额外好处:

    • 驱动程序不需要强制数字签名,因为UMDF驱动不是系统信任模块,所以在x64下面的部署更加方便。特别是个人开发者可能无法承受WHQL的费用,或者其他原因暂时无法WQHL的情况下,使用UMDF是一个较好的选择。
    • 调试难度大幅降低,不再需要SoftICE和Syser之类的单机内核调试器或者WinDBG之类的双机调试,我们可以用WinDBG或者VS调试器attach到UMDF宿主进程即可进行调试。可以参考调试UMDF驱动

    UMDF的局限

    以上简要介绍了UMDF的种种优势,下面来说说UMDF的局限性。UMDF由于是一种较新的驱动程序模型,所以其功能还在不断的演变和增加,但是至少在目前的情况下,UMDF还是受到较大限制的:

    • 无法直接访问硬件,不能直接处理中断,无法DMA。
    • 无中断计时循环,因为RING3代码会随时被系统调度。
    • 无法访问系统内核数据和系统内存空间。
    • 无法作为系统内核驱动栈的一层。

    可见受到的限制是很大的,那么什么最适合使用UMDF呢?简单的说,新开发的驱动如果UMDF能满足需要就使用UMDF。目前,使用UMDF最多的是USB驱动。

    USB驱动是一类最繁杂的驱动,包括很多个子类,其中HID类由于可能参与系统自举和涉及到安全问题,是不能用UMDF的,和系统内核流接口打交道也不能用UMDF。如果我们只是为了给自己的MP3播放器,手机管理软件,数码相机,数据采集设备等编写驱动,那么就可以采用UMDF。

    gDbgLoad

    现在的破解组织也越来越不敬业了,破解个软件,RSA Key替换了,KEYGEN也写了,结果最后留个尾巴,得在调试器里面才能用被破解的软件……真够可以的。

    得了,还是自己写个调试器,把软件Load起来吧。

    gDbgLoad for gDebugger 5.x

    PS Jailbreak 原理分析

    很久没有写技术文章了,最近PS3破解比较火热,我就分析一下。

    首先需要介绍一些破解用到的底层技术:

    • Heap overflow

    Heap(堆)是所有编程语言底层内存管理的基础,即便用汇编可以胡乱搞,写大程序也肯定还会用到堆管理器。在cc++里面,当我们用malloc、free、new、delete之类的内存操作函数,我们就和堆管理器打交道了。堆管理器的实现有很多种,算法各有不同,有用红黑树,也有简单的linklist,还有为了提高性能只能分配固定尺寸的内存池。

    这些不同的实现大多数有一个典型特征:对齐。由于现代计算机体系结构的缘故,对齐的数据才有较高的访问速度,在某些硬件体系架构上,访问非对齐内存甚至会直接导致machine check。由于对齐,比如我们请求100字节,其实堆管理器会自动选取一个对齐的尺寸,然后分配那么大的一块,比如说128字节,来返回给我们。当然这个描述是不精确的,下面解释一下。

    堆管理器需要维护它所管理的每一块内存,也就是说,要维护每块内存的相关信息,比如长度,向前向后指针,状态(未分配,已分配等),那么这个状态怎么维护呢?一种很常见的做法是,放在这个内存块的首部。

    假设现在程序员申请100字节,内存块信息本身16字节,那么堆管理器就会找一个128长度的内存块(如果没有这么大的,它会按照一定的算法,比如根据每块内存的信息合并未分配块),把这块内存的首指针+16返回给程序员,于是程序员很高兴,他有了100字节的内存。注意,在古老的系统上其实他可以安全的访问128-16字节。在现代系统上就不行了,堆管理器会在这100字节前后做特殊标记,在硬件页的尺度上也会设定一定的保护(请参考NXDEP),如果你写过了100,当free这块内存的时候,堆管理器会发现标记被破坏,也就是heap overflow了。

    如果我们在这100字节的内存上写了256字节的数据呢?一个极大概率的事件是:我们破坏了邻近的内存块的信息。这就为恶意攻击制造了机会。

    • USB

    USB是一种不对等总线,也就是有主机和客户机的区别,所有的操作都由Host发出。USB有两个比较重要的概念:地址,端点。

    先说地址,Host是没有地址的,只有设备才有。类似的概念是MAC地址,局域网用普通hub大家连在一起,所有的数据包都会经过你的网卡,只有符合你的MAC的数据包网卡才会接受(注意这是最原始的情况,请网络帝指正)。

    当一个新的USB设备插入host,比如优盘,由于USB接口上的电平变化,HOST控制器得知有设备插入并且区分出是1.x还是2.0(上拉下拉电阻不同),此时设备(优盘)的USB地址是0,HOST控制器和这个地址通信,并给设备指定一个新的USB地址,范围在1~~127(可以想象是DHCP过程),随后HOST控制器就用新的USB地址来访问设备了,每一个新插入的设备都会这么处理,于是你插上两个一样的优盘,它们也会得到不同的USB地址,于是系统就能区分开两个优盘了。

    USB HOST控制器给设备分配了新的USB地址以后,就开始问:你是什么东西啊,你有什么功能阿如此如此,设备会用描述符(descriptor)的方式应答(descriptor格式USB规范里面有):我的VID是xx,我的PID是yy,我的名字叫zz……如此如此。

    刚才说到了USB地址,这个数值USB收发器控制电路会保存下来用于今后的通信,但是因为这个东西比较特殊,所以大多数的芯片是不能手工修改自己的USB地址的。

    端点是真正执行数据通信的端口,端点0是始终可以用的,被称为控制端点,具体就不细说了。

    ok,上面废话这么多,下面开始说主题:PS Jailbreak。

    一句话概括:PS Jailbreak通过精心构造的特殊USB描述符,使PS3处理这些描述符的时候Heap overflow,导致代码注入进而获取了GameOS的访问权限。

    下面详细介绍PS Jailbreak(以下简称JB)攻击过程

    (绝大多数翻译自http://ps3wiki.lan.st/index.php/PSJailbreak_Exploit_Reverse_Engineering,并加上必要的解释说明):

    JB设备的外形(注意不是物理外形,是技术上的)是一个“六口 USB Hub”,注意我用了双引号,这东西只是对外宣称自己是Hub,实际上只是为了满足USB协议的需求,并没有完整地实现USB Hub的全部功能。

    PS3开机的时候,在特定的情况下会在USB接口上搜索官方的JIG设备(我不知道这玩意的具体功能,操作方法是按POWER后200ms内按Eject),JB利用这个特性在开机检测JIG的时候在其虚拟的六个USB Port上轮番插拔6个设备(…………),由于系统需要为每个设备的处理过程分配内存,通过精心构造的USB描述符,实现了Heap overflow。

    Port1:Hub初始化以后,第一个设备插入,pid/vid 0xAAAA/0x5555,有4个配置,每一个长度都是0xf00,由于这个长度没有超过4K的页面,所以推测PS3系统的malloc会为每一个配置分配一个4k的内存页。为什么要4个呢,因为可能已经有空闲内存了,用4个是保证有足够大的概率把页面对齐到4k边界上。然后JB重新报告其配置为18字节。其实在这个比较长的配置里面包含有payload(也就是用于注入攻击的功能代码)。

    Port2:PS3读取完成1号设备的描述符以后,JB切换回Hub USB地址,然后谎称第二个设备插入,pid/vid 0xAAAA/0xBBBB,这个设备有一个22字节的描述符,只有前18个字节是有意义的,最后4个意义不明。

    Port3:随后这个设备插入,pid/vid 0xAAAA/0x5555,和第一个一样但是描述符不一样,他有两个配置描述符,每一个长度为0xa4d,大部分的数据被认为是垃圾。按照对堆管理器的猜测,这些描述符会被放在一个新的4k页面上,紧随之前的两个设备。

    Port2:拔出。这个设备的拔出导致一个显而易见的结果,第一个设备和第三个设备之间分配的内存被释放了。

    OK,上面这样的折腾,准备好了真正的攻击环境上下文。

    Port4:连接。pid/vid 0xAAAA/0x5555,有三个配置描述符。

    配置描述符A,18字节的正常描述符。

    配置描述符B,和A一样的描述符,但是当PS3初次读取它之后,它把自己的长度变成了0字节。这是破解的关键之处,但是其具体含义含混不清,它导致了配置描述符C后面的数据覆盖了某一个malloc的边界标志,很可能是属于Port3的。但是这个溢出的详细原因恐怕得看攻击代码本身了。

    配置描述符C,这个描述符开始和A是一样的,但是最后多了14个字节。

    .. .. 3e 21 00 00 00 00
    fa ce b0 03 aa bb cc dd
    80 00 00 00 00 46 50 00
    80 00 00 00 00 3d ee 70

    前六个字节被认为是占位(但是我不这么认为,by hyperiris),接下来是一个magic number,fa ce b0 03 aa bb cc dd,用英语来看就是FACEBOOK AABBCCDD,随后的数据是一个指针,它覆盖了malloc块的边界标记,这会导致malloc在之后处理这个块的时候发生错误,使其按照攻击者的意愿在指定的位置操作内存。(这是两个64位的指针,by hyperiris)

    Port5:当Port4完成工作以后,假的JIG被插入到了Port5,它和SONY官方的JIG PID/VID 0x054C/0x02EB 是一样的,推测和官方的配
    和端点一致。

    可以猜测由于这个玩意(JIG)是PS3已知的设备,PS3系统不会为它在堆上分配内存。

    随后PS3发送64字节的数据要求JIG进行认证,然后JB返回64字节的应答。PS3将会分配内存来保存这个应答(!!!!),由于之前malloc块的边界标记已经被Port4的插入所修改,所以这次内存分配将会在一个设计好的位置,也就是某一个函数的前面,(某函数24字节偏移之前),然后函数的前面被这64字节覆盖了(!!!!)

    由于系统的JIG认证代码没有被patch,所以JB返回的数据被验证无效。

    Port3:拔出。JB现在通知PS3,Port3拔出,这导致PS3释放为Port3设备配置描述符分配的内存,也就是被Port4设备描述符覆盖的那个。

    于是Shell code此刻被调用,R3寄存器现在指向的是Port3配置描述符的内存边界标记位置。

    Shellcode:

    ROM:00000018                 ld      %r4, -0x10(%r3)
    ROM:0000001C                 ld      %r3, -8(%r3)
    ROM:00000020
    ROM:00000020 loc_20:                               # CODE XREF: sub_18+14�j
    ROM:00000020                 ld      %r5, 0x18(%r3)
    ROM:00000024                 addi    %r3, %r3, 0x1000
    ROM:00000028                 cmpw    %r4, %r5
    ROM:0000002C                 bne     loc_20
    ROM:00000030                 addi    %r6, %r3, -0xFE0
    ROM:00000034                 mtctr   %r6
    ROM:00000038                 bctr

    R4保存的就是0xfaceb003aabbccdd,然后R3加载0x8000000000465000,然后shellcode从0x8000000000465000开始搜索每一个4k边界,直到在某一个位置发现0xFACEB003AABBCCDD,发现之后,shellcode跳转到那里,从偏移0x20处开始执行。

    清理:现在一切都清静了,Port5,4,1都将被拔出。Payload应该在Port1拔出之前将自己复制到一个不会被释放的内存块里。

    Port6:这个设备没有任何的实际意义/功能,vid/pid 0xAAAA/0xDEC0,只响应一个控制传输0xAA,当PS3给这个设备发送这个控制传输,JB就知道自己成功了,并点亮LED。

    在原始的JB里面,payload会检测这个设备是不是被拔掉,如果拔掉了,就调用LV1_Panic宕机。PSGroove把这个傻逼功能去掉了。

    至于payload代码,和PS3版本有关,具体资料没有,因为需要ps3 main memory dump。

    OpenMP vs WinSxS

    VS2005

    今天把一个工程设置为支持OpenMP, 在代码中使用几个 #pragma omp parallel for 编译后, 程序启动时提示找不到 vcmp.dll.

    由于项目中别的工程之前我也用了OpenMP, 并没有出现这个错误, 经过比对发现是忘记 #include <omp.h>

    仔细看看 omp.h, 就不难理解为什么需要包含这个头文件了.

    为了解决著名的dll hell, 微软在现在的windows系统中引入了WinSxS, 将不同版本的系统共享 dll 分门别类的放在描述性的文件夹下, 文件夹的命名规则是已经严格定义的. 应用程序自身需要包含清单文件 manifest, 来说明自己需要动态链接哪一个版本的什么dll.

    omp.h 的作用之一就是生成 vc openmp 的 manifest, 来通知系统 loader 加载 WinSxS 下哪一个 vcmp.dll, 如果没有 manifest, 系统不知如何加载, 自然会提示找不到.

    STLPort & VC++2010

    升级到VS2010以后,还没有编译过依赖STLPort的工程,今天在家编译一个自己以前的项目,发现STLPort和VC++2010存在一个兼容性问题。

    Debug编译:

    1>e:stlport-5.2.1stlportstl_cstdlib.h(158): error C2084: function ‘__int64 abs(__int64)’ already has a body
    1>          d:program files (x86)microsoft visual studio 10.0vcincludestdlib.h(471) : see previous definition of ‘abs’

    Release下不存在这个错误。

    解决办法:注释掉_cstdlib.h(158)这一行即可。

    另外在STLPort论坛已经看到有这个问题的报告,相信在未来版本中会被修正。

    VC++2010 bug

    软件复杂了,总会有或多或少的bug,今天就遇到一个,从表现来看是VC++ 2010的bug,因为这个确实很难测试覆盖。

    http://social.microsoft.com/Forums/zh-CN/visualcpluszhchs/thread/cae52fba-6af8-4f93-bfae-209d44d4f134/

    我这里有一个VC++2005的工程编译正常,之前曾升级到VC++2008也编译正常,今天升级到2010以后,编译失败,信息如下:

    1>—— Build started: Project: Cal, Configuration: Debug Win32 ——

    1>LINK : fatal error LNK1104: cannot open file ‘E:workCalDebugCal.lib’
    1>
    1>Build FAILED.

    问题是Cal本身是一个dll工程,编译生成的应该是Cal.dll和Cal.lib,不可能link阶段依赖自己的lib。

    检查升级后的工程配置没有发现问题。磁盘可用空间足够,也没有只读的Cal.lib或者写入不能的目录。

    解决办法:

    整个项目在转换到2010前有一些工程不存在(unloaded),因为我并没有在当前这台机器上完整check out。

    在转换过程中看起来是VC2010错误的分析了依赖关系,把Cal依赖的别的工程错误的指向了Cal自己,变成了一个循环依赖。

    这个问题我暂时用下面的方法解决了:

    处理前,linker的命令行参数是: /OUT:".debugCal.dll" /INCREMENTAL /NOLOGO /DLL "opengl32.lib" "glew32.lib" "glu32.lib" "E:workCaldebugCal.lib" /DEF:".Cal.def" /MANIFEST /ManifestFile:".DebugCal.dll.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level=’asInvoker’ uiAccess=’false’" /DEBUG /PDB:".debugCal.pdb" /MAP /MAPINFO:EXPORTS /SUBSYSTEM:WINDOWS /PGD:"E:workCaldebugCal.pgd" /TLBID:1 /DYNAMICBASE:NO /NXCOMPAT /NOASSEMBLY /MACHINE:X86 /ERRORREPORT:QUEUE

    处理办法:打开工程属性,选择common properties,找到 link library dependencies,把 true 改成 false。

    这样可以看到linker的命令行中的 "E:workCaldebugCal.lib" 被去掉了。

    当然我不认为这个是真正的完美解决办法,有待微软修正。

    JIT, dynarec and binary translation

    HyperIris原创文章,谢绝转载

    JIT这个概念在计算机科学中,常见于两种上下文:对一般程序员来说,经常见到的是Just-in-time debugging,也就是当程序崩溃时,操作系统会根据预先的配置调用调试器来对故障进行诊断和处理;对于虚拟机来说,JIT意味着另一种技术-dynarec。

    dynarec是两个单词的缩写,即dynamic recompilation。顾名思义,动态重编译是一种编译技术,但是又和我们常见的编译器不一样,它是在代码的执行阶段动态的将一种代码的表示方式翻译成另一种,例如,常常是字节码翻译成本地机器码。

    我们每天使用的编译器,绝大多数都是将文本形式的源代码编译成机器可以执行的本地代码或者是虚拟机的字节码。这种编译过程又被称为ahead-in-time compile。这个编译过程往往是很慢的,在研发阶段进行的。因为这种编译过程往往涉及到复杂的词法、语法、语义分析和优化以及代码生成。由于现代编程语言大都规模庞大,所以这个编译过程通常需要较多的时间。

    在何时需要引入dynarec呢?对于传统编译语言来说,是不需要的。在基于虚拟机的语言中,常常纯粹的解释执行不能带来满意的执行速度,这个时候就需要dynarec来提高速度。也就是,虚拟机不再解释执行字节码而是根据某种规则,将字节码翻译成本地机器指令(native code),然后再执行。由于解释执行往往比本地代码慢一到两个数量级,所以使用dynarec能带来较大的性能改善。

    注意,无论如何,dynarec这个过程也是需要时间的,如果每次执行一条字节码都要执行dynarec然后再执行生成的本地代码,这显然是得不偿失的,dynarec必须要和一定的缓存策略配合才能真正起到作用。也就是说,我们将按照一定的策略,比如按照一定的字节码数,或者以函数为单位整体进行dynarec,然后将生成的本地代码缓存起来。这样,在最理想的情况下,dynarec只需要一次,当字节码下一次被执行的时候,虚拟机可以直接执行已经翻译的本地代码。

    如果生成字节码的高级语言编译器能够在生成的字节码流之中或者其他地方提供一些额外的信息,那么可以提高dynarec这个过程的性能。

    那么,binary translation又是什么呢?一般定义上它所处理的是从一种体系结构的机器指令到另一种体系结构的机器指令的翻译。注意输入输出都是传统意义上的机器指令。

    binary translation分为静态和动态两种,从理论上讲,我们可以编写一个“编译器”,把一种体系的可执行代码翻译成另一种体系,然后永久保存下来,比如说我们有一个windows x86平台的文字处理程序,可以用这种“编译器”将它编译成linux powerpc平台的可执行程序。但是一般来说这仅仅是理论上可行,实际实现难度极大,以后我将另行撰文说明。动态的binary translation是dynarec的一个特殊分支,它的输入不是字节码,而是本地代码。

    LBM Lid Driven Flow

    我把何雅玲老师的《格子Boltzmann方法的理论及应用》一书的附录D程序输入并且调试完毕,工程是VS2010的,有需要的可以到我的google code下载。

    工程地址是:https://code.google.com/p/openhyper/

    svn访问是:

    # Non-members may check out a read-only working copy anonymously over HTTP.
    svn checkout http://openhyper.googlecode.com/svn/trunk/ openhyper-read-only