精华内容
下载资源
问答
  • 有效反编译.so文件,安卓开发反编译.so文件,有效反编译.so文件
  • Win32汇编语言简明教程(适合反向工程和反病毒方向读者) 给你的快速指南!
  • 不过本文还是尽量能够给予读者一些初步的帮助,其中主要介绍一下x86-64寄存器以及用Linux下进行反向工程技巧。 x86-64寄存器 x64汇编使用16个通用64位寄存器,其中一些低位可以用作32,26,8位寄存器,寄存器命名...

    写本文其实有点心虚,逆向工程是个高深且需要长期积累的过程,在Title里面加入逆向工程关键字有点哗众取宠。不过本文还是尽量能够给予读者一些初步的帮助,其中主要介绍一下x86-64寄存器以及用Linux下进行反向工程技巧。

    x86-64寄存器

    64位寄存器
    x64汇编使用16个通用64位寄存器,其中一些低位可以用作32,26,8位寄存器,寄存器命名如上图所示。这些寄存器使用方法如下图:
    64位寄存器使用方法
    其中%rbx,%rbp, %r12-r15在函数调用过程中需要保存的,%rbp用来保存一个函数调用帧的栈基地址。
    %rsp主要用来保存一个函数调用帧的栈顶,动态函数调用栈开辟就是通过增长和缩减栈顶来实现的。
    %rdi, %rsi, %rdx, %rcx,%r8, %r9用来传递函数调用时前6个小于int长度或者指针类型的参数,其余多的参数或者传值的结构体主要通过栈来传递。
    %rax可用作函数返回,如果其返回值小于64位长度。否则将使用栈来返回结果,如返回值是结构体的时候。

    一个函数调用栈

    要进行逆向工程或者反向汇编,我们就需要了解函数调用栈。熟练的C/C++码农,应该对于进程堆栈的概念很熟悉,其中栈是辅助函数调用局部存储空间。每一次函数调用都将产生新的栈帧,每一个栈帧包含了当前堆栈上数据和通用寄存器上数据。请自行Google函数调用栈来获得更多知识点。本文将举一个自己认为好理解的例子来帮助理解调用栈以及后续的反汇编过程。
    假设我们写了如下的一段代码:

    #include <stdio.h>
    
    typedef struct _node{
            int a;
            char b;
    }NODE;
    
    int func(int a, int b, int c, char d, char e, char *f, NODE g, int h)
    {
            int res;
            res = a > b ? a : b;
    
            printf("%c %c %s\n", d, e, f);
            printf("%d %c\n", g.a, g.b);
            printf("%d\n", h);
    
            return res;
    }
    
    int main(int argc, char *argv[])
    {
            int res;
            char f[12] = "hello world";
            NODE g;
            g.a = 1;
            g.b = 'b';
    
            res = func(1, 2, 3, 'd', 'e', f, g, 4);
            return res;
    }
    

    那么调用func函数产生栈帧如下:
    栈帧

    Linux逆向一般套路

    • 获得符号表
    # nm test  //用nm命令获得符号表
    00000000006007d8 d _DYNAMIC
    0000000000600970 d _GLOBAL_OFFSET_TABLE_
    00000000004006b8 R _IO_stdin_used
                    w _Jv_RegisterClasses
    00000000006007b8 d __CTOR_END__
    00000000006007b0 d __CTOR_LIST__
    00000000006007c8 D __DTOR_END__
    00000000006007c0 d __DTOR_LIST__
    00000000004007a8 r __FRAME_END__
    00000000006007d0 d __JCR_END__
    00000000006007d0 d __JCR_LIST__
    000000000060099c A __bss_start
    0000000000600998 D __data_start
    0000000000400670 t __do_global_ctors_aux
    0000000000400430 t __do_global_dtors_aux
    00000000004006c0 R __dso_handle
                    w __gmon_start__
    00000000006007ac d __init_array_end
    00000000006007ac d __init_array_start
    00000000004005d0 T __libc_csu_fini
    00000000004005e0 T __libc_csu_init
                    U __libc_start_main@@GLIBC_2.2.5
    000000000060099c A _edata
    00000000006009b0 A _end
    00000000004006a8 T _fini
    0000000000400390 T _init
    00000000004003e0 T _start
    000000000040040c t call_gmon_start
    00000000006009a0 b completed.6364
    0000000000600998 W data_start
    00000000006009a8 b dtor_idx.6366
    00000000004004a0 t frame_dummy
    00000000004004d0 T func
    0000000000400560 T main
                    U printf@@GLIBC_2.2.5
    
    • 分析ELF format
    #readelf -a test //分析ELF帮助理解程序运行和加载等
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              EXEC (Executable file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x4003e0
      Start of program headers:          64 (bytes into file)
      Start of section headers:          2808 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         8
      Size of section headers:           64 (bytes)
      Number of section headers:         30
      Section header string table index: 27
    
    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 0]                   NULL             0000000000000000  00000000
           0000000000000000  0000000000000000           0     0     0
      [ 1] .interp           PROGBITS         0000000000400200  00000200
           000000000000001c  0000000000000000   A       0     0     1
      .....
    
    • objdump获得汇编代码
    #objdump -M x86-64 -D test  //使用objdump命令,以下我们分析主要片段的汇编语义
    00000000004004c4 <func>:
      4004c4:       55                      push   %rbp
      4004c5:       48 89 e5                mov    %rsp,%rbp                : 上一帧栈顶作为当前帧栈基
      4004c8:       53                      push   %rbx
      4004c9:       48 83 ec 38             sub    $0x38,%rsp               : 为函数局部变量申请0x38空间,把栈顶下移0x38位置
      4004cd:       89 7d dc                mov    %edi,-0x24(%rbp)         : 提取参数a存入栈基向下0x24位置
      4004d0:       89 75 d8                mov    %esi,-0x28(%rbp)         : 提取参数b存入栈基向下0x28位置
      4004d3:       89 55 d4                mov    %edx,-0x2c(%rbp)         : 提取参数c存入栈基向下0x2c位置
      4004d6:       89 c8                   mov    %ecx,%eax                : 提取参数d存入eax
      4004d8:       44 89 c2                mov    %r8d,%edx                : 提取参数e存入edx
      4004db:       4c 89 4d c0             mov    %r9,-0x40(%rbp)          : 提取参数f存入栈基向下0x40位置
      4004df:       88 45 d0                mov    %al,-0x30(%rbp)          : 从eax末8位提取d值存入栈基向下0x30位置
      4004e2:       88 55 cc                mov    %dl,-0x34(%rbp)          : edi从edx末8位提取e值存入栈基向下0x34位置从edx末8位提取e值存入栈基向下0x34位置
      4004e5:       8b 45 dc                mov    -0x24(%rbp),%eax         : 把参数a得值赋予eax
      4004e8:       39 45 d8                cmp    %eax,-0x28(%rbp)         : 比较a与b的值
      4004eb:       0f 4d 45 d8             cmovge -0x28(%rbp),%eax         : 如果b的值大于等于a则它的值存入eax
      4004ef:       89 45 ec                mov    %eax,-0x14(%rbp)         : res = a > b ? a : b;
      4004f2:       0f be 55 cc             movsbl -0x34(%rbp),%edx         : e值作为第三个参数存入edx
      4004f6:       0f be 5d d0             movsbl -0x30(%rbp),%ebx         : d值存入ebx
      4004fa:       b8 b8 06 40 00          mov    $0x4006b8,%eax           : printf format字符地址
      4004ff:       48 8b 4d c0             mov    -0x40(%rbp),%rcx         : f地址值作为第四个参数存入rcx
      400503:       89 de                   mov    %ebx,%esi                : d值作为第二个参数存入esi
      400505:       48 89 c7                mov    %rax,%rdi                : printf format首地址作为第一个参数存入rdi
      400508:       b8 00 00 00 00          mov    $0x0,%eax                : 请搜索“printf Variable Number of Arguments”
      40050d:       e8 a6 fe ff ff          callq  4003b8 <printf@plt>
    ....
      400547:       8b 45 ec                mov    -0x14(%rbp),%eax         : 返回值res写入eax
      40054a:       48 83 c4 38             add    $0x38,%rsp               : 销毁函数局部栈
      s0054e:       5b                      pop    %rbx
      40054f:       c9                      leaveq
      400550:       c3                      retq
      
    main:
    ....
    40058d:       c7 45 e0 01 00 00 00    movl   $0x1,-0x20(%rbp)         : g.a = 1;
    400594:       c6 45 e4 62             movb   $0x62,-0x1c(%rbp)        : g.b = 'b';
    400598:       48 8d 55 f0             lea    -0x10(%rbp),%rdx         : 取字符串f首地址存rdx
    40059c:       c7 44 24 08 04 00 00    movl   $0x4,0x8(%rsp)           : 第8参数h存到rsp向上偏移0x8;
    4005a4:       48 8b 45 e0             mov    -0x20(%rbp),%rax         : 结构体g值存入rax
    4005a8:       48 89 04 24             mov    %rax,(%rsp)              : 把rax值存到当前栈顶,暨扩展栈为下一个函数调用
    4005ac:       49 89 d1                mov    %rdx,%r9                 : 第6参数f指针地址from rdx存到r9
    4005af:       41 b8 65 00 00 00       mov    $0x65,%r8d               : 第5参数e存r8
    4005b5:       b9 64 00 00 00          mov    $0x64,%ecx               : 第4参数d存ecx
    4005ba:       ba 03 00 00 00          mov    $0x3,%edx                : 第3参数c存edx
    4005bf:       be 02 00 00 00          mov    $0x2,%esi                : 第2参数b存esi
    4005c4:       bf 01 00 00 00          mov    $0x1,%edi                : 第1参数a存edi
    ...
    
    • strace查看系统调用
    #strace ./test
    execve("./test", ["./test"], [/* 27 vars */]) = 0
    brk(0)                                  = 0x112d000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efce54fa000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY)      = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=47935, ...}) = 0
    mmap(NULL, 47935, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7efce54ee000
    close(3)                                = 0
    open("/lib64/libc.so.6", O_RDONLY)      = 3
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\356a]0\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0755, st_size=1930416, ...}) = 0
    mmap(0x305d600000, 3750184, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x305d600000
    mprotect(0x305d78a000, 2097152, PROT_NONE) = 0
    mmap(0x305d98a000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x305d98a000
    mmap(0x305d990000, 14632, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x305d990000
    close(3)                                = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efce54ed000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efce54ec000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efce54eb000
    arch_prctl(ARCH_SET_FS, 0x7efce54ec700) = 0
    mprotect(0x305d98a000, 16384, PROT_READ) = 0
    mprotect(0x305d420000, 4096, PROT_READ) = 0
    munmap(0x7efce54ee000, 47935)           = 0
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efce54f9000
    write(1, "d e hello world\n", 16d e hello world
    )       = 16
    write(1, "1 b\n", 41 b
    )                    = 4
    write(1, "4\n", 24
    )                      = 2
    exit_group(2)                           = ?
    +++ exited with 2 +++
    
    • gdb调试
    # gdb ./test
    (gdb) b func
    Breakpoint 1 at 0x4004c8
    (gdb) run
    (gdb) ni     //下一条汇编指令
    (gdb) info reg //查看寄存器值
    rax            0x1      1
    rbx            0x0      0
    rcx            0x64     100
    rdx            0x65     101
    rsi            0x2      2
    rdi            0x1      1
    rbp            0x7fffffffe3c0   0x7fffffffe3c0
    rsp            0x7fffffffe380   0x7fffffffe380
    r8             0x65     101
    r9             0x7fffffffe400   140737488348160
    r10            0x7fffffffe260   140737488347744
    r11            0x305d61ec20     207725128736
    r12            0x4003e0 4195296
    r13            0x7fffffffe4f0   140737488348400
    r14            0x0      0
    r15            0x0      0
    rip            0x4004e8 0x4004e8 <func+36>
    eflags         0x202    [ IF ]
    cs             0x33     51
    ss             0x2b     43
    ds             0x0      0
    es             0x0      0
    fs             0x0      0
    gs             0x0      0
    (gdb) disas //反汇编当前的代码
    Dump of assembler code for function func:
       0x00000000004004c4 <+0>:     push   %rbp
       0x00000000004004c5 <+1>:     mov    %rsp,%rbp
       0x00000000004004c8 <+4>:     push   %rbx
       0x00000000004004c9 <+5>:     sub    $0x38,%rsp
       0x00000000004004cd <+9>:     mov    %edi,-0x24(%rbp)
       0x00000000004004d0 <+12>:    mov    %esi,-0x28(%rbp)
    => 0x00000000004004d3 <+15>:    mov    %edx,-0x2c(%rbp)
       0x00000000004004d6 <+18>:    mov    %ecx,%eax
       0x00000000004004d8 <+20>:    mov    %r8d,%edx
       0x00000000004004db <+23>:    mov    %r9,-0x40(%rbp)
       0x00000000004004df <+27>:    mov    %al,-0x30(%rbp)
       0x00000000004004e2 <+30>:    mov    %dl,-0x34(%rbp)
       0x00000000004004e5 <+33>:    mov    -0x24(%rbp),%eax
       0x00000000004004e8 <+36>:    cmp    %eax,-0x28(%rbp)
       0x00000000004004eb <+39>:    cmovge -0x28(%rbp),%eax
       0x00000000004004ef <+43>:    mov    %eax,-0x14(%rbp)
       0x00000000004004f2 <+46>:    movsbl -0x34(%rbp),%edx
       0x00000000004004f6 <+50>:    movsbl -0x30(%rbp),%ebx
       0x00000000004004fa <+54>:    mov    $0x4006b8,%eax
       0x00000000004004ff <+59>:    mov    -0x40(%rbp),%rcx
       0x0000000000400503 <+63>:    mov    %ebx,%esi
       0x0000000000400505 <+65>:    mov    %rax,%rdi
       0x0000000000400508 <+68>:    mov    $0x0,%eax
       0x000000000040050d <+73>:    callq  0x4003b8 <printf@plt>
       0x0000000000400512 <+78>:    movzbl 0x14(%rbp),%eax
       0x0000000000400516 <+82>:    movsbl %al,%edx
       0x0000000000400519 <+85>:    mov    0x10(%rbp),%ecx
       0x000000000040051c <+88>:    mov    $0x4006c2,%eax
       0x0000000000400521 <+93>:    mov    %ecx,%esi
       0x0000000000400523 <+95>:    mov    %rax,%rdi
       0x0000000000400526 <+98>:    mov    $0x0,%eax
       0x000000000040052b <+103>:   callq  0x4003b8 <printf@plt>
       0x0000000000400530 <+108>:   mov    $0x4006c9,%eax
       0x0000000000400535 <+113>:   mov    0x18(%rbp),%edx
       0x0000000000400538 <+116>:   mov    %edx,%esi
       0x000000000040053a <+118>:   mov    %rax,%rdi
       0x000000000040053d <+121>:   mov    $0x0,%eax
       0x0000000000400542 <+126>:   callq  0x4003b8 <printf@plt>
       0x0000000000400547 <+131>:   mov    -0x14(%rbp),%eax
       0x000000000040054a <+134>:   add    $0x38,%rsp
       0x000000000040054e <+138>:   pop    %rbx
       0x000000000040054f <+139>:   leaveq
       0x0000000000400550 <+140>:   retq
    End of assembler dump.
    (gdb) x /32b 0x7fffffffe3c0 //打印具体地址分内存值
    0x7fffffffe3c0: 16      -28     -1      -1      -1      127     0       0
    0x7fffffffe3c8: -74     5       64      0       0       0       0       0
    0x7fffffffe3d0: 1       0       0       0       98      0       0       0
    0x7fffffffe3d8: 4       0       0       0       0       0       0       0
    

    最后,我其实想说反向工程并不是那么简单,因为在开启优化后汇编代码将变得扑朔迷离,没有很深功力是很难理解对应的语义,慢慢尝试,慢慢积累经验吧!

    参考链接:

    [1] x64_cheatsheet
    [2] 64位汇编参数传递
    [3] Stack frame layout on x86-64
    [4] ELF files on Linux: Understanding and Analysis

    展开全文
  • Tenet-反向工程师的跟踪资源管理器 概述 Tenet是一个插件,用于探索执行跟踪。 该插件的目标是提供更自然的人工控件,以针对给定的二进制文件浏览执行跟踪。 这项工作的基础源于对研究新的或创新的方法以检查和提取...
  • Lighthouse-反向工程师的代码覆盖率浏览器 概述 Lighthouse是和的功能强大的代码覆盖插件。 作为主要反汇编程序的扩展,当符号或源对于给定的二进制文件不可用时,此插件使人们能够以新颖的创新方式交互式地探索代码...
  • 2018年4月至5月,我主要研究了有关Windows上反向工程的“反向工程圣经”的备忘录和示例代码。 作者:坂本雅彦 目的: 为了解决CTF中存在的问题并考虑作弊方法,有必要了解以下技术和知识。 x86 / x64汇编程序,...
  • 用于对混淆的Flash文件进行反向工程的工具。 该反混淆器在反汇编的swf文件上运行,因此,如果尚未完成,请使用RABCDAsm反汇编该文件。 链接: : 首先,您必须运行命令abcexport和rabcdasm(有关RABCDAsm存储库...
  • radare2, unix像反向工程框架和命令行工具
  • 反转 逆向工程tuya固件的工具集 我不了解逆向工程,但是也许这可以帮助知道他们在做什么的人。 image1M.bin来自: : 工作汇编: 用法 使用以下命令克隆代码:git clone --recursive 运行start.sh
  • 尽量通过程序库或API来执行系统调用而不要直接使用汇编指令。这样做有两大优势:代码写起来更为一致,而且便于移植。 各平台以及同一个平台的不同版本可能会用不同的方式来提升系统调用的速度。总之,系统例程会以...

    《汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码都放在了GitHub上,包括x86和x86_64,并且给出了较多的网络参考资料链接。这里只摘记了MASM和NASM,测试代码仅支持Windows和Linux的x86_64。

    10. 与处理器及体系结构有关的高级话题

    10.2 处理器与系统的性能

    系统寄存器:包括下面几组:

    (1).控制寄存器(control register):用来表示处理器的模式以及与当前正在执行的任务有关的一些特征:cr0至cr4,其中cr1暂时保留不用;cr8寄存器,也叫作任务优先级寄存器(task priority register, TPR),用来安排外部中断的优先顺序,该寄存器只在64位模式下使用。

    (2).内存管理寄存器(memory-management register):用来指出保护模式的描述符表所在的位置:gdtr(Global Descriptor Table Register):全局描述符表寄存器;ldtr(Local Descriptor Table Register):局部描述符表寄存器;idtr(Interrupt Descriptor Table Register):中断描述符表寄存器;tr(Task Register):任务寄存器。

    每一个内存管理寄存器都有对应的加载及存储指令,上面的4个寄存器可以分别通过LGDT/SGDT、LLDT/SLDT、LIDT/SIDT及LRT/STR来加载或存储。其中的加载指令仅供系统软件使用,以便将描述符表的线性内存地址载入相应的寄存器。存储指令同样是供系统软件使用的,不过,应用软件在确有必要的时候也可以使用这些指令。

    (3).特定于机器的寄存器(Machine-specific register, MSR):用来控制并报告处理器的执行情况。其中某些寄存器与系统调用有明确的关系。

    段寄存器(segment register)是一种16位宽的寄存器,代码段(code segment)、数据段(data segment)、栈段(stack segment)以及附加段(extra segment)都有对应的段寄存器,分别称为cs、ds、ss、es,此外,还有两个通用的段寄存器叫做fs与gs,这两者是由Intel 80386所添加的。es是执行MOVS、CPMS及SCAS等字符串操作时默认使用的段寄存器,而fs与gs的用途则没有明确为硬件所定义。

    处理器模式:在整个x86时代,处理器模式都随着指令集与系统机能而逐渐演化。下表列出了x86/x86_64所支持的主要几种处理器模式,并写出了首个支持该模式的处理器,以及此模式能够在多大的内存空间中寻址。除了这几种模式外还有其它一些模式,例如系统管理模式以及虚拟8086模式。Intel的开发文档,经常把长模式称为IA-32e。

    Intel 8086是一款支持x86指令集的16位处理器,它运行在实模式下,这意味着软件能够直接访问物理内存空间、I/O通道以及周边硬件。之所以不限制访问是因为软件有时要直接与硬件沟通,例如在系统启动的时候。

    当前的x86及x86_64处理器为了保持向后兼容,确实会以实模式来发起boot流程,但是很快就会切换到保护模式或长模式。这些处理器大多通过UEFI来配置硬件,不过,也可以用老式的BIOS方式来配置。

    从Intel 80286开始,所有的x86处理器都支持保护模式,这也是32位x86系统的默认模式。Intel 80386完善了该模式并解决了存在的问题。当前的处理器所支持的保护模式正是从386演化而来的。

    AMD Opteron处理器支持长模式,从而可以使用x86_64指令集中的指令以及与之相关的寄存器。该模式必须搭配64位操作系统。长模式之下有几种小模式(sub-mode, 子模式),其中64位模式(64-bit Mode)适用于64位程序,而兼容模式(Compatibility Mode)则是一种增强版的保护模式,适用于32位及16位程序。在不使用长模式的情况下,x86_64处理器是可以支持实模式及保护模式的。

    内存模型:计算机系统所用的内存模型要根据可以使用的处理器模式以及操作系统的支持情况来决定。常见的两种模型叫做分段(segmented)模型及平面(flat)模型。分段内存模型会把地址空间分成不同的区段或者说段落,以供程序中的各个部分使用,例如数据可以放在一个区段中,代码可以放在另一个区段中。这种模型通过段基址加偏移量的方式来指定内存地址。平面内存模型会把整个内存空间展示成一块连续的区域以供开发者及程序来使用,这种模型也叫做线性(linear)模型。所有的内存地址都是直接以它在线性空间中的位置来确定的,而不通过段与段中的偏移量确定。大多数计算机架构都采用平面内存模型,而x86架构则在这两种模型之间摇摆。

    这两种模型的目标都是把逻辑地址(也就是抽象的引用)映射成物理地址(也就是实际的位置)。对于x86来说,16位系统使用分段内存模型,实模式下的32位系统也是如此。保护模式下实际使用的同样是分段模型,但看起来却跟平面模型一样。x86_64使用平面内存模型。

    x86在32位保护模式下的寻址方式与实模式既有相似之处又有所不同。首先,它用的还是分段内存模型,但由于引入了一些抽象机制,因此整个内存空间看上去好像是个线性的模型。其中一种抽象机制是描述符,另一种是分页。

    当前很多x86处理器都是从实模式开始运行的,但很快就会切换到保护模式。在启动过程中系统会设定描述符,例如中断描述符表(Interrupt Descriptor Table, IDT)、全局描述符表(Global Descriptor Table, GDT)、局部描述符表(Local Descriptor Table, LDT)、任务状态段(Task State Segment, TSS)描述符,以及一个代码段描述符(Code Segment Descriptor)与一个数据段描述符(Data Segment Descriptor)。其中,GDT与LDT是用来详细描述内存段的数据结构,可以指出内存段的基址、大小与访问权限。GDT针对的是全局及共享区段,而每个运行中的程序/进程则有其各自的LDT可以使用。TSS用来存放任务所涉及的寄存器值、I/O权限及栈指针,以便支持多任务处理(multitasking, 尤其是暂停及切换任务)。

    指令指针寄存器(ip/eip)指向处理器即将获取并加以执行的下一条指令所在的地址。然而x86的ip/eip中所保存的这个地址实际上是个针对代码段而言的偏移量,因此必须把该值与cs结合起来才能够确定处理器获取指令时所依据的实际地址,也就是说要根据cs:ip/cs:eip来判断。其中,cs是代码段的段选择器,ip/eip是以偏移量形式所表示的指令地址。

    分页技术给x86系统的地址转换过程又添加了一层抽象机制。当前的各种操作系统都在使用分页机制,这意味着cr0的PG位(也就是31号位)处于置位状态(其值是1)。启用分页之后,系统要通过cr3寄存器把线性虚拟地址(linear virtual address)转换成物理地址。cr3中有一个字段用来保存当前任务的首个页目录所在的物理地址。

    32位的x86采用多级分页机制。它的页面(page, 页)大小一般是4KB,在系统中以连续内存空间的形式出现。如果开启了(Page Size Extension, 页面大小扩展)也就是cr4寄存器的4号位,那么系统就支持2MB及4MB的页面。页表(page table)是含有1024个元素的数组,其中每个元素都占据32个二进制位,这意味着整个页表的大小是4KB,刚好可以放入内存页面中。页表里的每个元素都指向某个页面所在的物理地址。在页面与页表之上还有一种更大的结构叫做页目录(page directory),是个包含1024个元素的数组,而且其中的每个元素同样占据32个二进制位,这些元素均指向某个页表所在的物理地址。下图演示了x86分页机制的结构安排以及32位虚拟地址的解读方式:

    系统获取到某个虚拟地址之后,要按照下列方式来确定数据的物理位置。首先,根据cr3找到当前页目录的基址。然后把虚拟地址的高10位(也就是22至31号位)当成索引,在页目录中查找与该索引相对应的元素,并根据该元素的取值找到对应的页表所在的基址。接下来,把虚拟地址的中间10位(也就是12至21号位)当成索引,在页表中查找与该索引相对应的元素,并根据该元素的取值找到对应的内存页面所在的基址。最后,把虚拟地址的低12位(也就是0至11号位)当成索引,在页面中查找与该索引相对应的元素,这个元素正是虚拟地址所指代的那份数据。

    这种32位的虚拟地址使得4GB(也就是2^32个字节)的内存空间可以当成一个平面的模型来用,从而令程序数据能够以页面文件的形式出现在该空间中。系统会按照相应的方式解读虚拟地址,并结合cr3寄存器的内容确定其物理地址。页面文件保存在辅助存储器中,并按需移动到主存及缓存中。如果要找的数据不在主存或缓存里,那么就称为页缺失(page fault),此时系统会从辅助存储器中获取相应的页面。启用了分页机制之后,分段就显得不那么重要了,因为每个程序都有自己的空间可以使用,而且能够以虚拟地址的形式引用该空间中的每个位置。

    分页对于程序和用户来说是透明的。在启用了分页的平面模型中,虚拟地址当成线性地址空间中的线性地址来看待。这样的线性空间会分成不同的页面,这些页面会与物理内存中的帧(frame)对应起来(这些帧是用来保存页面的)。

    64位的x86_64架构在寻址的时候几乎完全抛开了分段机制。长模式下是不进行分段的,而那些段寄存器在这种模式下的用法也各有不同。cs、ds、ss与es寄存器,要么强行设为0x0h,要么当成零来对待(也就是说,无论取什么值,一律视为0),同时可寻址的空间也由2^32上升到2^64,这使得代码、数据与栈都可以出现在一个平面的地址空间中。x86_64的分页机制在原有的三层结构上又加了一层。此时的cr3不直接指向页目录,而是指向页目录指针表(page directory pointer table),系统会通过该表中的元素(或者说指针)来确定对应的页目录所在的位置。查询页表与查询页目录时所用的索引都从10位降成了9位,多出来的这两位正是用来查询指针表的。

    10.3 中断与系统调用

    软件中断:中断(interrupt)是针对处理器而发的信号,用来表示某种必须立刻予以关注的情况。它可以分为硬件中断与软件中断两大类。软件中断(software interrupt, 软中断)的一种形式是应用程序主动给处理器发信号,可以通过很多办法来实现,例如可以用INT指令明确地产生软件中断。还有一种形式叫做异常(exception)或陷阱(trap),这表示处理器在执行指令的过程中遇到了某种状况,而当前正在运行的应用程序却没有处理或不能处理该状况。

    硬件中断(hardware interrupt):也会给处理器发信号,但这个信号并不是由软件发出的,而是由某个与系统相通信的设备通过中断请求线(interrupt request line, IRQ line)所发。IRQ line使得处理器能够与硬件设备相通信。处理器用一些号码来表示通过IRQ line所发出的硬件中断。这些IRQ line是用可编程的中断控制器(Programmable interrupt controller, PIC)连接到处理器上的。

    通过INT进行系统调用(旧方法):明确地通过中断来执行任务是一种老式的做法,用于在16/32位x86系统中完成某些工作,他们都通过INT指令来执行系统调用。系统调用(system call)是向操作系统内核(operating system kernel)所发出的请求,旨在使系统完成某件事情。有些情况下,这种请求是通过API发出的,例如Windows系统就是这样。32位的Unix/Linux系统通过int 80h做系统调用,Microsoft Dos用的是int 21h,后续版本的Windows(例如Windows 2000、Windows XP)用的是int 2Eh。

    通过int 80h处理系统调用时,遵循如下流程:

    (1).执行INT指令;

    (2).处理器把运行级别从PL3(应用程序级别)切换到PL0(内核级别);

    (3).通过IDT(Interrupt Descriptor Table, 中断描述符表)中的相关描述符找到与INT 80h对应的例程;

    (4).把控制权交给内核,以便处理由系统调用号(system call number)所表示的请求;

    (5).处理该请求;

    (6).将优先级重新设为PL3;

    (7).把控制权返还给调用INT指令的程序。

    通过SYSENTER、SYSCALL及程序库/API进行系统调用(新方法):SYSENTER与SYSEXIT指令以及SYSCALL与SYSRET指令,并不像CALL与RET指令那样是调用与返回的关系,而是用来在PL0与PL3之间切换。

    系统调用的处理方式经常在变化,就连同一个操作系统的不同版本之间可能都会有所区别,甚至在操作系统与版本均相同的情况下,也还是有可能因为各种各样的细节而产生差异。

    INVOKE命令可以在32位版的MASM代码中使用,但是不能用在64位版的MASM代码中。如果要针对Microsoft x64编程,那么必须先把参数放入适当的位置,然后通过CALL指令来实现系统调用。

    尽量通过程序库或API来执行系统调用而不要直接使用汇编指令。这样做有两大优势:代码写起来更为一致,而且便于移植。

    各平台以及同一个平台的不同版本可能会用不同的方式来提升系统调用的速度。总之,系统例程会以很多种方式加以抽象,而且抽象方式也在逐渐变化。

    11. 其它架构

    11.2 CISC与RISC:指令集架构通常可以分为复杂与精简两种。复杂指令集(Complex Instruction Set Computing, CISC)架构中的指令长度不一。之所以称为复杂指令集,是因为某条指令可能会执行多项任务(例如既访问内存中的某个位置又执行算术运算)。反之,精简指令集(Reduced Instruction Set Computing, RISC)架构中的指令长度通常都一样,而且每条指令只执行一项任务(例如要么访问内存中的某个位置,要么将两个寄存器相加,但不会身兼二职)。

    这种把运算操作(例如加法ADD)与内存访问相分离的设计叫做加载/存储设计(load/save design)。RISC架构采用的就是这种设计,这意味着它必须用专门的指令将操作数载入内存,或是将其保存到内存中。例如要想执行ADD运算,首先必须从内存中获取操作数并将其放在寄存器中,然后把两个寄存器相加,最后将结果放回内存。这种设计实际上使得RISC系统的寄存器比CISC系统更多,由于它的静态RAM较多而动态RAM较少,因此可以节省空间及耗电量,这很适合用在移动设备与嵌入式设备中。

    CISC采用的不是加载/存储设计,因为它的操作指令还有可能要负责访问内存。仍然以ADD运算为例,CISC指令集中用来执行该运算的指令可以把某个寄存器中的操作数与内存中某个位置上的操作数直接相加。由于CISC处理器会频繁地访问动态内存,因此高速缓存就显得相当重要,只有把它做好才能提升速度与效率。在当前的架构设计中,RISC与CISC都通过大量的晶体管来实现解码器、逻辑单元及微码(microcode)机制。微码是一种依照具体的处理器而设定的逻辑,通常位于预留的高速内存中。它会把机器指令与系统操作翻译成一系列电路级的操作。微码可以视为一个经过编程的层,它定义了某条指令究竟应该怎样在硬件上执行。CISC与RISC架构的例子如下表所示:

    如果不考虑指令流水线这一因素,那么RISC与CISC系统之间的区别主要体现在指令长度与内存访问上。前者的指令长度是固定的而且大多数指令会在一个时钟周期内执行完毕,而后者的指令长度则不固定,由于指令较为复杂,因此许多指令要花费多个时钟周期才能执行完。这是理论上的差别,从实际角度来看,由于这两种处理器均会运用一些增强技术,因此它们在同一个时钟周期内其实都是可以执行多条指令的。

    精简指令集不一定意味着指令的数量少,而复杂指令集也不一定意味着指令的数量多。RISC系统倾向于减少专门针对某种数据类型的指令。CISC系统通常会用专门的指令来支持字符串等更为复杂的数据类型。

    11.3 更多架构:

    ARM:本来是Acorn RISC Machines的缩写,但现在已经成了ARM Holdings公司的名字。该公司研发了多种RISC处理器架构。

    ARM架构有三个不同的profile(配置):Application(A, 应用)、Realtime(R, 实时)及Microcontroller(M, 微控制器)。A系列的profile最为流行,32位与64位处理器中都有采用这种profile的产品,用在运行用户应用程序的设备上。R系列都是32位处理器,这种profile适合用在实时的,或者对安全要求很高的系统中,例如交通工具与医疗设备。M系列也是32位的,这种profile适用于单片机,通常出现在Arduino及NXP生产的单片机板上。

    x86架构的处理器有保护模式与长模式之分,与之类似,ARM处理器也可以运行在各种模式下。其中用户模式主要给非特权的处理任务使用,此外还有10种特权模式用来执行中断及异常等任务。大多数ARM处理器都采用”获取(fetch)--解码(decode)--执行(execute)”这样的传统三阶段流水线,不过,最近出现的几种版本,如ARM8与ARM9,装配有更为复杂的流水线。

    ARM处理器支持哪些寄存器,要看它的核心是32位还是64位,此外还要看是否支持Thumb指令集(一种采用16位编码的ARM指令集)以及它使用的是哪种VFP(Vector Floating-Point unit, 向量浮点运算单元)。下面给出用户模式中两套最为常见的组合:

    (1).AArch32:16个32位的寄存器r0~r15,其中r13用作栈指针(Stack Pointer, SP),r14用作链接寄存器(Link Register, LR),用来保存函数调用的返回地址,r15用作程序计数器(Program Counter, PC)。此外还有一个32位的寄存器叫做当前程序状态寄存器(Current Program Status Register, CPSR),其中包含一些与处理器管理有关的二进制位以及一些与算术有关的标志。许多32位ARM核心都配有这样一种VFP,它有16个或32个64位的浮点寄存器,可以存放单精度或双精度值。

    (2).AArch64:31个64位的通用寄存器x0~x30,它们的32位形式为w0~w30,其中w29用作帧指针(Frame Pointer, FP),x30用作LR。此外还有专门的SP寄存器、PC寄存器,以及32个128位的FPU寄存器。同时使用Advanced SIMD extension(也被称为NEON)指令集,支持8位、16位、32位及64位的整数,此外也支持单精度及双精度SIMD运算。

    尽管AArchh64的内存空间与地址都是64位,但指令依然是32位的。其中的操作数可以是32位也可以是64位。不同的ARM核心可能会采用不同的设计方案与增强技术。

    AVR:是一种高级的(增强的)8位RISC架构,它由Atmel所开发。AVR是把程序与数据分别存放在两块内存中 。它的所有内存空间都呈线性排布(内存空间是平面的)。

    RISC-V:是一种开源的RISC架构。它的指令集用的是可伸缩的地址空间(例如可以是32位、64位或128位),其指令编码的长度也同样可以伸缩。

    System-z/Architecture:IBM,采用CISC设计。

    11.4 量子架构(quantum architecture):量子系统所使用的能源却是光子能这样更为基本的能源,这些光子是离散量,也可以说是量子。于是,量子架构指的就是根据基本粒子来构建信息单位和通信单位的架构。量子架构的信息单位是量子位(qubit),它是量子数位。

    12. 硬件与电子元件

    12.2 电学基础:

    基本的物理量:包括电流(current)、电压(voltage)、电功率(power)以及电阻(resistance)。这些基本的物理量及其单位如下表所示:

    电流可以定义成电子通过某个点的速度。用安培(Ampere, Amp)做电流的单位可以更好地描述它的强度(intensity)。安培数越大,意味着通过导线的电子流动得越快。通过某个点的电流为1安培,意味着每秒钟大约有6.24*10^18个电子通过该点。

    电压指的是两个点之间的电位差,有的时候也叫做电势差(electrical potential difference),它的单位是伏特(Volt)。

    功率是电流所做的总功,单位是瓦特(Watt, 简称瓦)。电功率与电压及电流的关系:P=I*E。

    电阻是电流因为材料、环境或元件等因素而受到的阻碍。由于现实中的导体都不完美,因此,电能总会在传输过程中有所流失。不过,某些情况下可能会人为地提升电阻,以确保某个元件的电能可以达到必要的水平。电阻的单位是欧姆(Ohm, 简称欧)。欧姆定律:E=I*R。

    电通常以两种形式输送,这就是交流(alternating current, AC)和直流(direct current, DC)。墙上的插座输出的一般是交流电,它的电流方向总是不停地改变。大多数电子设备所用的电是直流电,它通常用正电压来表示。交流电之所以成为家庭用电的标准形式,是因为它的传输效率较高(三相交流电采用较小的中性导线,减轻了发电机与电动机的摩擦,并且也无须设计复杂的电动机)而成本较低。

    12.3 电子元件:

    供电设备:要把插座输出的120V交流电输送给需要在适当的直流电压下工作的各种计算机设备,就必须改变电流的形式。这项电流处理工作由两个电子元件负责,它们都位于计算机的电源供应器中,无论是台式机的电源盒还是笔记本电脑用的类似砖块的电源都具备这样两个元件。这就是变压器(transformer)与整流器(rectifier)。

    变压器:是一种通过电磁感应(electromagnetic induction)来提升或降低交流电压的元件,它把120V的电压降低到计算机内部元件能够承受的程度。电源供应器通常包含两个或三个变压器,用来降低各种信号的电压,使得计算机内部的元件都能够得到各自所要求的电力。

    电压降低后,需要用另一个元件将其从交流变为直流,这个元件就是整流器,它使得电流只朝着一个方向流动。除此之外它还有另一项职能,就是确保电流不会反向流动。

    逆变器的功能与变压器相反,它是把直流电转换成交流电。

    电阻器:由于导线与电容都不可能用完全理想的材料来制作,因此,任何电路中都有电阻。某些电路还需要进一步降低电流,因此会人为地增加电阻。这种减缓电子流动的元件叫做电阻器(resistor)。电阻器的电阻值各有不同。这些值以欧姆为单位,并通过电阻器上的色环(color band)来表示。典型的电阻器标有4个色环。与电池一样,电阻或其它元件之间也可以串联或并联。串联时电流不变,电压等于各电阻的电压之和,并联时电压不变,电流等于各电阻的电流之和。还有一种电阻器是可变电阻器,它也经常称为电位器,这种电阻器的电阻是可以改变的。

    二极管:控制电流方向以防其回流的元件就是二极管(diode)。二极管的原理是:它在其中一个方向上具备低电导(conductance)或零电导,而在另外一个方向上则具备高电导或无穷电导。也可以换一种说法,从电阻的角度来描述:它在其中一个方向上具备高电阻或无穷电阻,而在另外一个方向上则具备低电阻或零电阻。

    电容器(capacitor):是一种临时存储电荷的元件。电路中的电容器通常用来过滤电流。它的另一项功能是用来确保:无论电力来源是否稳定(例如是否会突降或激增),电路中的下一个元件总能获得适当的电压。由于很多电子元件都对功率波动较为敏感,因此需要由电容器来减缓这种波动,以延长这些元件的寿命。只要电容器中还有电荷(也就是电子还未耗尽),就能够起到稳定功率的作用。由于不同的电子元件需要的电压不一样,因此电容器也分为很多种。它们要按照存储电子的能力,也就是电容量(capacitance)来划分。

    晶体管(transistor):这是一种可以当作信号放大器或开关来用的元件,当代的电子设备都在这种基本元件的基础上构建而成。目前的计算机所使用的晶体管是由硅制作的。晶体管的三个极(terminal)叫做基极(base)、发射极(emitter)、集电极(collector)。给基极通电可以令发射极与集电极之间形成通路,从而使电流得以通过。放大程度与施加给基极的电压成正比。晶体管的发射极与集电极之间有时导电有时不导电,这种特性,就是半导体(semiconductor, semi-前缀表示”半”)一词的来源。

    12.4 集成电路:把前面的那些电子元件合起来可以构成电路,以完成某种任务。

    以下测试代码是通过CPUID指令来判断个人机上的处理器是否支持某些指令功能:

    int test_cpuid()
    {
    	// reference: https://github.com/brianrhall/Assembly/tree/master/Appendices/Appendix_G_Using%20CPUID/Program_G.1/x86_64
    #ifdef _MSC_VER
    	std::bitset<32> features1;		// standard features in EDX
    	std::bitset<32> features2;		// standard features in ECX
    	std::bitset<32> eFeatures1;		// extended features in EBX
    	std::bitset<32> eFeatures2;		// extended features in EDX
    
    	int cpu_info[4];			// for returns in EAX[0], EBX[1], ECX[2], EDX[3]
    
    	__cpuid(cpu_info, 1);			// functionID = 1 (EAX = 1)
    	features1 = cpu_info[3];		// standard features1 = EDX
    	features2 = cpu_info[2];		// standard features2 = ECX
    
    	__cpuidex(cpu_info, 7, 0);		// functionID = 1 (EAX = 7), subfunctionID = 0 (ECX = 0)
    	eFeatures1 = cpu_info[1];		// extended features1 = EBX
    
    	__cpuid(cpu_info, 0x80000001);		// functionID = 80000001h (EAX = 80000001h)
    	eFeatures2 = cpu_info[3];		// extended features2 = EDX
    #else
    	std::bitset<64> features1;     // standard features in RDX
    	std::bitset<64> features2;     // standard features in RCX
    	std::bitset<64> eFeatures1;    // extended features in RBX
    	std::bitset<64> eFeatures2;    // extended features in RDX
    
    	asm("movq $1, %%rax \n\t"                // RAX = 1
    		"cpuid \n\t"                         // execute CPUID
    		"movq %%rdx, %[features1] \n\t"
    		"movq %%rcx, %[features2] \n\t"
    		"movq $7, %%rax \n\t"                // RAX = 7
    		"xorq %%rcx, %%rcx \n\t"             // RCX = 0
    		"cpuid \n\t"
    		"movq %%rbx, %[eFeatures1] \n\t"
    		"movq $0x80000001, %%rax \n\t"       // RAX = 80000001h
    		"cpuid \n\t"
    		"movq %%rdx, %[eFeatures2] \n\t"
    		:[features1] "=m"(features1),        // outputs
    		[features2] "=m"(features2),
    		[eFeatures1] "=m"(eFeatures1),
    		[eFeatures2] "=m"(eFeatures2)
    		:                                    // inputs
    		: "rax", "%rbx", "%rcx", "%rdx"          // clobbered registers
    		);
    #endif
    
    	// binary output of features, output in reverse due to Little-Endian
    	fprintf(stdout, "===== CPUID bits (right-to-left) =====\n");
    #ifdef _MSC_VER
    	std::cout << features1 << " -- EDX bits, EAX=1\n"; // fprintf(stdout, "%s\n", features1.to_string<char, std::string::traits_type, std::string::allocator_type>().c_str());
    	std::cout << features2 << " -- ECX bits, EAX=1\n";
    	std::cout << eFeatures1 << " -- EBX bits, EAX=7 & ECX=0\n";
    	std::cout << eFeatures2 << " -- EDX bits, EAX=80000001h\n\n";
    #else
    	std::cout << features1 << " -- RDX bits, RAX=1\n";
    	std::cout << features2 << " -- RCX bits, RAX=1\n";
    	std::cout << eFeatures1 << " -- RBX bits, RAX=7 & RCX=0\n";
    	std::cout << eFeatures2 << " -- RDX bits, RAX=80000001h\n\n";
    #endif
    
    	auto support = [](int i) {
    		if (i == 1) return "Yes";
    		else return "No";
    	};
    
    	fprintf(stdout, "===== CPU Features =====\n");
    	fprintf(stdout, "x87 FPU: %s\n", support(features1[0])); // FPU
    	fprintf(stdout, "SYSENTER/SYSEXIT: %s\n", support(features1[11])); // SEP (SYSENTER/SYSEXIT)
    	fprintf(stdout, "MMX: %s\n", support(features1[23])); // MMX
    	fprintf(stdout, "SSE: %s\n", support(features1[25])); // SSE
    	fprintf(stdout, "SSE2: %s\n", support(features1[26])); // SSE2
    	fprintf(stdout, "SSE3: %s\n", support(features2[0])); // SSE3
    	fprintf(stdout, "SSSE3: %s\n", support(features2[9])); // SSSE3
    	fprintf(stdout, "FMA3: %s\n", support(features2[12])); // FMA3
    	fprintf(stdout, "SSE4.1: %s\n", support(features2[19])); // SSE4.1
    	fprintf(stdout, "SSE4.2: %s\n", support(features2[20])); // SSE4.2
    	fprintf(stdout, "AVX: %s\n", support(features2[28])); // AVX
    	fprintf(stdout, "F16C: %s\n", support(features2[29])); // F16C (half-precision)
    	fprintf(stdout, "RDRAND: %s\n", support(features2[30])); // RDRAND (random number generator)
    
    	fprintf(stdout, "===== Extended Features =====\n");
    	fprintf(stdout, "AVX2: %s\n", support(eFeatures1[5])); // AVX2
    	fprintf(stdout, "AVX512f: %s\n", support(eFeatures1[16])); // AVX512f
    	fprintf(stdout, "AVX512dq: %s\n", support(eFeatures1[17])); // AVX512dq
    	fprintf(stdout, "RDSEED: %s\n", support(eFeatures1[18])); // RDSEED
    	fprintf(stdout, "AVX512ifma: %s\n", support(eFeatures1[21])); // AVX512ifma
    
    	fprintf(stdout, "===== More Extended Features ======\n");
    	fprintf(stdout, "SYSCALL/SYSRET: %s\n", support(eFeatures2[11])); // SYSCALL/SYSRET
    
    	return 0;
    }

    在Windows上执行结果如下:

    在Linux上执行结果如下:

    GitHubhttps://github.com/fengbingchun/CUDA_Test

    展开全文
  • 阻止 Visual Basic .NET 或 C# 代码的反向工程- - 摘要 .NET 体系结构的一个优势是:利用它构建的程序集包含了可以使用 ILDASM 进行恢复的很多有用的信息以及中间语言反汇编程序。尽管存在一个负面影响,即...

    阻止 Visual Basic .NET 或 C# 代码的反向工程- -

                                          

    摘要 .NET 体系结构的一个优势是:利用它构建的程序集包含了可以使用 ILDASM 进行恢复的很多有用的信息以及中间语言反汇编程序。尽管存在一个负面影响,即可以访问您的二进制文件的人可以恢复与原始源代码非常接近的代码。此处作者提供的程序模糊处理作为一种阻止反向工程的方法。此外,他们还讨论了可用的不同类型的模糊处理技术,并说明了包含在 Visual Studio .NET 2003 中的新的模糊处理工具。

    *

    到目前为止,您可能已经熟悉了元数据丰富的 Microsoft®.NET Framework 体系结构为表格带来的所有好处,从减轻部署和版本控制的负担到由自说明的二进制文件所启用的丰富的 IDE 功能。您可能不知道所有元数据的简单可用性已经引入了一个问题,直到目前为止尚未被大多数开发人员所关注。为通用语言运行时 (CLR) 编写应用程序对于反向工程来说越来越简单了。在 .NET Framework 中不允许出现任何错误,它仅仅是现成的中间编译语言(Java 语言应用程序表现出相同的特性)。Java 和 .NET Framework 都使用嵌入到可执行代码内部的丰富元数据:在 Java 中为字节代码,在 .NET 中为 Microsoft 中间语言 (MSIL)。比二进制机器码高级很多的可执行文件包含有可以被轻松破译的信息。


    利用像 ILDASM(.NET Framework SDK 附带的 MSIL 反汇编程序)这样的工具或诸如 AnakrinoReflector for .NET 这样的反编译程序,任何人都可以轻松地研究您的程序集并利用反向工程将它们转换为可读的源代码。黑客可以搜索可利用的安全漏洞、盗取独特的思想,甚至破解程序。这足以让您犹豫一阵。


    但是,请不要担心。有一个解决方案,即模糊处理,它将帮助您防止反向工程。模糊处理是一种提供程序集中无缝重命名的符号以及阻止反编译程序的其他技巧的技术。正确应用该技术后,模糊处理可以极大地增加免遭反编译的保护,而使应用程序不受任何损害。模糊处理通常用于 Java 环境中,很多年来一直用于公司保护基于 Java 的产品的知识产权。


    很多第三方根据需要创建了适用于 .NET 代码的模糊处理程序。Microsoft 在与我们公司 PreEmptive Solutions 的合作中将 Dotfuscator Community Edition 包括在 Visual Studio®.NET 2003 中,我们的公司提供了多种模糊处理程序包。


    使用 Dotfuscator Community Edition,本文将教您有关模糊处理的所有知识(以及一些有关反编译的知识)、通常可用的模糊处理类型以及当使用模糊处理程序时您需要注意的一些问题。


    要说明反编译和模糊处理,我们将使用一个经典 Vexed 游戏的开放源代码实现。Vexed.NET 是由 Roey Ben-amotz 编写的,位于 http://vexeddotnet.benamotz.com。这是一个智力游戏,您的目标是将相似的块移动到一起,之后它们就会消失。下面就是来自 Vexed.NET 源代码的简单方法:

    public void undo() {
      if (numOfMoves>0) {
        numOfMoves--;
        if (_UserMoves.Length>=2)
            _UserMoves = _UserMoves.Substring(0, _UserMoves.Length02);
        this.loadBoard(this.moveHistory[numOfMmoves -
                          (numOfMoves/50) * 50]);
        this.drawBoard(this.gr);
      }
    }
    

    反汇编


    .NET Framework SDK 提供的名为 ILDASM 的反汇编实用工具,允许您将 .NET Framework 程序集反编译为 IL 程序集语言语句。为了启动 ILDASM,您必须确保已安装了 .NET Framework SDK,并在命令行上键入 ILDASM,后跟要进行反编译的程序名。在我们的例子中,将键入“ILDASM vexed.net.exe”。这将会启动 ILDASM UI,可以用来浏览任意基于 .NET Framework 的应用程序的结构。图 1 显示了反汇编的 undo 方法。

    反编译


    如果您现在认为只有那些真正了解 IL 汇编语言的少数人会查看和可以理解您的源代码,那么请记住反编译并非止步于此。我们可以使用反编译程序重新创建实际的源代码。这些实用工具可以将 .NET 程序集直接反编译回高级语言,例如 C#、Visual Basic®.NET 或 C++。让我们看一下由 Anakrino 反编译程序生成的 undo 方法:

    public void undo() {
      if (this.numOfMoves > 0) {
        this.numOfMoves = 
          this.numOfMoves - 1;
        if (this._UserMoves.Length >= 2)
          this._UserMoves = 
               this._UserMoves.Substring(0, this._UserMoves.Length - 2);
          this.loadBoard(
               this.moveHistory[this.numOfMoves -
                   this.numOfMoves / 50 * 50]);
          this.drawBoard(this.gr);
        }
    }
    

    正如您所看到的那样,结果几乎与原来的代码相同。稍后,我们将重新回到该示例以查看在使用模糊处理后的结果。

    深入模糊处理


    模糊处理是使用一套相关的技术完成的。它的目标就是隐藏程序的意图,而不更改其运行时行为。它并不是加密,但在 .NET 代码的上下文中,它可能会更好。您可以加密 .NET 程序集以使它们完全不可读。但是,这种方法会面临进退两难的局面 - 因为运行库必须执行未加密过的代码,而加密密钥必须保存在已加密的程序中。因此,可以创建一个自动的实用工具来恢复密钥、解密代码,然后将 IL 以其原始的格式写入磁盘。只要发生这种情况,程序就完全暴露于反编译。


    作一个比喻,加密就像将六道菜锁入了一个带锁的盒子中。只有希望进餐的人(在这个例子中是 CLR)才有钥匙,我们并不想让其他任何人知道他或她想吃什么东西。遗憾的是,就餐时食物将会被所有旁观者一览无余。模糊处理工作就像是将六道菜放入了搅拌器,然后将其放入塑料袋送给进餐者。当然,每个人都可以看到传递中的食物,但是除了幸运的豌豆或者某些牛肉色的糊状物之外,他们并不知道原来的菜到底是什么。进餐者仍然获得了想要的菜肴,并且菜肴仍然提供了与以前相同的营养价值(幸好,CLR 并不过分挑剔味道如何)。模糊处理程序的诀窍就是使观察者糊涂,同时仍然为 CLR 提供相同的产品。


    当然,模糊处理(或者加密)并不是百分之百的安全。即使编译的 C++ 也可以被反汇编。如果黑客足够有耐力,她可以重新生成您的代码。

    NFissues0311netcodeobfuscationgif02.gif

    模糊处理是应用到已编译的 .NET 程序集而不是应用到源代码的过程。模糊处理程序不会读取或更改您的源代码。图 2显示了模糊处理过程的流程。模糊处理程序的输出是另外一套程序集,在功能上与输入的程序集相同,只是改变了阻止反向工程的方式。现在,我们将考虑 Dotfuscator Community Edition 所使用的两个基本技术来达到该目标:重命名和删除不必要的元数据。

    重命名元数据


     

    模糊处理的第一道纺线就是使用无意义的名称重命名有意义的名称。正如您所知道的那样,经过慎重选择的名称有非常大的价值。它们有助于您的代码进行自我说明,并可以作为有价值的线索来揭示它们所表示项目的目的。CLR 并不介意名称的说明性如何,因此模糊处理程序可以毫无限制地对它们进行更该,通常将其更改为一个字符的名称,如“a”。


     

    很明显,对于模糊处理程序可以在某个特殊应用程序上执行重命名的数量,还会有许多限制。一般而言有三种通用的重命名方案。


     

    如果您的应用程序由一个或多个独立的程序集组成(即,不会有未模糊处理的代码依赖于任何程序集),那么模糊处理程序可以无限制地重命名程序集,无需顾及名称的视觉效果,只要它们的名称和引用在程序集集合中保持一致即可。Windows®Form 应用程序就是一个很好的示例。在相反的极端,如果您的应用程序设计为由未模糊处理的代码使用,那么模糊处理程序无法更改类型的名称或者对那些客户端可见的成员。该类型应用程序的示例是共享的类库、可重用的组件及其他。在某些位置中间是要插入到现有未模糊处理框架中的应用程序。在这个例子中,模糊处理程序可以重命名不被模糊处理特定环境(该模糊处理程序在其中运行)所访问的任意内容,无需考虑可视性。ASP.NET 应用程序是这种类型应用程序的典型示例。


     

    Dotfuscator Community Edition 使用名为“重载归纳”的专利重命名技术,该技术向重命名中添加了转换。在彻底的作用域分析后,方法标识符会最大限度的过载。不再使用新的名称替换每个旧的名称,重载归纳技术使用尽可能多的方法重命名为相同的名称,进而迷惑试图要理解反编译代码的任何人。


     

    此外,还有一个很好的附带作用,应用程序的大小会由于包含在程序集中字符串堆的减小而相应的减小。例如,如果您有一个长为 20 个字符的名称,将其重命名为“a”会节省 19 个字符。此外,连续地重用名称会由于保存字符串堆项而节省空间。将所有名称重命名为“a”意味着“a”仅存储一次,而且重命名为“a”的每个方法或字段都可以指向它。重载归纳增强了这个效果,因为最短的标识符可以连续地重复使用。通常情况下,一个重载归纳的项目可以将最多 35% 的方法重命名为“a”。


     

    要查看重命名对反编译代码的影响,请在重命名过程后研究一下 undo 方法:

    public void c() {
        if (this.p > 0) {
            this.p = this.p - 1;
            if (this.r.Length >= 2)
                this.r = this.r.Substring(0, this.r.Length - 2);
            this.a(this.q[this.p - this.p / 50 * 50]);
            this.a(this.e);
        }
    }
    

    您可以发现如果没有任何其他种类的模糊处理,这种方法已经非常不容易理解。

    删除不必要的元数据


     

    并不是在已编译的基于 .NET 的应用程序中的所有元数据都由运行库使用。其中一些由诸如设计程序、IDE 和调试程序的其他工具使用。例如,如果您在 C# 中的某个类型上定义了名为“Size”的属性,则编译器会忽略属性名为“Size”的元数据,并且将该名称与实现 get 和 set 操作(分别为“get_Size”和“set_Size”)的方法相关联。当您编写设置 Size 属性的代码时,编译器将始终生成一个对方法“set_Size”本身的调用,并且不会通过其名称引用该属性。实际上,此处的属性名称用于 IDE 和那些使用您的代码的开发人员;它不会由 CLR 访问。


     

    如果您的应用程序希望只由运行库使用而不被其他工具使用,则模糊处理程序删除这种类型的元数据是安全的。除了属性名外,事件名和方法参数名也属于这个类别。当 Dotfuscator Community Edition 认为将所有这些类型的元数据删除安全时,它就会这么做。

    其他技术


     

    Dotfuscator Community Edition 使用我们刚刚介绍的技术提供了很好的模糊处理,但是您应该注意到提供更强保护的其他模糊处理技术,可能会一起阻止反向工程。Dotfuscator Professional Edition 实现了很多其他的技术,包括控制流模糊处理、字符串加密、增量模糊处理和大小减小等。


     

    控制流是一种强大的模糊处理技术,其目标就是隐藏指令结果的目的,而不更改逻辑。更重要的是,它用于删除反编译程序要查找以忠实地重新生成高级源代码语句(例如,if-then-else 语句和循环)的线索。实际上,这种技术趋向于终止反编译程序。


     

    要查看该操作的效果,请在应用重命名和控制流模糊处理后,再次查看反编译的 undo 方法(请参阅图 3)。您可以看到,取代了原来的嵌套 if 语句,反编译程序生成了一个 if 语句、两个嵌套 while 循环,以及将它们全部联系在一起的一些 goto。引用了标签 i1,但它不是由反编译程序生成的(我们认为这是一个反编译程序的错误)。


     

    字符串加密是一项将简单的加密算法应用于嵌入到应用程序中的字符文字的技术。如前所述,在运行时执行的任何加密(或者尤其是解密)本质上就是不安全的。也就是说,聪明的黑客可以最终破解它,但对于应用程序代码中的字符串,这是值得的。让我们面对它,如果黑客要进入您的代码,他们不会盲目地开始搜索重命名的类型。他们可能确实要搜索“Invalid License Key”,这将他们正好引导到执行许可证处理所在的代码。在字符串中进行搜索出乎意料的简单;字符串加密建立起障碍,因为加密的版本只出现在编译后的代码中。


     

    增量模糊处理帮助在模糊处理的局面下发布修补程序以修复客户的问题。修复代码中的错误通常会创建或删除类、方法或字段。更改代码(例如添加或删除方法)可能会引起随后的模糊处理运行重命名事务稍微有所不同。在前面称为“a”的事务现在可能称为“b”。遗憾的是,重命名不同的方式和不同的内容都是一个谜。


     

    增量模糊处理可以解决这个问题。模糊处理程序创建一个映射文件来通知您它是如何执行重命名的。但是,这个相同的映射文件可以在随后的运行中用作模糊处理程序的输入,从而规定以前使用的重命名应该尽可能的再次使用。如果您发布您的产品,然后为一些类推出修补程序,则模糊处理程序可以以这种方式运行以模拟它以前的重命名架构。这样,您可以只为客户发行已修补的类。


     

    大小减小并不严格阻止反向工程,但它值得讨论,因为模糊处理程序几乎始终必须在输入程序集集合上执行依赖性分析。这样该模糊处理程序就能够很好地进行模糊处理,并且一些更好的模糊处理程序将使用其对应用程序的了解来删除程序不使用的代码。将不使用的代码删除看起来有些多余,但它确实能够实现一定目的 - 不是有些人并不使用自己编写的代码吗?好了,这就是我们所有的回答。还有,我们所使用的库和类都是由其他人针对重复使用而编写的。


     

    重复使用的代码表示可以处理许多情况的临时代码;但是,对于任意给定的应用程序,通常情况下您只使用这些情况中的一个或两个。高级的模糊处理程序可以确定这一点,并且可以剥离所有不使用的代码(同样,从编译后的程序集中,而不是从源代码中)。结果是输出精确地包含应用程序所需要的类型和方法 - 一点儿多余的都没有。较小应用程序的好处是节省计算资源和减少加载时间。对于在 .NET Compact Framework 或分布式应用程序上运行的应用程序来说,这可能尤其重要。

    使用 Dotfuscator Community Edition


     

    现在,让我们使用 Dotfuscator Community Edition 来模糊处理 Vexed 应用程序。Dotfuscator Community Edition 使用为特殊应用程序指定模糊处理设置的配置文件。它具有 GUI,可以帮助您简单地创建和维护配置文件以及运行模糊处理程序并检查输出。此外,Dotfuscator Community Edition 的命令行界面使您可以轻松地将模糊处理集成到自动化的构建过程中。您可以从 Visual Studio .NET 2003 的工具菜单中启动 GUI。


     

    要为进行模糊处理配置 Vexed,您需要在 Dotfuscator Community Edition GUI 中指定三个项目:输入程序集、映射文件位置和输出目录。在“Trigger”选项卡上指定输入程序集(模糊处理程序将这些称为“触发程序集”)。您可以在此处添加任意多个,但是对于 Vexed 应用程序,您只需添加一个。


     

    在“Rename | Options”选项卡上指定映射文件位置(请参阅图 4)。映射文件是基本的信息块,包含原始和未模糊处理名称之间明确的名称映射。在模糊处理应用程序后保存该文件是非常重要的,如果没有它,您将无法简单地排解模糊处理应用程序的问题。由于其重要性,在默认情况下,模糊处理程序将不会覆盖现有的映射文件,除非您明确选中“Overwrite Map file”复选框。


     

    最后,“Build”选项卡使您可以指定要放置模糊处理应用程序的目录。完成这个操作后,您就可以开始模糊处理应用程序了。您可以保存您的配置文件以便将来使用,然后按下“Build”选项卡上的“Build”按钮或使用工具栏上的“Play”按钮。在构建过程中,模糊处理程序会在 GUI 的输出窗格中显示进度信息。您可以通过在“Options”选项卡上选择“Quiet”或“Verbose”来控制在此处显示的信息量。


     

    构建完成后,您可以在“Output”选项卡上浏览结果,如图 5 所示。正如您所看到的那样,模糊处理程序显示应用程序的图形视图,类似于对象浏览器。新名称立即出现在视图中原始名称的下面。在该图表中,您可以看到名为“board”的类重命名为“h”,并且带有不同签名(init 和 ToImage)的两个方法都重命名为“a”。

    检查映射文件


     

    Dotfuscator 所产生的映射文件是 XML 格式的文件,除了已经提到的名称映射外,它还包含有关重命名过程效率的一些统计。图 6 总结了模糊处理 Vexed 应用程序后类型和方法的统计。


     

    映射文件也用于执行增量模糊处理。该过程使您可以从以前的运行中导入名称,这会通知模糊处理程序以它以前执行的相同方式进行重命名。如果您为已经模糊处理后的应用程序发布一个修补程序(或者新的插件),您可以使用与原始版本相同的名称集合来模糊处理更新。对于维护多个相互依赖应用程序的企业开发团队来说,这尤其有用。

    模糊处理程序缺陷


     

    在复杂的应用程序中,需要慎重对待模糊处理(尤其是重命名),它对正确的配置非常敏感。如果您不小心,模糊处理程序可能会破坏您的应用程序。在本部分中,我们将讨论当使用模糊处理程序时可能会出现的十分常见的问题。


     

    首先,当应用程序包括强命名的程序集时,您需要做更多的工作。强命名的程序集是经过数字签名的,允许运行库确定在签名后程序集是否已经更改。该签名是一个 SHA1 哈希,该哈希签有 RSA 公钥/私钥对的私钥。签名和公钥都嵌入到程序集的元数据中。由于模糊处理程序修改了程序集,在模糊处理后进行签名很重要。在开发过程中和进行模糊处理之前,应该延迟签名该程序集,然后在模糊处理后完成签名过程。有关延迟签名程序集的更多详细信息,请参阅 .NET Framework 文档,并且记住在测试延迟签名的程序集时关闭强名称验证。


     

    使用 Reflection API 和动态类加载也会使模糊处理过程复杂化。因为这些功能是动态的,所以它们趋向于取代由大多数模糊处理程序使用的静态分析技术。请考虑下面的 C# 代码片段,它按名称获得一个类型并动态对其进行实例化,将类型转换返回到接口:

    public MyInterface GetNewType() {
        Type type = Type.GetType( GetUserInputString(), true );
        object newInstance = Activator.CreateInstance( type );
        return newInstance as MyInterface;
    }
    


     

    类型的名称来自于另一个方法。GetUserInputString 可能会要求用户输入字符串,或者可能从数据库中检索字符串。任意一种方法中,类型名称都没有出现在用于恢复的静态分析的代码中,因此无法知道输入程序集中的哪个类型可能会以这种方式进行实例化。这个例子的解决方案就是防止实现 MyInterface 的所有潜在可加载的类型进行重命名(请注意,仍然可以执行方法和字段重命名)。这就是手动配置和了解进行模糊处理的应用程序扮演重要角色的原因所在。Dotfuscator Community Edition 为您提供了防止重命名选择类型、方法或字段的工具。您可以选择独立的名称;另外,您还可以使用正则表达式和其他标准(例如在作用域上的可视性)来编写排除规则。例如,您可以排除所有公共方法使其避免重命名。


     

    使用模糊处理程序的另一个问题发生在部署已模糊处理的应用程序之后,并试图支持它时。假设您的应用程序引发一个异常(这甚至会发生到我们最优秀的人身上),客户向您发送了如下所示的堆栈转储:

    System.Exception: A serious error has occurred
       at cv.a()
       at cv..ctor(Hashtable A_0)
       at ar.a(di A_0)
       at ae.a(String[] A_0)
    

    很明显,这比从未模糊处理程序中进行堆栈转储所得到的信息要少得多。好消息就是您可以使用在模糊处理过程中生成的映射文件来将堆栈跟踪解码回原始符号。坏消息就是有时在堆栈跟踪中没有足够的信息来明确地从映射文件中检索原始符号。例如,转储中忽略方法返回类型的通知。在利用增强的重载归纳重命名算法模糊处理的应用程序中,只有由返回类型区别的方法可能被重命名为相同的名称。因此,堆栈跟踪是模糊的。在大多数情况下,您可以使可能性变得足够小,这样就会以很高的确定性发现原始名称。为此,Dotfuscator Professional 提供了一个工具来自动将堆栈跟踪转换回原始的有问题的方法。

    小结


     

    您不需要让黑客为了不可告人的目的在您的应用程序中使用手头的 ILDASM 实用工具。您可以使用很好的模糊处理程序来保护您的代码。模糊处理产生了反向工程的障碍。在 Visual Studio .NET 2003 框中,Dotfuscator Community Edition 可以只通过几次单击就能够完成很好的模糊处理。

    相关文章,请参阅:
    Inside Microsoft .NET IL Assembler,作者是 Serge Lidin (Microsoft Press, 2002)
    Dotfuscator FAQ
    有关背景信息,请参阅:
    Ildasm.exeTutorial
    Anakrino
    http://vexeddotnet.benamotz.com
    http://www.preemptive.com

    Gabriel Torok 是 PreEmptive Solutions 的总裁。他是 JavaScript Primer PlusJava Primer Plus 的作者之一,这两本书都是由 Macmillan 出版的。Gabriel 已经在全世界很多软件开发研讨会上做了很多报告并提供了教程。

    Bill Leach 是 PreEmptive Solutions 的首席技术官。他是模糊处理程序产品线的架构师和技术领导。Bill 曾经做过软件开发书籍和文章的技术审查人员。

    摘自 MSDN Magazine2003 年 11 月刊。

    - 作者: Beautifully 2004年09月13日, 星期一 21:14

    转载于:https://www.cnblogs.com/walkingboy/archive/2005/03/28/127016.html

    展开全文
  • 这是《 Commodore Disk User》杂志第2卷第5期中发布的代码的反向工程汇编程序版本。 杂志的原始图像位于doc文件夹中。 享受! 建造 使用CBM Prg Studio将项目编译到bin \ FontFactory.prg。 使用VICE或其他CBM 64...
  • 高级语言源程序经过 编译 变成可执行文件,反编译就是逆过程。 但是通常不能把可执行文件变成高级语言源代码,只能... 计算机软件反向工程(Reversepengineering)也称为计算机软件还原工程,是指通过对他人..共1次编辑
  • 1、对裸金属固件的二进制文件逆向工程,与windows EXE和LINUX系统不一样。没有预定义的结构 2、需要先获取芯片组的数据单,以使用拆卸工具创建内存映射 功能完善的操作系统 1、具备比裸金属固件更完毕的功能(如...

    裸金属固件

    1、执行的任务较简单,直接与硬件连接,无需驱动或者内核程序(如温湿度传感器)
    2、简单的SDK可以实现,稍微复杂的SDK如FREERTOS等,有实时性的需求
    3、固件用C语言来写的,所以会受缓存溢出相关漏洞的较大影响,会成为整个物联网的安全突破口

    逆向工程

    1、对裸金属固件的二进制文件逆向工程,与windows EXE和LINUX系统不一样。没有预定义的结构
    2、需要先获取芯片组的数据单,以使用拆卸工具创建内存映射

    功能完善的操作系统

    1、具备比裸金属固件更完毕的功能(如路由器)
    2、常见的嵌入式系统有linux、wince、cisco IOS、android等,其中linux最流行
    3、此类固件通常都由三个部分组成:启动引导、内核、文件系统
    4、对设备的安全评估通常从应用程序的审计开始,远程应用服务是重点

    安全工具

    静态分析:通过分析代码寻找bug,没有源代码的情况下,就需要逆向工程二进制文件,读取汇编指令来理解程序的功能
    动态分析:运行应用,并观察其表现

    常用工具

    1、binwalk
    用于提取固件的二进制文件
    2、Qemu
    用于模拟不同架构(ARM、mips)下的二进制固件运行
    3、gdb-multiarch
    动态debug工具,可以暂停进程并查看内存
    4、Firmware ModKit
    用于给固件打补丁并重新封装

    展开全文
  • 逆向编程与反汇编的区别

    万次阅读 2015-09-16 20:37:00
    汇编:一般是只对编译器根据高级语言生成的本机二进制可直接在芯片上执行的机器码”解析“为人类可读的汇编形式的代码(实际上最最早期的计算机操作员具备直接阅读机器码和使用机器码编程的能力,不需要转换为汇编...
  • 汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码...
  • 工程化系列】逆向工程(反编译)

    千次阅读 2018-12-28 21:16:56
    DATE: 2018.12.28 文章目录1、参考2、反编译的概念3、常用的反编译软件 1、参考 ...2、反编译的概念 计算机软件反向工程(Reverse engineering)也称为计算机软件还原工程,是指通过对他人软件的
  • 当我对指令集进行反向工程时,它旨在进行快速迭代和实验。 因此,该代码是肮脏,丑陋且组织不良的。 也缺乏功能,总体上不建议您将其用于自己的自制项目,除非它们涉及逆向工程。 有关更多用户友好的实现方式,请...
  • 解决办法:得到你重新签名的组件1的public key token ,然后修改其它引用这个组件反向之后的il文件,把publick key token改成你重新签过名组件1的publick key token。这样问题就解决了。 最后所有问题全部解决,宣告...
  • 很重要的关键词在于逆向工程(reverse engineering) ,一旦知道了这个词之后,你再去搜索结果,出来的工具会靠谱很多。 在查阅了大量资料只好,我发现IDA-pro可以将exe文件转为汇编语言。 IDA-pro 安装...
  • 过去,计算机非常奇怪,现代反向工程工具做出的一些基本假设(尤其是“内存是字节的线性数组”)使它们对历史性计算机毫无用处。 PyReveng3尽可能通用地处理所有此类问题,以处理我遇到的任何奇怪的计算机体系结构...
  • Win32汇编语言简明教程(适合反向工程和反病毒方向读者)
  • windows逆向工程学习

    千次阅读 2018-11-20 14:16:29
    逆向工程(又称逆向技术),是一种产品设计技术再现过程,即对一项目标产品进行逆向分析及研究,以反汇编阅读源码 的方式去推断其数据结构、技术实现流程。特别是当手里没有合适的文档资料,而你又很需要实现某个...
  • 文章目录 一、 阅读然后回答2个问题 二、阅读 三、阅读 复现android逆向...注意,工程的报名必须和目标程序的包名一致,这样我们的注册机运行后得到的APK签名才会是一样的: 通过监听获取注册码: 四、 (2选1,原理一样...
  • 这里我使用反汇编调试器hopper dissassembler进行逆向分析,大家也可以用别的工具。 比如ida pro之类的。打开加载 我们脱壳之后的app程序,显示结果如下:   当然...
  • {软件工程}之逆向工程

    千次阅读 2013-03-27 23:37:26
    又称反向工程 是根据已有的东西和结果,通过分析来推导出具体的实现方法。比如你看到别人写的某个exe程序能够做出某种漂亮的动画效果,你通过反汇编(W32Dasm)、反编译和动态跟踪(OD)等方法,分析出其动画效果的实现...
  • 逆向工程

    2018-09-06 21:06:00
    文章的开始,先有必要辨别一个单词“crack”。...有时候他们更喜欢称自己为“逆向工程”学者――一个褒义词,而不是“破解者cracker”;当然,在软件作者眼里,“破解”肯定属于贬义。 逆向工程(Rev...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,607
精华内容 1,442
关键字:

反向工程汇编