笔记

frida hook主动调用未导出的so函数

thumb32位的ida函数偏移需要加1, 64位下不需要


010检索DES算法标准的sbox因子


ida识别加密插件

findcrypt2.zip ida官方插件

findcrypt-yara

polymorf/findcrypt-yara: IDA pro plugin to find crypto constants (and more) (github.com)

js逆向

JavaScript Obfuscator Tool

AST解析 AST explorer

shift-parser jsoverson/shift-refactor: A suite of utilities to query and modify JavaScript source (github.com)

Shift-query demo site (jsoverson.github.io)

vscode Prettier - Code formatter


.text:000082E0 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000082E0                 EXPORT main
.text:000082E0 main                                    ; CODE XREF: j_main↓j
.text:000082E0
.text:000082E0 var_C           = -0xC
.text:000082E0 var_8           = -8
.text:000082E0
.text:000082E0                 PUSH    {R11,LR} ; 将R11,LR寄存器压入堆栈
.text:000082E4                 ADD     R11, SP, #4 ; 初始化R11寄存器,设置栈帧,用于访问局部变量
.text:000082E8                 SUB     SP, SP, #8 ; 开辟栈空间
.text:000082EC                 STR     R0, [R11,#var_8]  保存第一个参数
.text:000082F0                 STR     R1, [R11,#var_C] ; 保存第二个参数
.text:000082F4                 LDR     R3, =(aHelloArm - 0x8300) ;取(aHelloArm - 0x8300)的内容保存到R3寄存器中,即"Hello ARM!"的偏移地址
.text:000082F8                 ADD     R3, PC, R3 ; 算R3("Hello ARM!")字符串的内存地址
.text:000082FC                 MOV     R0, R3 ; s ; 置参数一
.text:00008300                 BL      puts ; 用puts函数
.text:00008304                 MOV     R3, #0 ; 置R3寄存器的值为0
.text:00008308                 MOV     R0, R3 ; 程序返回结果为0
.text:0000830C                 SUB     SP, R11, #4 ; 恢复SP寄存器的值
.text:00008310                 POP     {R11,PC} ; 恢复R11寄存器,并将LR寄存器的值赋给PC寄存器
.text:00008310 ; End of function main

指令简介

SP为堆栈指针寄存器,"SUB SP, SP, #8"将SP寄存器的值减去8后重新赋给SP寄存器,作用是在堆栈上分配8个字节的空间,也就是栈变量var_C与var_8的空间。

STMFD(push)与LDMFD(pop)是堆栈寻址指令,STMFD(push)指令用于把寄存器的值压入堆栈LDMFD(pop)指令用于从堆栈中恢复寄存器的值,在本实例(ida7.6为push,pop)中是为了保护原始寄存器的值(因为这些寄存器在下面可能会被使用,它们的值在使用前需要保存下来,在程序返回的时候恢复)

ADD为加法指令,"ADD R11,SP, #4"就是将SP寄存器的值加4后赋给R11寄存器。SUB为减法指令,"SUB SP, R11, #4"功能恰恰相反。"ADD R3, PC, R3"为计算R3("Hello ARM!")字符串的内存地址

STR与LDR是存储器访问指令。存储器指的就是内存地址,通常也可以称为内存单元或存储单元,存储器的访问包含从存储器中读数据与写入数据到存储器中,ARM指令中将存储器使用一对中括号“[]”表示,STR是写存储器指令,"STR R0, [R11,#var_8]"就是指将R0寄存器的值保存到栈变量var_8中,"STR R1, [R11,#var_C]"则将R1寄存器的值保存到栈变量var_C中。"LDR R3, =(aHelloArm - 0x8300)"为取(aHelloArm - 0x8300)的内容保存到R3寄存器中,即"Hello ARM!"的偏移地址。

MOV为数据传送指令。它用于寄存器间的数据传送,"MOV R0, R3"表示把R3寄存器的值赋给R0寄存器。

BL为带链接的跳转指令。完成类似其它编程语言中子程序调用的功能,"BL puts"就是调用puts函数。puts为标准输入输出函数中printf的实现

寄存器

ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,ARM处理器支持七种运行模式。

1.用户模式(usr):ARM处理器正常的程序执行状态(大多数程序)。
2.快速中断模式(fiq):用于高速数据传输或通道处理。
3.外部中断模式(irq):用于通用的中断处理。
4.管理模式(svc):操作系统使用的保护模式。
5.数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
6.系统模式(sys):运行具有特权的操作系统任务。
7.未定义指令中止模式(und):当未定义的指令执行时进入该模式。

在用户模式下,处理器可以访问的寄存器为不分组寄存器R0~R7、分组寄存器R8~R14、程序计数器R15(PC)以及当前程序状态寄存器CPSR。

x86返回值是eax, arm32返回值是R0, Arm64返回值是X0和W0; x86中EBP相当于ARM中的FP(R11)寄存器([optional] frame pointer); 连接寄存器R14(thumb是LR, arm64是X30) 作用, 一是(返回地址)使用BL或BLX时保存子程序返回地址放入R14, 二是R14用来保存异常返回地址, 将R14入栈可以处理嵌套中断; 程序计数器R15(PC)

r0-r3 用作传入函数参数,传出函数返回值。寄存器R0~R3是函数的前4个参数寄存器,超过4个参数的函数调用使用堆栈来传递。在子程序调用之间,可以将 r0-r3 用于任何用途。 被调用函数在返回之前不必恢复 r0-r3。如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。

r4-r11 被用来存放函数的局部变量。为通用寄存器,每一次函数调用都可能改变它们中一个或多个的值,因此,在使用它们前需要先保存它们的值,以便不影响到函数调用时的中间结果。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。

r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。

r13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。

r14 是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复

r15 是程序计数器 PC。保留下一条CPU即将执行的指令, 它不能用于任何其它用途。

ida转换为指针快捷键, 0x3E80转换为常量快捷键

ARM处理器有两种工作状态:

ARM状态与Thumb状态。处理器可以在两种状态之间随意切换。当处理器处于ARM状态时,会执行32位字对齐的ARM指令,当处于Thumb状态时,执行的是16位对齐的Thumb指令。Thumb状态下对寄存器的命名与ARM有部分差异

Thumb状态下的R0~R7与ARM状态下的R0~R7相同。
Thumb状态下的CPSR与ARM状态下的CPSR相同。
Thumb状态下的FP对应于ARM状态下的R11。
Thumb状态下的IP对应于ARM状态下的R12。
Thumb状态下的SP对应于ARM状态下的R13(堆栈指针)。
Thumb状态下的LR对应于ARM状态下的R14(连接寄存器)。
Thumb状态下的PC对应于ARM状态下R15(程序计数器)。

Android平台GAS(GNU Assembler,GNU汇编器)在线手册:http://sourceware.org/binutils/docs/as/index.html

定义的三个段:

".section .rodata"定义只读数据段,属性采用默认。
".text"定义了代码段,没有使用.section关键字。
".section .note.GNU-stack," ",%progbits"定义.note.GNU-stack段,它的作用是禁止生成可执行堆栈,用来保护代码安全,可执行堆栈常常被用来引发堆栈溢出之类的漏洞

ARM处理器寻址方式

  1. 立即寻址 MOV R0, #1234 执行后R=1234

  2. 寄存器寻址 MOV R0, R1 执行后R0=R1

  3. 寄存器移位寻址 MOV R0, R1, LSL #2 R1 << 2赋值给R0, 执行后R0=R1*4

    • LSL:逻辑左移; LSR:逻辑右移; ASR:算术右移; ROR:循环右移; RRX:带扩展的循环右移
  4. 寄存器间接寻址 LDR R0, [R1] 将R1寄存器的值作为地址,取出此地址中的值赋给R0寄存器

  5. 基址寻址 LDR R0, [R1, #-4] 将R1寄存器的值减4作为地址,取出此地址中的值赋给R0寄存器

  6. 多寄存器寻址 LDMIA R0, [R1, R2, R3, R4] 执行后 R1=[R0],R2=[R0+#4],R3=[R0+#8],R4=[R0+#12]。
    LDM是数据加载指令,指令的后缀IA表示每次执行完加载操作后R0寄存器的值自增1个字; 字表示的是一个32位的数值

  7. 堆栈寻址 PUSH {R1-R7, LR} 将R1-R7,LR入栈, 多用于保存子程序"现场"
    - ​ POP {R1-R7, LR} 将数据入栈,放入R1-R7,LR寄存器, 多用于恢复子程序"现场"

    • ARM处理器特有,堆栈寻址的指令有LDMFA/STMFA、LDMEA/STMEA、LDMFD/STMFD、LDMED/STMED
  8. 块拷贝寻址 LDMIA R0!, {R1-R3} 从R0寄存器指向的存储单元中读取3个字节到R1-R3寄存器
    - ​ STMIA R0!, {R1-R3} 存储R1-R3寄存器的内容到R0寄存器指向的存储单元

    • 块拷贝寻址可实现连续地址数据从存储器的某一位置拷贝到另一位置。块拷贝寻址的指令有LDMIA/STMIA、LDMDA/STMDA、LDMIB/STMIB、LDMDB/STMDB。LDM和STM为指令前缀,表示多寄存器寻址,即一次可以传送多个寄存器值。IA、DA、IB、DB为指令后缀
  9. 相对寻址 BL NEXT 以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,两者相加得到操作数的有效地址, 执行后是跳到NEXT标号处执行。这里的BL采用的就是相对寻址,标号NEXT就是偏移量

ARM与Thumb指令集

ARM指令的基本格式

# opcode为指令助记符。如MOV、ADD等。
# cond为执行条件, 取值表为下图
# S指定指令是否影响CPSR寄存器的值。如ADDS、SUBS等。
# .W与.N为指令宽度说明符。在armv6t2及更高版本的Thumb代码中,部分指令的编码即可以是16位,也可以是32位。如果想要生成32位的编码,则可以为指令加上.W宽度说明符。如果要将指令汇编为16位编码,则可以为指令加上.N宽度说明符。
# Rd为目的寄存器。
# Rn为第一个操作数寄存器。
# operand2为第二个操作数。第二个操作数可以是立即数、寄存器或寄存器移位操作。
<opcode><cond>}{S}{.W (.N)  <Rd>,<Rn>{,<operand2>}
# 例子
MOV R0, #2
ADD R1, R2, R3
SUB R2, R3, R4, LSL #2


跳转指令

# 是ARM最简单的分支指令, 如果条件cond满足,ARM处理器将立即跳转到label指定的地址处继续执行。例:“BNE LABEL”表示条件码Z=0时跳转到LABEL处执行。
B{cond} label
# BL带链接的跳转指令, 如果条件cond满足,会首先将当前指令的下一条指令的地址拷贝到R14(即LR)寄存器中,然后跳转到lable指定的地址处继续执行。这条指令通常用于调用子程序,在子程序的尾部,可以通过“MOV PC, LR”返回到主程序中。
BL{cond} label
# BX带状态切换的跳转指令, 如果条件cond满足,则处理器会判断Rm的位[0]是否为1,如果为1则跳转时自动将CPSR寄存器的标志T置位,并将目标地址处的代码解释为Thumb代码来执行,即处理器会切换至Thumb状态;反之,若Rm的位[0]为0,则跳转时自动将CPSR寄存器的标志T复位,并将目标地址处的代码解释为ARM代码来执行,即处理器会切换到ARM状态。
BX{cond} Rm
# BLX带链接和状态切换的跳转指令, 集合了BL与BX的功能,当条件满足时,除了设置链接寄存器,还根据Rm位[0]的值来切换处理器状态。
BLX{cond} Rm

存储器访问指令

存储器访问操作包括从存储器中加载数据、存储数据到存储器、寄存器与存储器间数据的交换等。

# LDR用于从存储器中加载数据到寄存器中
# type指明了操作的数据的大小
# cond为执行条件
# Rt为要加载的寄存器
# label为要读取的内存地址。表示方法有三种: 1. 直接偏移量, 如:LDR R8, [R9, #04] 、LDR R8, [R9], #04; 2. 寄存器偏移, 如:LDR R8, [R9, R10, #04]; 3. 相对PC。如:LDR R8, label1
LDR{type}{cond} Rd, label
# LDRD一次加载双字的数据,将数据加载到Rd与Rd2寄存器中。如: LDRD R0, R1, label2 @从标号label2指向的内存中加载两个字的数据到R0与R1寄存器中
LDRD{cond} Rd, Rd2, label

# STR用于存储数据到指定地址的存储单元中。STR指令与LDR指令的格式相同,只是type中的SB与SH对STR无效。如: LDRD R0, [R2, #04] @将R0寄存器的数据存储到R2+4所指向的存储单元
STR{type}{cond} Rd, label
STRD{cond} Rd, Rd2, label

# LDM可以从指定的存储单元加载多个数据到一个寄存器列表。
# addr_mode取值
# cond为执行条件
# Rn为基地寄存器,用于存储初始地址
# !为可选的后缀。如果有!,则最终地址将写回到Rn寄存器中。
# reglist为用来存储数据的寄存器列表,用大括号括起来。寄存器列表可以是多个连续的寄存器,多个寄存器可以用“-”连接,如R0-R3表示连接的R0至R3寄存器列表,如果多个寄存器不是连续的,则使用逗号将它们分隔开来,如{R0, R1, R7}。
LDM{addr_mode}{cond} Rn{!} reglist
LDMIA R0!, {R0-R3} @依次加载R0指向的存储单元的数据到R1,R2,R3寄存器

# STM将一个寄存器列表的数据存储到指定的存储单元。STM与LDM的格式是一样的
STM{addr_mode}{cond} Rn{!} reglist
STMDB R1!, {R3-R6, R11} @将R3-R6, R11寄存器的内容存储到R1指向的存储单元
STMFD SP!, {R3-R7} @将R3-R7寄存器压入堆栈, 功能等价于STMDB SP!, {R3-R7}

# PUSH将寄存器推入满递减堆栈。
PUSH{cond} reglist
PUSH {r0, r4-r7} @将R0, R4-R7寄存器内容压入堆栈
# POP从满递减堆栈中弹出数据到寄存器。
POP{cond} reglist
POP {r0, r4-r7} @将R0, R4-R7寄存器内容从堆栈中弹出

# SWP用于寄存器与存储器之间的数据交换。
# B是可选的字节,若有B,则交换字节,否则交换32位的字。
# cond为执行条件。
# Rd为要从存储器中加载数据的寄存器。
# Rm为写入数据到存储器的寄存器。
# Rn为需要进行数据交换的存储器地址。Rn不能与Rd和Rm相同。
# 如果Rd与Rm相同,可实现单个寄存器与存储器的数据交换。
SWP{B}{cond} Rd, Rm, [Rn]
SWP R1, R1, [R0] @将R1寄存器与R0指向的存储单元的内容进行交换
SWPB R1, R2, [R0] @从R0指向的存储单元读取一个字节存入R1(高24位清零), 然后将R2寄存器的字节内容存储到该存储单元


数据处理指令

数据处理指令包括数据传送指令、算术运算指令、逻辑运算指令以及比较指令4类。数据处理指令主要是对寄存器间的数据进行操作。所有的数据处理指令均可选择使用S后缀,来决定是否影响状态标志,比较指令不需要S后缀,它们会直接影响状态标志。

数据传送指令

# MOV是使用最频繁的数据传送指令,它的功能是将8位的立即数或寄存器的内容传送到目标寄存器中。
MOV{cond}{S} Rd, operand2
MOV R0, #8 @R0=8
MOV R1, R0 @R1=R0
MOVS R2, R1, LSL #2 @R2=R1*4,影响状态标志

# MVN为数据非传送指令。它的功能是将8位的立即数或寄存器按位取反后传送到目标寄存器中。
MVN{cond}{S} Rd, operand2
MVN R0, #0xFF @R0=0xFFFFFF00
MVN R1, R2 @将R2寄存器数据取反后存入R1寄存器中

算术运算指令主要完成加、减、乘、除等算术运算。

# ADD为加法指令。它的功能是将Rn寄存器与operand2的值相加,结果保存到Rd寄存器。
ADD{cond}{S} Rd, Rn, operand2
ADD R0 ,R1, #2 @R0=R1+2
ADDS R0, R1, R2 @R0=R1+R2, 影响标志位
ADD R0, R1, LSL #3 @R0=R1*8
# ADC为带进位加法指令。它的功能是将Rn寄存器与operand2的值相加,再加上CPSR寄存器的C条件标志位的值,最后将结果保存到Rd寄存器。
ADC{cond}{S} Rd, Rn, operand2
ADD R0, R0, R2 
ADC R1, R1, R3 @两条指令完成64位加法, (R1,R0) = (R1,R0) + (R3,R2)
# SUB为减法指令。它的功能是用Rn寄存器减去operand2的值,结果保存到Rd寄存器中。
SUB{cond}{S} Rd, Rn, operand2
SUB R0 ,R1, #4 @R0=R1-4
SUBS R0, R1, R2 @R0=R1-R2, 影响标志位
# RSB为逆向减法指令。它的功能是用operand2减去Rn寄存器,结果保存到Rd寄存器中。
RSB{cond}{S} Rd, Rn, operand2
RSB R0, R1, #0x1234 @R0=0x1234-R1 
RSB R0, R1 @R0=-R1
# SBC为带进位减法指令。它的功能是用Rn寄存器减去operand2的值,再减去CPSR寄存器的C条件标志位的值,最后将结果保存到Rd寄存器。
SBC{cond}{S} Rd, Rn, operand2
SUB R0, R0, R2 @R0=0x1234-R1 
SBC R1, R1, R3 @两条指令完成64位减法, (R1,R0) = (R1,R0) - (R3,R2)
# RSC为带进位逆向减法指令。它的功能是用operand2减去Rn寄存器,再减去CPSR寄存器的C条件标志位的值,最后将结果保存到Rd寄存器。
RSC{cond}{S} Rd, Rn, operand2
RSBS R2, R0, #0 @R0=0x1234-R1 
RSC R3, R1, #0 @两条指令完成64位数取反, (R3,R2) = -(R1,R0)
# MUL为32位乘法指令。它的功能是将Rm寄存器与Rn寄存器的值相乘,结果的低32位保存到Rd寄存器中。
MUL{cond}{S} Rd, Rm, Rn
MUL R0 ,R1, R2 @R0=R1*R2
MULS R0, R2, R3 @R0=R2*R3, 影响CPSR的N位与Z位
# MLS指令将Rm寄存器和Rn寄存器中的值相乘,然后再从Ra寄存器的值中减去乘积,最后将所得结果的低32位存入Rd寄存器中。
MLS{cond}{S} Rd, Rm, Rn, Ra
MLS R0, R1, R2, R3 @R0的值为R3 - R1*R2结果的低32位
# MLA指令将Rm寄存器和Rn寄存器中的值相乘,然后再将乘积与Ra寄存器中的值相加,最后将结果的低32位存入Rd寄存器中。
MLA{cond}{S} Rd, Rm, Rn, Ra
MLA R0, R1, R2, R3 @R0的值为R3 + R1*R2结果的低32位
# MULL指令将Rm寄存器和Rn寄存器的值作为无符号数相乘,然后将结果的低32位存入RdLo寄存器,高32位存入RdHi寄存器。
UMULL{cond}{S} RdLo, RdHi, Rm, Rn
UMULL R0, R1, R2, R3 @(R1,R0) = R2*R3
# UMLAL指令将Rm寄存器和Rn寄存器的值作为无符号数相乘,然后将64位的结果与RdHi、RdLo组成的64位数相加,结果的低32位存入RdLo寄存器,高32位存入RdHi寄存器。
UMLAL{cond}{S} RdLo, RdHi, Rm, Rn
UMLAL R0, R1, R2, R3 @(R1,R0) = R2*R3 + (R1,R0)
# SMULL指令将Rm寄存器和Rn寄存器的值作为有符号数相乘,然后将结果的低32位存入RdLo寄存器,高32位存入RdHi寄存器。
SMULL{cond}{S} RdLo, RdHi, Rm, Rn
SMULL R0, R1, R2, R3 @(R1,R0) = R2*R3
# SMLAL指令将Rm寄存器和Rn寄存器的值作为有符号数相乘,然后将64位的结果与RdHi、RdLo组成的64位数相加,结果的低32位存入RdLo寄存器,高32位存入RdHi寄存器。
SMLAL{cond}{S} RdLo, RdHi, Rm, Rn
SMLAL R0, R1, R2, R3 @(R1,R0) = R2*R3 + (R1,R0)
# SMLAD指令将Rm寄存器的低半字和Rn寄存器的低半字相乘,然后将Rm寄存器的高半字和Rn的高半字相乘,最后将两个乘积与Ra寄存器的值相加并存入Rd寄存器。
SMLAD{cond}{S} Rd, Rm, Rn, Ra
# SMLSD指令将Rm寄存器的低半字和Rn寄存器的低半字相乘,然后将Rm寄存器的高半字和Rn的高半字相乘,接着使用第一个乘积减去第二个乘积,最后将所得的差值与Ra寄存器的值相加并存入Rd寄存器。
SMLSD{cond}{S} Rd, Rm, Rn, Ra
# SDIV为有符号数除法指令。
SDIV{cond} Rd, Rm, Rn
SDIV R0, R1, R2
# UDIV为无符号数除法指令。
UDIV{cond} Rd, Rm, Rn
UDIV R0, R1, R2 @R0 = R1/R2
# ASR为算术右移指令。它的功能是将Rm寄存器算术右移operand2位,并使用符号位填充空位,移位结果保存到Rd寄存器中。
ASR{cond}{S} Rd, Rm, operand2
ASR R0 ,R1, #2 @将R1寄存器的值作为有符号数右移2位后赋给R0寄存器

逻辑运算指令主要完成与、或、异或、移位等逻辑运算操作。

# AND为逻辑与指令
AND{cond}{S} Rd, Rn, operand2
AND R0, R0, #1 @指令用来测试R0的最低位
# ORR为逻辑或指令
ORR{cond}{S} Rd, Rn, operand2
ORR R0, R0, #0x0F @指令执行后保留R0的低四位, 其余位清0
# EOR为异或指令
EOR{cond}{S} Rd, Rn, operand2
EOR R0, R0, R0 @指令执行后R0的值为0
# BIC为位清除指令。它的功能是将operand2的值取反,然后将结果与Rn寄存器的值相“与”并保存到Rd寄存器中。
BIC{cond}{S} Rd, Rn, operand2
BIC R0, R0, #0x0F @将R0的低四位清0, 其余位保持不变
# LSL为逻辑左移指令。它的功能是将Rm寄存器逻辑左移operand2位,并将空位清0,移位结果保存到Rd寄存器中
LSL{cond}{S} Rd, Rm, operand2
LSL R0, R1, #2 @R0 = R1 * 4
# LSR为逻辑右移指令。它的功能是将Rm寄存器逻辑右移operand2位,并将空位清0,移位结果保存到Rd寄存器中
LSR {cond}{S} Rd, Rm, operand2
LSR R0, R1, #2 @R0 = R1 / 4
# ROR为循环右移指令。它的功能是将Rm寄存器循环右移operand2位,寄存器右边移出的位移回到左边,移位结果保存到Rd寄存器中
ROR {cond}{S} Rd, Rm, operand2
ROR R1, R1, #1 @将R1寄存器的最低位移到最高位
# RRX为带扩展的循环右移指令。它的功能是将Rm寄存器循环右移1位,寄存器最高位用标志位的值填充,移位结果保存到Rd寄存器中
RRX {cond}{S} Rd, Rm
RRX R1, R1, #1 @指令执行后R1寄存器右移1位, 最高位用标志填充

比较指令用于比较两个操作数之间的值。

# CMP指令使用Rn寄存器减去operand2的值,这与SUBS指令功能相同,但CMP指令不保存计算结果,仅根据比较结果设置标志位
CMP {cond} Rn, operand2
CMP R0, #0 @判断R0寄存器值是否为0
# CMN指令将operand2的值加到Rn寄存器上,这与ADDS指令功能相同,但CMN指令不保存计算结果,仅根据计算结果设置标志位
CMN {cond} Rn, operand2
CMN R0, R1
# TST为位测试指令。它的功能是将Rn寄存器的值和operand2的值进行“与”运算,这与ANDS指令功能相同,但TST指令不保存计算结果,仅根据计算结果设置标志位
TST {cond} Rn, operand2
TST R0, #1 @判断R0寄存器最低位是否为1
# TEQ的功能是将Rn寄存器的值和operand2的值进行“异或”运算,这与EORS指令功能相同,但TEQ指令不保存计算结果,仅根据计算结果设置标志位
TEQ {cond} Rn, operand2
TEQ R0, R1 @判断R0寄存器与R1寄存器的值是否相等

其他指令

# SWI是软中断指令。该指令用于产生软中断,从而实现从用户模式到管理模式的切换。如系统功能调用
SWI {cond}, immed_24
# immed_24为24位的中断号,在Android的系统中,系统功能调用为0号中断,使用R7寄存器存放系统调用号。使用R0-R3寄存器来传递系统调用的前4个参数,对于大于4个参数的调用,剩余参数采用堆栈来传递。
# 例如调用exit(0)的汇编
MOV R0, #0 @参数0
MOV R7, #1 @系统功能号1为exit
SWI #0 @执行exit(0)

# NOP为空操作指令。该指令仅用于空操作或字节对齐。指令格式只有一个操作码NOP。
# MRS为读状态寄存器指令, psr的取值可以是CPSR或SPSR。
MRS Rd, psr
MRS R0, CPSR @读取CPSR寄存器到R0寄存器中
# MSR为写状态寄存器指令, psr的取值可以是CPSR或SPSR, field指定传送的区域
MSR Rd, psr_fields, operand2
MSR R0, CPSR @读取CPSR寄存器到R0寄存器中
BIC R0, R0, #0X80 @清除R0寄存器第7位
MSR CPSR_c, R0 @开启IRQ中断
MOV PC, LR @子程序返回

"BMI loc_365C"BMI指令的作用是判断比较结果是否为负数(即CPSR寄存器的N位为1)时进行跳转。比较的结果来源于上一条指令“LSLS R0, R1, #0x1F”

代码中有两条“LDR PC, [R3,#0x18]”指令,前者是JavaVM指针(JNIInvokeInterface结构体的首地址),后者是JNIEnv指针(JNINativeInterface结构体的首地址)。LDR PC, [R12,#0x35C]"指令的数值0x35C上点击右键(load jni.h文件),然后在弹出的菜单中选择[R12,#JNINativeInterface.RegisterNatives]

arm64中X8寄存器的特殊情况


指针_bindata(文件偏移为0x1194)开始的前4个字节为"7F 45 4C 46",正是Android原生文件的有效标识

指针类型的变量宽度永远是4字节、无论类型是什么,无论有几个*。指针++,--为去掉一个*后的变量的宽度(一个*的基本类型, char*++为1, short*为2), 基本类型为自增自减运算, 指针类型只能加减不能乘除, 可以大小比较(为无符号比较), *(取指针内容符)加指针类型的类型是指针类型减去一个*, &(取地址符)加变量类型是取变量加一个*的地址

动态调试相关

gcc -E h.c -o h.i #1预处理
gcc -S h.i -o h.s #2编译
gcc -c h.s -o h.o #3汇编
gcc h.o –o h #4链接(需要其他目标文件,makefile)
# 可选
adb forward tcp:12345 tcp:12345
adb shell setenforce 0
# 远程调试, 安卓x86和arm使用对应的ndk\21.4.7075529\prebuilt\android-x86\gdbserver,需要使用ndk中提供的gdb,而不是系统的gdb
./gdbserver :12345 --attach $(pid or process_name) # 安卓无法attach name
gdb
(gdb) target remote 192.168.9.55:12345
# 自行启动进程方式
./gdbserver :12345 elf_name params
gdbserver --multi 0.0.0.0:12345

# lldb远程调试Android
# 安卓lldb的位置ndk\21.4.7075529\toolchains\llvm\prebuilt\windows-x86_64\lib64\clang\9.0.9\lib\linux\i386\lldb-server
# p或platform
./lldb-server platform --server --listen "*:12345"
# ./lldb-server p --server --listen unix-abstract:///data/local/tmp/debug.sock
adb forward tcp:12345 tcp:12345
# emulator-5554为adb devices的id
$ lldb
platform list  # 看下lldb可以连接的平台
platform select remote-android
platform status # 查看平台状态
platform connect connect://emulator-5554:12345
# arm真机ip 192.168.9.28, 使用wsl 连接,没有安装adb, 宿主机有usb连接,使用下面的命令连接成功,奇怪
platform connect connect://localhost:12345
# platform connect unix-abstract-connect:///data/local/tmp/debug.sock
# 调试一方便调试那种一启动就崩溃的bug, 可以在应用启动前打好断点, 启动时即可触发断点(launch breakpoint).
file a.out # 指定将要调试的二进制文件,注意是相对于WorkingDir(/data/local/tmp/)的路径
br set -f app_core.cpp -l 128 # 意思就是在app_core.cpp的128行处打个断点
run

# 调试二程序已经运行, 可以用attach的方式
file a.out # 指定将要调试的二进制文件,注意是相对于WorkingDir的路径
platform process list # 查看一直远端的进程, 找到目标进程pid, 或者名称
attach 9053
# 这种jdwp不行,使用ddms可以成功, 然后lldb执行attach 9053, continue
adb forward tcp:12346 jdwp:<pid> # the same pid(9053) which we attached in lldb
# linux
jdb -attach localhost:12346
# win
jdb -connect "com.sun.jdi.SocketAttach:hostname=localhost,port=12346"
# adb shell am start -D -n com.instagram.android/com.instagram.nux.activity.SignedOutFragmentActivity
# jdb链接port为8625/8700的debug端口
jdb -connect "com.sun.jdi.SocketAttach:hostname=localhost,port=8700"

Android NDK根目录下的ndk-gdb与toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/bin目录下的arm-linux-androideabi-gdb都可以用来调试Android原生程序。但这两个程序是动态编译的,不包含符号信息,调试时需要设置Android系统动态链接库的符号加载路径,并且只能调试拥有调试信息的原生程序,而一般情况下,使用Android NDK编译的原生程序都不包含调试信息,因此无法使用官方自带的gdb来对原生程序进行汇编级调试。原版gdb下载地址:ftp://sourceware.org/pub/gdb/releases/

# python2.7-dev,libc6-dev
apt install bison flex libncurses-dev texinfo gawk libtool libgmp-dev python2-dev
# gdb
cd gdb-xxx/gdb
./configure --target=arm-linux-androideabi --with-python --prefix=$HOME/mybinaries/bin
make
make install
# gdbserver
cd gdb-xxx/gdb/gdbserver
CC=arm-linux-androideabi-gcc LDFLAGS="-fPIC -pie" ./configure --target=arm-linux-androideabi --host=arm-linux-androideabi --prefix=$HOME/mybinaries/bin
make
make install

apt instll expat

gcc64编译32位应用

gcc -m32 test.cpp
apt-get install build-essential
apt-get install gcc-multilib g++-multilib  

ida的Graph view有三种颜色的箭头:蓝色箭头表示顺序执行;绿色箭头表示条件满足时执行;红色箭头表示条件不满足时执行。

内存劫持补丁 AheadLib


GLOBAL_OFFSET_TABLE名为全局偏移表,称为GOT,注释中的PIC表示这是位置无关代码(Position Independent Code)。gcc编译程序时期变量的装载地址不能确定,ARM采用了GOT来解决这个问题。在ARM程序编译期间,编译器将程序中所有全局变量的labal地址存放到一个“.got”段中,代码中对变量的访问被设计成采用索引方式对GOT的元素访问。程序在运行时,linker会读取变量的实际地址并填充GOT中的各项元素,这样在访问变量时就不会出现变量地址错误等问题。

X86相关

EAX - Main register used in arithmetic calculations. Also known as accumulator, as it holds results of arithmetic operations and function return values. 用于算术计算的主寄存器。也称为累加器,因为它保存算术运算的结果和函数返回值。

EBX - The Base Register. Pointer to data in the DS segment. Used to store the base address of the program. 基本寄存器。指向 DS 区段中数据的指针。 用于存储程序的基址。

ECX - The Counter register is often used to hold a value representing the number of times a process is to be repeated. Used for loop and string operations. 计数器寄存器通常用于保存表示进程要重复的次数的值。用于循环和字符串操作。

EDX - A general purpose registers. Also used for I/O operations. Helps extend EAX to 64-bits. 通用寄存器。也用于 I/O 操作。帮助将 EAX 扩展到 64 位。
ESI - Source Index register. Pointer to data in the segment pointed to by the DS register. Used as an offset address in string and array operations. It holds the address from where to read data. 源索引寄存器。指向 DS 寄存器所指向的段中数据的指针。 用作字符串和数组操作中的偏移地址。它保存从何处读取数据的地址。

EDI - Destination Index register. Pointer to data (or destination) in the segment pointed to by the ES register. Used as an offset address in string and array operations. It holds the implied write address of all string operations. 目标索引寄存器。指向 ES 寄存器所指向的段中的数据(或目标)的指针。 用作字符串和数组操作中的偏移地址。它保存所有字符串操作的隐含写入地址。

EBP - Base Pointer. Pointer to data on the stack (in the SS segment). It points to the bottom of the current stack frame. It is used to reference local variables. 基本指针。指向堆栈上数据的指针(在 SS 段中)。 它指向当前堆栈帧的底部。它用于引用局部变量。

ESP - Stack Pointer (in the SS segment). It points to the top of the current stack frame. It is used to reference local variables. 堆栈指针(在 SS 段中)。它指向当前堆栈帧的顶部。它用于引用局部变量。

EIP - Instruction Pointer (holds the address of the next instruction to be executed) 指令指针(保存要执行的下一条指令的地址)

call会压入返回地址进栈, ret会把栈顶的地址存入eip, ebp+8为函数的第一个参数, ebp+C为第二个参数, 传递参数可以push堆栈(参数入栈从右向左压栈), 也可以使用寄存器, ebp-4为第一个局部变量, ebp-8为第二个局部变量

常用的三种调用约定

调用者约定 __codecl 参数从右向左入栈, __stdcall 参数从右向左入栈, __fastcall ecx/edx传送前两个参数,剩下:从右向左入栈, 堆栈平衡为 __codecl 调用者清理栈, __stdcall自身清理堆栈, __fastcall 自身清理堆栈,

ebp寄存器

EBP+4*N+4: 参数N
EBP+C: 参数 2
EBP+8: 参数 1
EBP+4: 返回地址
EBP: 旧的ebp值(调用者的ebp)previous EBP
EBP-4: 局部变量 1
EBP-8: 局部变量 2

PE可执行文件结构体定义在winnt.h

call地址的计算 E8 00 00 00 00, E8为call的机器吗, 后面的地址数据为code(计算公式为要跳转的地址-下一行的地址[当前地址+5]) jmp为E9, push为6A, 计算方式: 要跳转的地址 - E8指令当前的地址(ImageBase+offset) - 5

扩大节: 在最后的section段添加大小为N的hex(比如1000h,4096), 然后将最后一个节的SizeOfRawData和VirtualSize(改二者值最大)按照内存对齐后的值加上N, 最后修改 SizeOfImage的数值加上N

vc++6.0代码提示列出成员快捷键Ctrl+Alt+T

编译VS时候,出现fatal error C1010: unexpected end of file while looking for precompiled head。

1.右键单击项目工程中的cpp文件,在菜单Project->Settings->C/C++->Precompile Header,设置为第一项:Not using precompile headers。

2.在.cpp文件开头添加包含文件stdafx.h。 #include"stdafx.h"

加壳技术的识别

ADVMP:https://github.com/chago/ADVMP

函数抽取是函数体无效(nop),vmp和dex2c加壳都是Activity.onCreate函数native化,不同点是vmp的特征是Activity的跳转,logcat的注册地址entryPoint的地址不会变化,函数逻辑是否相似,dex2c的地址会变,逻辑也不相似

dex2c:获取到保护的dex后,和VMP一样, 被保护函数的属性由java属性变为Native,如 开源的DCC (Dex-to-C Compiler); DCC (Dex-to-C Compiler)编译生成so; 混合型壳:多种加固技术混合使用,比如先将原有smali指令流使用 VMP或dex2c保护,然后再经过函数抽取进一步保护

脱壳点

dalvik,在函数里添加dump dex的代码

char dexfilepath[100]={0};
int pid=getpid();
sprintf(dexfilepath,"/sdcard/%d_%d_dexFileParse.dex",length,pid);
//fopen
int fd=open(dexfilepath,O_CREAT|O_RDWR,0666);
if(fd>0)
{
    write(fd,data,length);
    close(fd);
}

xref: /dalvik/libdex/DexFile.cpp -> dexFileParse(const u1* data, size_t length, int flags)

常用hook点

java层

Native层

trace("exports:*!open*");
exports:*!write*
exports:*!malloc*
exports:*!free*
最后由 不一样的少年 编辑于2022年03月11日 14:44