汇编语言

不会汇编,强行刚软保,瑟瑟发抖稳得一匹

本文的编撰仅面向作者本人

前置知识

C语法

  1. 指针作差得到区间元素个数。指针的类型参与计算!
  2. 要动态改变函数体,需要在编译期赋予权限。
1
#pragma comment(linker, "/section:test,RWE")
  1. __asm int 3加入汇编断点(OD)。要在OD中启用StrongOD,禁用跳过int3;并设置测试选项

Windows exe结构

  1. 401000H这个内存地址对应的文件地址是1000H

工具

Quickview:更改EXE数据

  1. F5可以按地址定位
  2. 提供三种模式:16进制格式,汇编格式,纯文本格式;按F2切换到32位模式
  3. Alt+F9保存
  4. ins选择块(开始和结束各按一次),选择后按shift底部出现块工具

OD:单步跟踪EXE

  1. 可以用来追踪去壳代码
  2. 可以dump数据块到文件
  3. 可以设置硬件断点,当某储存位置发生更改时中断
  4. Alt+B管理断点,空格切换

IDA:静态分析EXE

  1. G定位
  2. 可通过加壳反抗静态分析。

方法

面对加壳

  1. 用OD动态追踪,尝试还原被加壳代码(等待去壳),再用QV还原EXE(用块复制的办法加快速度)

面对序列号

  1. 观察到现象:弹框并消失。可行办法:跟踪CreateWindow/ShowWindow/DestoryWindow(推荐)

  2. 用OD动态追踪,尝试中断DestoryWindow,观察堆栈顶端找到出栈后的程序运行位置(或直接跑retn)

  3. 对不当场判断的场合(没有明显现象) 怀疑有文件/注册表读写 FileMon + RegMon = ProcMon

    RegCreateKey()
    RegQueryValue()
    RegSetValue()

    CreateFile
    CreateFileEx
    ReadFile
    WriteFile

  4. 软件断点:当前断点指令的首字节改为0xCC(对应的汇编指令为int 3)。 可能被程序自检。

  5. 硬件断点:选中某条指令->断点->硬件执行。 检查已设置的断点:调试->硬件断点... 原理:CPU调试寄存器保存了断点地址和断点条件,每个周期都会检查条件。触发条件有executeread, write。 要设置读/写断点,选中变量的首字节->右键->断点->硬件写入->DWORD(对应的类型)。要设置DWORD断点对变量首字节有要求。读/写完成后到达断点。
    可以用printf("%p",...)知道要跟踪的变量的地址。

  6. 利用读/写硬件断点,当操作序列号时就能断住。Ctrl+B搜索已输入的序列号找到内存地址。跳跃几次看到rep movs(字符串拷贝),此时取消断点,回到用户代码,跳出几次,看到getText

  7. 如果进入系统代码,则应跳出到用户代码Alt+F9,再按Ctrl+F9跳出一些函数。

  8. CallWindowProcA的作用是由系统内核来回调用户事先写的消息处理函数

汇编规则

  1. cdq edx:eax, idiv, edx = edx:eax %n

逆向工程历史

  1. 微软开发debugcodeview, 宝蓝开发TurboDebug;

debug

  1. debug 编程:a(assemble) 地址+汇编语言,偏移地址固定从100开始 (OD中改指令相当于a,若下一条语句实效,会有安全措施,改90NULL)。
  2. u(unassemble) 反汇编,指定开始地址和末地址(l+长度亦可),反汇编对应地址内的机器语言。
  3. r(register) 查看寄存器 SF+PL-NG UP上DN下. r + 寄存器名字, 修改内存
  4. p (optional 地址) 单步前进,目标地址是执行完毕的
  5. g (optional =首地址 (optional) 末地址) (从csip)跳跃直到int 3(末地址是未执行的);
  6. d(dump) 查看内存 可以指定(d cs:100 120 || d cs:100 l20)
  7. e(edit) 修改内存 地址 + "string" || hex
  8. t(track in)

加密软件

  1. lock89, lockup, 作者:杨道沅, 硬盘指纹加密技术
  2. lock93, lock93NT, 周辉
  3. BitLock, 雷军

驻留内存程序

需要技术

  1. 驻留内存
  2. 中断

es:[bx] 0:32是一个中断向量的地址

0:0 ~ 0:3FF 是中断向量表,每个中断向量占用4字节。 例如int 00h的中断向量储存在0:0 ~ 0:3之间; int 01h的中断向量储存在0:4 ~ 0:7之间。

当cpu执行int 00h时:
pushf 保护当前标志位(flag)的状态
push cs 当前短地址
push 下条指令的偏移地址
cli
jmp 8756h:3412h

// cli disable interrupt (clear interrupt)
// sti enable. (set interrupt)

8756h:3412h 是中断服务程序的入口地址
……
iret; 中断返回

当cpu执行iret时:
pop ip;
pop cs;
popf; 从而实现恢复现场。

1
2
3
4
5
; 调换中断向量表
cli
mov word ptr es:[bx], offset int_8h
mov es:[bx+2], cs
sti
1
2
jmp dword ptr cs:[old_8h] ; 中断链接   old_8h定义在code段内是必要的
; iret 导致旧程序不再执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int_8h:    ; 中断服务
;;;;;;
push ds
push ax
mov ax, data
mov ds, ax
;;;;;;

inc [count]
cmp [count], 18

inc cs:[count] ; count db 0 at the end 定义在code段内


push ... ; 记忆所有寄存器 ax bx cb dx si di ds

push cs
pop ds
push cs
pop es
mov al,4
out 70,al
in al,71h // hour
call convert
mov word ptr current_time[?], ax
...



jmp dword ptr cs:[old_8h] ;返回旧程序

驻留

1
2
3
4
5
6
7
8
9
install:
mov ah, 9

; program segment prefix(PSP) 长度为100h,其中PSP:80h存放命令行参数,程序刚刚开始时 ds = es = psp,

...


in al, 60h

gamebuster

1
2
3
4
5
6
7
8
mount c:... d:\...
gb
pc
[ctrl][ctrl] 分析"/十进制" "十六进制"
[alt + 1]
[shift + 1] 锁


dosboxdeb

dos mcbs 获得当前程序的PSP (memory control block)
u 01dd:1785 // 查看反汇编

单步中断

1
2
3
4
5
6
7
8
9
pushf
pop ax
or ax, 0100h
push ax
popf ; 修改 TF = 1

nop; ; 当TF==1,执行一条指令后调用 int 1h


某种病毒

修改int 21h中断向量为xxxx:yyyy,原向量保存在内部变量old_21h中。新指令为cmp ah 4Bh,判断是否要执行程度。

1
2
3
4
5
6
7
8
9
je infect_exe
jmp dword ptr cs:[old_21h]

infect_exe:
mov ah, 3dh; AH==3DH 表示打开文件
mov al, 2; 读写方式
pushf
call dword ptr cs:[old_2h]; call 隐含push段地址、偏移地址 ..?

原始的int 21h中断向量的段地址<=70h

假装调用一个无用的int 21h功能,如获取dos版本号

1
2
3
4
mov ah, 30h
pushf
置位TF ; 不能手动int 21h,因为系统将flag置零
call dword ptr [current_21h]

EXE文件格式

+00 4D, 5A; EXE标志“MZ”
+02 50, 01; 最后一个扇区的字节数为150h (例外:如果是00 00则最后一个扇区是满载)
+04 02, 00; EXE占用的扇区数量(200字节/一个扇区)
+06 02, 00; 重定位项数
+08 02, 00; 文件头的节长度=20h,所以字节长度=200h (20h x 10h)
+0A 00, 00; 至少内存 DOS是单任务的,所以内存限制没有太大意义
+0C FF, FF; 至多内存
+0E 05, 00; SS与程序首段地址的距离 (程序首段地址在运行时确定, = psp+10h)
+10 00, 01; SP = 100h

+12 E8, 20; EXE文件头的校验值
+14 28, 00; IP=0028h ; 注意此处先IP后CS CS:IP第一句指令
+16 02, 00; CS与程序首段地址的距离
+18 1E, 00; 重定位表的偏移地址

; 下面是重定位表。每个重定位项4个字节。每两个字节为一个信息。
+1E 01, 00; 重定位的偏移地址=0001h
+20 02, 00; Δ=0002h,
; 首段地址+Δ=重定位的段地址
;
+22 0D, 00; 重定位的偏移地址=000Dh
+24 02, 00; Δ=0002h,
; 首段地址+Δ=重定位的段地址

DOS运行程序时,文件头不载入内存,所以内存头可认为是文件头长度

200h+2:1 = 0:200h + 0:21 = 0:211

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mov ah, 2
mov dl, 'A'
int 21h
mov ah, 1
int 21h


mov ax, ds
add ax, 10
add ax, 2
push ax
mov ax, 28h
push ax
retf ; pop ip, pop cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
code segment
assume cd:code
shell:
call next
next:
pop ax ; ax = ip = next的地址
sub ax, (offset next - offset shell)
mov bx, ax

mov cs:[old_ds+bx], ds
mov cs:[old_es+bx], es
mov bp, ds ; psp
add bp, 10h; 首段段地址
mov cx, [reloc_count+bx]
push cs
pop ds
mov si, offset reloc_table
add si, bx

reloc_next_item:
mov ax, [si] ;lodsw ; ax = ds:[si] ; offset
mov dx, [si+2] ; delta segment
add dx, bp
mov es, dx

add es:[di], bp
add si, 4
dec cx
jnz reloc_next_item

mov ax, [csip+bx]
mov dx, [csip+bx+2]
add dx, bp
push dx
push ax
mov ds, cs:[bx+old_ds]
mov es, cs:[bx+old_es]
retf



old_ds dw 0
old_es dw 0
csip dw 0, 0
reloc_count dw 0
reloc_table db label byte ; 定义为db但不赋初值


code ends
end shell

注意加密堆栈后引发的解密对新数据的影响的问题。

保存ss和sp,之后修改它为一段预留空间

保护模式

32位系统下

保护模式 实模式
最大段长度 4G 64K
越界访问 运行时错误 允许
物理寻址 查gdt表 基址*10h+10h+偏移

查gdt表

ds=10h,现通过查gdt表获得其物理地址。gdt表储存描述符,每个占8字节。表的首地址存在于gdtr寄存器。

gdt+10h xx xx xx xx xx xx xx xx

idt中断描述符表

首地址存在于idtr中。每项有8字节。基址2字节,偏移4字节,剩余2字节记录权限。

1
2
3
4
5
6
7
8
9
10
lgdt fword ptr gdt ; 把gdt表的首地址和长度载入到gdtr寄存器

cli
smsw ax
and ax, 1Fh
or al, 1
lmsw ax; enable PE bit of MSW or CR0
db 0EAh ; JMP FAR PTR
dw 00h
dw pseg_