SYCTF2306 babyThread题解
本题难点
1.这道题要给线程下断点,
2.而且还要跳过一个IsDebuggerPresent的反调试,
3.在进入线程后,输入阶段也有一个跳转需要修改。
静态反汇编,
一路跟踪跳转关系,
1 | int Main_func() |
猜测memcmp比较的:unk_41B018是程序的密文,Buf2存储根据输入生成的密文。
跟入前面的处理函数,
1 | unsigned __int64 __cdecl sub_411DA0(int a1_16, int flag, int a3_32, int cipher) |
可以看到前面根据一系列操作,生成了v7,然后用v7和flag异或得到了正确的cipher。
所以动态调试,
1.先搜索字符串,下好断点,一个是输入位置,一个是比较位置。
考点1
多线程下好断点。
考点2
2.一开始直接jmp F12E10
。
3.进入到F12A90。
F9运行,断到了线程函数执行前。
按几下F9,成功断到了关键函数处。
此时的线程状态:
考点3
跟入F11037,在F12860的跳转处,强制让其不跳转。
断在输入位置0x411BDE位置,
先随便输入32个1,根据对比函数里的栈地址,找到密文unk_41B018,
1 | 00F1B018 DE 1C 22 27 1D AE AD 65 AD EF 6E 41 4C 34 75 F1 ?"'enAL4u? |
整理如下:
1 | DE1C22271DAEAD65ADEF6E414C3475F1165050D448696D93361C863BBBD04C91 |
再次运行程序,先断到长度函数处。
修改这里的输入值,将前面的密文输入进去。
再断到加密函数位置处:
经过异或处理后,此时memcmp的Buf2里, 就是flag值。
flag
Run跟踪
- 跟踪一个call运行了哪些代码,有递归所有call和只对一层call的方法,操作方式分别是CTRL+F11和CTRL+F12。
给线程下断的另一种方式
Chap6-可执行文件的装载和进程
假设程序都是静态链接的,先从整体上把握程序的装载过程,下一章将把程序拆成模块来观察。
Linux下的分段故障Segmentation fault
与Windows的“进程因非法操作需要关闭,很多时候是因为进程访问了未经允许的地址。
1 | /**Linux操作系统 |
PAE
(Physical Address Extension),是针对32位CPU内存不足的一种修补。类似的,针对16位CPU,借助段偏移寻址能够达到1MB,每次读块只能读64KB,使用XMS(一种中断处理技术)读取大于1MB内存的地方。
OS采用36位地址线\内存地址时,程序使用的最大虚拟地址空间仍旧不会超过4GB,但是它程序的虚拟地址空间可以映射到的物理内存的范围扩大到64GB。
通过这种页或块映射在Win下访存的操作方式叫地址窗口映射扩展AWE(Address Windowing Extension);在Linu下通过mmap()系统调用来实现。
装载的方式
- overlay覆盖装入,适合内存受限场景比如木马或者嵌入式设备;
保证调用路径上的块都在内存;禁止跨树间调用。
编写程序时将程序分块,写一小段辅助代码,管理模块在内存的驻留和更替。
1 | /** |
如上图所示,A和B模块互不依赖,都被Main模块调用时,则可以采用Overlay的方式,使得需要的内存空间节省256Bytes。
- Paging页映射 和页替换算法 MMU的地址映射
从操作系统可执行文件的装载
进程的建立
- 1.创建虚拟地址空间(只创建映射函数需要的数据结构,并不实际创建空间)
- Linux i386中是分配一个页目录,不设置页映射关系,在后面程序发生页错误的时候才设置)
- 2.读取可执行文件头,建立虚拟空间与可执行文件的映射关系。(装载)
(Linux中)
3.令EIP=EOP of Executable File
内核堆栈和用户堆栈的切换、CPU运行权限切换
1 | /** |
Linux将进程虚拟空间中的一个段叫VMA(Virtual Mempry Area),包括
1 | start address;size;Attributes(RWE);State;Type; |
对于相同权限状态的节Section,把它们合并到一起作为一个段Segment映射。这样在进程虚拟空间中只有一个VMA而不是多个,可以减少页面内部碎片,节省内存空间。
页错误
程序执行时进程虚拟空间中发生Page Fault,控制权从进程转移到OS;
根据数据结构找到VMA,计算页面在磁盘文件中的偏移,
然后物理内存分配物理页,读取该磁盘块到物理页中,
将发生页错误的虚拟页与物理页之间建立映射关系。
控制权归还给进程,从发生页错误的位置继续执行。
ELF文件
从链接角度(Linking View)看,elf文件按照节section存储,描述它的结构叫做节表Section Headers;
从装载角度或者执行视图(Execution View)看,elf文件可以按照段segment划分,描述它的结构叫做程序头Program Header;
elf可执行文件和共享库文件处于装载的需要,比目标文件多一个程序头表(Program Header Table)。
1 |
|
对于LOAD类型的Segment,p_memsz一定>=p_filesz(bss被合并在数据类型的段里,存放那些未被初始化的数据)。
PE文件的装载
步骤
PE文件可以装载到任何内存位置。PE文件中选用RVA,因为RVA可以始终保持一致。
1.读取文件第一个页,获取到DOS头、PE头、段表。
2.选择装载地址。检查进程空间地址里,目标地址是否可用。若不可用,则另外选择装载地址。
3.段映射。使用段表将PE文件中的段,映射到进程内存空间地址。—>若装载地址≠目标地址,Rebasing。
4.装载所需要的dll文件—>解析PE文件中的导入符号——>
5.建立初始化栈和堆—>建立主线程并启动进程
数据结构
1 | coffHeader{ |
6.4 进程虚存空间分布
CPU体系架构回顾
指令集
MIPS 长度固定的指令,是RISC,处理器通过硬连线实现
x86(IA-32),长度变化,用CISC的指令集,CISC的处理器需要用微指令配合运行。但是当下Intel实际处理器的结构都已经变成了risc结构了,risc的结构实现流水线等特性比较容易。
cisc的寄存器数量较少,指令能够实现一些比较特殊的功能,例如Win32汇编语言对应的8086的一些寄存器: AX-DX,CS、DS、ES、SS。
GNU编译的elf文件对应到RISC的寄存器,会有很多通用寄存器。
虚拟模式需要使用一些特殊的寄存器、为了支持分页需要使用页表寄存器等,为了加速内存的访问需要使用TLB,加速数据和指令的访问而使用data cache和instruction cache等
jmp $是为了让程序停在这一行,防止程序跑飞(跑飞的程序危害很大!有可能把数据当代码或者把代码当数据!)
仿真工具
用synplify综合的电路,然后用debussy+modelsim仿真
IA-32的指令格式
前缀部分可选,分为四个group。
操作数前缀用于设置 锁总线和重复前缀 、段重写和分支预测、操作数宽度、地址宽度。
Opcode
x86的opcode最短是1个字节,最长是3个字节。
x86对于源操作数和目的操作数是暗含在opcode里面的。
1 | 88 11 BL(011) CL(001) 88 D9 MOV CL, BL MR |
二字节通用opcode是0fh+一字节的编码;但是二字节的SIMD opcode是三字节长度,即一个强制前缀+0fh+一字节的操作码。
同样的,三字节的通用opcode,是0fh+二字节的编码。SIMD opcode格式是强制前缀+0FH+二字节编码。
5.个别的SIMD指令不需要强制前缀来引导,比如addps(0FH+58H)
addps
指令中,ps
代表”packed single-precision floating-point” 。XMM (eXtended MultiMedia ) 寄存器是SSE(Streaming SIMD Extensions)指令集中的一种寄存器。
它可以同时存储4个单精度浮点数,每个数占用32位,总共128位。
对应的,MM寄存器是64位的。
addps
指令将分别对应位置上的单精度浮点数相加,并将结果存储回目标XMM寄存器中。
1 | 0F 58 01 addps xmm0,xmmword ptr[ecx] //从内存位置ecx处取128位单精度浮点数值加到xmm0 寄存器中的128位单精度浮点数上 |
Mod R/M
切分为3个位域,233.
mod:提供寻址模式,11=寄存器寻址 其余都是内存寻址
reg/opcode:
两种作用,第一种是提供寄存器寻址;另一种为某些opcode提供补充说明。
R/M:
结合MOD位域,提供内存/寄存器寻址。
R/M=100& MOD≠11 作为SIB的转义码 。
例如,
1 | 编码mov eax(000),ebx(011) |
SIB
同modr/m类似,SIB字节也是采用233切分成三个位域,名字分别叫Scale、Index、Base。SIB的名字也来自这三个位域名字的首字母缩写。
SIB字节由 R/M=100& MOD≠11 引导出来。
1.SIB确定的寻址方式是[base+Index* Scale +disp],
esp作为index时候,index自动被忽略,即此时scale因子视为0 , 寻址计算方法是[base+disp]
2.disp意思是后面尾随的若干个displacement字节。
1 | mov eax,[0x1]可以编码为以下形式: |
一些论文问题的总结
面向 APT 家族分析的攻击路径预测方法研究
面向 APT 家族分析的攻击路径预测方法研究,信息学报,陈伟翔 1, 任怡彤 1, 肖岩军 2, 侯 锐 3, 田志宏 1
IDS需要关联 APT 生命周期内的数 个阶段, 以此提高检测攻击能力。
1.要解决的问题
基因库—可靠数据获取方法
HMM——预测的攻击路径的可解释性
2.概念
攻击行为概述
“寻找入口点”、“C2 通信”、“权限提升”、“资 产发现”、“数据过滤”、
APT 攻击路径的可见状态集
注册表&服务
CS(create-service): 创建服务
MRV(modify-registry-value): 修 改注册表的行为;
DRKV(delete-registry-key-value): 删除注册表关键信息的行为;
内核
CKO(create-kernelobject): 创建内核对象的行为;
MMP(modify-memory-property): 修 改内存权限的行为;
进程
CP(create-process): 创建进程;
LL(load-library): 加载 库 ;
ESI(execshellcode-instr): 执行 shellcode 指令;
EHI(exec-heapinstr): 执行堆指令;
WTPM(write-to-process-memory): 写入进程内存;
ERI(exec-ret-instr): 执行 ret 指令;
EEI(exec-esp-instr): 执行 esp 指令;
CT(create-thread): 创建线程;
网络
SDQ(send-dnsquery): 发送 DNS 请求行为;
SNP(send-network-packet): 发送网络数据包;
LOP(listen-on-port): 监听端口。
CTS(connect-to-socket): 连接 socket;
文件
CF(create-process): 创 建文件的行为;
WTF(write-to-file): 写入文件的行为;
动态分析工具
Anubis、Norman、Joebox
基因库
基因(序列):(MD5,env,object_1,object_2,action_name)按照时间排序
object_1 与 object_2 分别代表软件执行动作的对象 和路径
同一个 APT 家族下恶意软件基因集合, 称为一个 APT 家族的基 因库。
钻石模型
一个 APT 攻击包含“攻击者”、 “受害者”、 “能力”和“基础设施”4 个核心元 素。
“能力”是攻击者使用的工具或技术, 从 探查到最终目的达成, “技术”存在于每一个攻击阶 段, 每一个恶意动作对应的技术细节可体现出相应 战略意图。
“基础设施”是攻击者维持权限控制的通 道或者载体。
3.方法
Smith waterman算法+
相似的公共子序列一定是得分最高的那一条。
比对序列为:A=GGTTGACTA,B=TGTTACGG
则两序列的长度分别为len(A) = n,Len(B)=m;
s(a,b):字符a和字符b的相似分数,这里设置为3,不相等则为-3;
W1 = 2 :一个空位罚分,这里设置为2(可根据需要设置)
H:匹配分数矩阵如下。
- 初始化算法分数矩阵H。A是列向量,B是行向量。使行i表示字符ai,列j表示字符bj;
- 回溯,从矩阵H中分数最大的一项开始:
若ai=bj,则回溯到左上角单元格
若ai≠bj,回溯到左上角、上边、左边中值最大的单元格,若有相同最大值的单元格,优先级按照左上角、上边、左边的顺序 - 根据回溯路径,写出匹配字符串:
若回溯到左上角单元格,将ai添加到匹配字串A‘,将bj添加到匹配字串B’;
若回溯往上走,此时A走了,B没走。将ai添加到匹配字串A’,将_添加到匹配字串B’;
若回溯到左边单元格,B走了,A没走。将_添加到匹配字串A’,将bj添加到匹配字串B’。 - 得到局部最优匹配序列,结束
数据处理
1.APT基因库要去重、去无效、去冗余(Smith-waterman局部序列对比算法去除相似基因,阈值为80%)。
->节省存储、简化算法复杂度
基因个体不重要,基因行为才重要。:(MD5,env,object_1,object_2,action_name),去除md5,env作关键词替换,object1只保留可执行文件。
2.公共基因:对所有APT基因库中的基因进行相似度检测,相似度90%以上的基因序列作为公共基因。
恶意软件的通用动作。
3.恶意行为基因库:各个家族APT基因库-公共基因,之后合并各个家族基因库。
4.可观测状态集:恶意软件行为可观测链:恶意软件基因序列,与恶意行为基因库进行比较。保留相似度 Thres_hold 在 90%以上的基因序列的基因尾的动作名称,不包含基因段属性信息,从而保证可观测状态可计算属性。
缺陷1:
恶意行为基因库进 行可观测链提取, 而这些基因序列来自于软件行为。
只能使用基因中 包含 C2 通信的部分进行关联和提取, 基因库的使用 率不足 30%,
5.隐藏状态集 资产发现 数据过滤 寻找入口 C2 通信
将测试集 中的样本放入各自的 APT 家族的 HMM 中计算下一 时刻攻击路径的隐藏状态和可观测状态的概率
展示结果时
将各个家族样本按照编号排序,把得分取对数,构成下面的得分图。
4.优势
1.精准定位到具体基因
能精准定位到具体基因, 能在一定程度上应对 恶意软件变种, 能在一定程度上应对 恶意软件变种
2.可同时对隐藏状态和可观测状态展开预测
3.在进行最终结果判定时, 将多个观测值作为判定结果
只要结果在观测值组里,就正确,可以提高实验表现,也能提供参考。
劣势
隐藏状态只有四个;
在 数据质量方面, 若一条观测链在多个模型下均展现 出极少或是单一的 APT 阶段, 就会严重影响 HMM 的构建。
在多个路径时,基因检测的识别准确率不如 HMM 检测法, 但是
利用三阶段特征选择和数据不平衡纠正增强勒索软件分类
SP-Analysis failed[Xctf g0Re]
xctf的一道Go语言逆向题,涉及AES加密和base64换表加密。
用IDA打开g0Re报告SP-Analysis failed
错误。
静态检测
将文件拷贝到kali后,静态检测一番。
1 | $ file g0Re |
报告如下错误:
1 | readelf: Warning: Section 49 has an out of range sh_link value of 2302008908 |
strings找到一些特征字符串1
2
3
4
5
6
7
8
9
10┌──(kali㉿kali)-[~/idawork]
└─$ strings -a -tx g0Re|grep -E '\s{4,30}'
ec OKXX$
13e FN{o
1e6 r mL
284 dRe6eYPgXygMd
295 fSCPpMP/C9DU36D2kliiYS5D9wKG/E_p
2b9 XkJb3WwcGMbUPd63r/bG8gVDS6EsZ5vv
81e18 $Info: This file is packed with the
81e67 $Id:
猜测是UPX加壳,但是UPX标志被抹去了。
UPX! 标志被抹除
unpack时会依次在三个地方检查UPX_MAGIC_LE32(即”UPX!”):
1 | 1、在倒数第36字节偏移处检查,如果特征值不符就会转入throwNotPacked()异常抛出函数,打印not packed by UPX; |
结合上面的规则2,可以很清晰地判断出出题人用0KXX
代替了UPX!
。
在对应的三个位置处换上UPX!魔数。
upx脱壳。
1 | ┌──(kali㉿kali)-[~/idawork] |
输入flag后的处理流程
1 | _int64 __usercall sub_48CD80@<rax>(__int64 i%16@<rdi>, __int64 key[i%16]@<rsi>, __int64 a3@<r14>, __int128 a4@<xmm15>) |
1.aes 2.base64 3.简单运算
aes加密 密钥获取
1 | 动态调试获取密钥:wvgitbygwbk2b46d |
启动ida服务端。
1 | #端口,密码,详细模式 |
Hex窗口与RSI同步。
base64换表加密
1 | strings -tx g0Re_upx|grep -E '[A-Z]{10,64}' |
解密脚本
1 | #-*-encoding:utf-8 |
反汇编算法
- 1.线性扫描反汇编 关键是确定代码起始位置,之后线性扫描整个代码段,并逐条反汇编每条指令。不会识别分支来解释控制流。
- 优点:覆盖程序的所有代码段;
- 缺点:假设代码段中全是代码,没法处理代码段中混入的数据。
- 2.递归下降反汇编。根据指令间的引用关系决定是否反汇编,在一个代码块内部,还是使用线性扫描算法。
- 优点:大部分情况下可以区分代码和数据。
- 缺点:无法处理间接代码路径。
IDA pro是递归下降反汇编器。
1 | //add、xor、mov、栈操作push |
反汇编出问题的情形
1.对于无条件分支指令,例如jmp eax这样的运行时才能确定跳转地址的指令,需要人工赋值。
2.call指令,在函数内部篡改了函数返回地址。
1 | foo proc near |
3.ret没有提供返回地址。
IDA遇到的函数返回语句,检测到其栈指针值不为0。
反汇编的困难
1.编译,和自然语言的翻译一样是一个多对多操作。除了C编译器,还有Go、Python、Delphi编译器、WinAPI库,反编译器非常依赖语言和库。
2.编译过程中会丢失命名和类型信息。反汇编后最多知道变量的位数,类型信息需要通过变量的用途确定。
一些静态工具用法
Linux:ldd; nm展示符号,C++filter、展示重定义。
OSX:otool; 处理MACH-O。
Windows:VS里的dumpbin /dependents ;objdump
ldd显示依赖库
1 | ┌──(kali㉿kali)-[~/idawork] |
dumpbin /dependents
1 | Dump of file baby.exe |
strings
-t 显示字符的文件偏移
-a 使得strings扫描整个文件,而非只有文件中可加载的、经初始化的部分
-e 搜素其他字符编码,如Unicode
几乎不会用到的工具:x86流式反汇编器,ndisasm和diStorm。
Windows汇编语言基础
x86汇编语法
- AT&T:GNU工具(Gas:GNU汇编器、gcc、gdb)
- 前缀:%reg,$立即数
- 操作数排序:mov 源操作数,目的操作数 add $0x4,%eax
- Intel :MASM(微软汇编器)、Turbo汇编器、NASM
- add eax,0x4
.model flat,平坦模式,代码段和数据段共用一个4G段。
局部变量、
局部变量是堆栈变量。作用域是单个子程序,从子程序返回到主程序时被释放。一种规范写法是
1 | LOCAL @buffer[10]:BYTE ;LOCAL 变量名1[重复数量]:类型,变量名2[重复数量]:类型 |
LOCAL
用于为堆栈变量预留空间。
局部变量:只能在过程中用,主程序里不能用;
堆栈变量:没有初始值,只在调用时分配。C语言中的函数值传递的本质。
一些常用类型整理
1 | byte db,word dw,DWORD dd,fword df,QWORD dq,tbyte dt 1,2,4,6,8,10 |
- TYPE返回变量大小
- LENGTHOF 返回变量元素个数
1 | ;,用于表示一个变量是否结束 |
- SIZE,SIZEOF返回所占字节数=LENGTHOF*TYPR
1.=和EQU伪指令
由等号或者EQU定义的符号常量不占用存储空间。
EQU类似于等号伪指令,但是EQU伪指令不许重复定义,而等号伪指令可以(同一个常量名可重复定义多次)。
1 | presskey EQU <"Output is:"> |
2.$ 当前地址运算符
3.LEA伪指令
OFFSET和ADDR编译时起作用,LEA是指令,在运行时起作用。
1 | MOV ESI,ADDR BVAL ;相当于 |
计算堆栈(程序执行是分配)变量的偏移地址时,只能用lea。
4.PTR 操作符
按照指定类型在内存中读写值。
1 | LIST DB 12H,34H,56H,78H; |
5.ALIGN和EVEN伪指令
CPU处理偶数地址比处理奇数地址要快。 可以在一个内存访问周期内获取该数据
ALIGN 对齐 (1,2,4) 按照n个字节的边界值对齐
EVEN 使下一地址从偶数地址开始
6.TYPEDEF和TYPEDEF PTR操作符
类型自命名和定义指针的类型,不占用存储空间
7.LABEL伪指令
别名,不占用存储空间
8.基数控制伪指令RAIDX
1 | .RADIX 16;默认该指令后面的数据都是16进制数,其他进制的数据需要额外说明 |
9.ORG伪指令
10.REPT伪指令
11.ASSUME伪指令
将程序的段与逻辑段绑定。
12.SHORT伪指令
循环
1 | .while(条件) |
14.结构体和共用体
15.宏定义
16.过程
模块化代码
PROC [NEAR] 或者[FAR],默认近调用,即在ODS实模式下的一个段里(<64KB(。
1 | 主程序中 CALL {PROC_noarg_name} |
17.ENTER和LEAVE
18.RET和RETN
19.IDA的反汇编
0day 安全
文件偏移地址 = 虚拟内存地址(VA)−装载基址(Image Base)−节偏移
= RVA -节偏移
对于 栈桢可能发生移位的情况
解决办法:
一个函数返回时,esp正好指向原来存储返回地址的下一位,我们将shellcode从ret_addr的后一个位置开始填充,并将ret_addr填充为一个进程中的”jmp esp”的指令的地址,这样函数返回后就会跳到esp指向的栈顶的位置开始执行shellcode。
缓冲区组成方式,现阶段已经讲了两种:
- 将shellcode放到缓冲区,然后覆盖返回地址到缓冲区的起始地址。这种适用于缓冲区较大的场合。
将shellcode放到函数返回地址以后,然后覆盖返回地址为”jmp esp”之类的指令,使得函数返回时跳转到shellcode处执行指令。这种适用于缓冲区较小的场合。
找到程序运行的线程环境块TEB。
- TEB的起始地址偏移0x30的地方指向进程环境块PEB。
- PEB的地址偏移0x0C的地方存放指向PEB_LDR_DATA结构体的指针,该指针指向一个存放着被进程装载的动态链接库的信息的结构体。
- PEB_LDR_DATA结构体偏移位置位0x1C的地方指向模块初始化链表的头指针InInitializationOrderModuleList。
- 4中的链表存放PE被载入时初始化的模块信息,第一个链表节点时ntdll.dll,第二个位kernel32.dll。
- kernel32.dll的节点偏移0x08是kernel32.dll在内存中载入的基址。
- kernel32.dll的基址加0xe3C是PE头的地址。
- PE头偏移0x78存放着指向函数导出表的指针。
- 安照下述方法寻址:
- 导出表偏移0x1C的指针指向存储导出函数偏移地址(RVA)的列表。
- 导出表偏移0x20指针指向存储导出函数名的列表。
- 根据函数名找到我们要的函数是导出表中的第几个,然后再地址列表中找到对应RVA。
- RVA加上动态链接库的基址即是VA,这个也是我们在shellcode中需要的地址。
这里shellcode的构造为了尽可能的短,所以需要给每个API名字用一个hash去代替。
MessageBoxA:0x1e380a6a
ExitProcess:0x4fd18963
LoadLibraryA:0x0c917432
1 | push 0x1e380a6a |
如何实现一款 shellcodeLoader
MITRE ATT&CK™ 矩阵
本文记录那些恶意软件使用的关键API。
NtProtectVirtualMemory ,创建PAGE_GUARD属性的内存页,通常用于反逆向和反调试 ; 将可读可写的内存属性改为可读可执行 。