这两天做题的时候同届的pwn师傅sakura来问有没有可以修改IDA局部变量的方法。研究了一下感觉很有意思,于是单独开一篇文章记录我经常用到的IDA小技巧。

修改局部变量

IDA

IDA有两种实现方式,我在网上查到可以force new variable,但是我一直没成功。

实现效果:

步骤:

因为可以观察到都是分开赋值的

我们在var_5那边按U

我们的目标是分出一个char+int,因为我得需求是让v2[0]单独分出来变成char,v2[1]-v2[4]变成四字节的int,所以我们选择1+4

在01的地方按N重命名成i,在05的地方选择建立数组(可以按A)在N成opcode

回到executeBrainfuck按F5

接着来改汇编

因为IDA在反编译的时候,会根据变量的访问方式和指令来推断变量类型

1
mov     dword ptr [rbp+opcode+1], 0  ; 赋值了 4 个字节

opcode[1]之后是 4 个连续的字节,IDA会推测opcode[5]这样的存储

我们把它nop掉

1、opcode+1字节数显着地4初始化,IDA 无法确认它是内存的一部分,因此可能会被拆除int

2.IDA偏向于最简单的数据结构,因此[rbp-5h]直接赋值signed __int8 opcode,而[rbp-4h]作为int opcode_1处理。

在opcode那里按Y转换函数类型+按N重命名i就好了

Ghidra一把梭

考虑到是pwn的师傅,所以不推荐()ghidra挺麻烦的但是足够底层,确实好用

伪代码对应汇编

可以实现点伪代码哪里汇编会相应跳转,汇编同理,patch的时候好用

修复枚举值

以ptrace函数为例,这里的12LL表示一个功能号。在12上面按M,点击yes,ctrl+F搜索ptrace,这里是PTRACE_GETREGS

positive sp value

方法

函数堆栈不平衡:
1、函数头尾是否平衡
2、永假跳转&破坏栈指针的nop掉

其实能反编译的可以都不用管,网上很多方法都只是为了反编译服务没有真的解决问题,具体的问题得追具体的堆栈。
实在不会直接Alt+K改显示负数的堆栈一个个试就行。

原理

打开栈指针显示

Options-General

在0x411940的位置按Alt+K,可以理解为没有push ebp前SP是0,push ebp完是0x4。

堆栈是从高处往低处生长的。
函数调用(call)一般会压栈返回地址,函数内会使用 push 压参数,调用完毕后会通过 ret 或 ret x(其中 x 是参数大小)来恢复栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:004119FA -04    cmp     ebp, esp
;这是对比当前栈指针 esp 和基址指针 ebp。
;如果这两个相等,说明函数栈帧还“干净”,没有栈溢出或未平衡的 push/pop 操作。
;否则,就是 栈不平衡的迹象(也就是函数执行过程中破坏了栈结构)


.text:004119FC -04 call j___RTC_CheckEsp
;MSVC 编译器生成的运行时检测函数,主要用于调试和运行时检查目的。
;检查 cmp ebp, esp 的结果,如果 esp 与 ebp 不相等,就可能抛出错误或崩溃提示。


.text:00411A01 -04 mov esp, ebp
;无论前面 esp 被破坏得多严重,这条语句是强制把栈恢复到原始基址,保证函数退出时栈是干净的。

堆栈平衡

在 x86 体系中,函数调用是通过堆栈传参的,每次 call 函数都会把返回地址压入堆栈,参数也一般是通过 push 入栈。
而函数执行完后,需要恢复这些 push。

1
2
3
4
5
6
7
8
int add(int a, int b) {
return a + b;
}

int main() {
int x = add(1, 2);
return 0;
}

函数调用处(main):
1
2
3
4
push 2        ; 参数 b
push 1 ; 参数 a
call add ; 跳转并保存返回地址到栈里
add esp, 8 ; 清理参数(2个 × 4字节)

函数返回处(add):
1
2
3
4
mov eax, [ebp+8]   ; a
add eax, [ebp+0Ch] ; b
pop ebp
ret ; 弹出返回地址

  • 比如少了 少了 add esp, 8ret 8堆栈就会不平衡

当执行 call 指令时,CPU 会自动做两件事:
1、把下一条指令的地址(返回地址)压栈(即 ESP 所指的地方);
2、跳转到函数体的地址(call 指向的函数)执行;

Decompile as call

IDA官方:Decompile as call (反编译为调用)
Invoke Edit > Other > Decompile as call…

Decompile as call 主要用于在反编译视图中将某个不可识别的指令或未知函数强制当作函数调用处理。

__usercall: 是一种伪函数声明语法,可以手动指定调用约定、输入/输出寄存器等。

too big stack frame

打开栈指针显示
Options-General

官方文档:Fixing “stack frame is too big”
你总览先扒拉一下看看哪里SP不对

  • 错误创建的(按下立即作数)
  • 故意诱导的(垃圾代码是指实际未使用的大堆栈偏移量)


其实和positive sp value解方法差不多,哪里不对修哪里。第一个异常增加esp,nop掉,第二个修改局部变量大小(上面贴的🔗和我文中上面都有讲怎么改)

已知结构体大小创建结构体

IDA9.0/IDA9.1找不到Structure:https://github.com/CynicRus/StructureCreatorPlugin-for-IDA-9.0
local types也可以我用不习惯


在Structure窗口()

1
2
3
4
Ins/Del : 插入/删除结构体
D/A/* : 创建结构体成员(数据型/ASCII字符型/数组型)
N : 重命名结构体或结构体成员
U : 删除结构体成员

  • ins创建结构体
  • D 创建字段
  • * 修改数组大小

  • 添加数组间隔

跳转表修复

GCC在编译超过5个switch的结构时会用跳表进行优化
Edit -> Other —> Specify switch

  • Address of Jump table:跳表的地址
  • Number of elements:跳转表项数
  • Size of table element:跳转每项的字节数
  • Element shift amount:这个一般情况下都是零,和跳表计算时的方式有关,
  • Element base value:设置为计算跳转地址时给跳表元素加的值,
  • Start of the switch idiom:这个默认就行,就是获取跳表值的语句的地址
  • Input register of switch:设置为用于给跳表寻址的寄存器
  • First(lowest) input value:就是 switch 的最小值了
  • Default jump address:也就是 default 的跳转位置,其实有时候可以不填,但是最好还是填上,这个一般在上方不远处的 cmp 指令附近。