keypatch基本使用
keypatch是用来辅助打patch的一个插件。IDA本身有简单的patch功能,可以写十六进制和汇编语句。keypatch提供的功能主要是可以即时显示汇编语句的十六进制,你可以知道你的汇编占多少字节,打完patch后在它把原汇编备注在旁边,它还可以帮你计算跳转偏移。
Edit -> Keypatch -> Patcher
选中一行指令patch
可以输入汇编代码 nop啥的。
Edit -> Keypatch -> Fill Range
选中一定范围的指令一起patch
可以输入汇编代码,也可以输入16进制 “90” “0x90” “90,91” “AAh”等。
Edit -> Patch program -> Apply patches to input file
撤消上一个操作,而且可以撤销多次(好像是无限制?亲测20次还是能继续撤销
Edit -> Patch program -> Apply patches to input file
将修改保存到一个新的二进制文件(这是ida原本就有的功能
Edit -> Keypatch -> Search
可以搜索汇编指令,不能直接搜索16进制,多条指令用 ;
分隔
awd里的打patch技巧
UAF
增加置0的代码,新增的代码可以放在.eh_frame
段里,call free
改成call 置0代码的地址
。
例如:修复前:
修复后:
再次翻阅时发现我写的patch相当傻,t1an5t师傅是这么写的,很简洁标准。
call free;
lea rax, qword_2032A0;
add rax, rdx;
mov qword ptr [rax], 0;
ret;
这里lea rax, xxxx由七个字节构成,其中lea rax部分由三字节构成:48 8d 05。
后面接四字节的偏移值,计算方法为:
offset = target - (start + 7)
keypatch算偏移有时会出现错误,手动算偏移方法是:
call的固定长度为5个字节,跳转指令若目的地址大于当前地址且为近地址跳转的时候,为2个字节,否则为5个字节。
(1)要到达的地址高于当前地址,这种比较好计算:(这个5不是因为call指令占5个字节,当前指令无论是多少字节这个偏移都是5)
offset = target - (start + 5)
(2)是要到达的地址低于当前地址,计算如下:
offset = 0xffffffff + 1 - (start + 5 - target)
例如,从0x400AC2的call puts
跳到0x400A4B,指令是E8 84 FF FF FF
p/x 0xffffffff + 1 - (0x400AC2 + 5 - 0x400A4B)
$1 = 0xffffff84
还有一个发现是,虽然.eh_frame
段在IDA的Segmentation里看到的权限只有R,但是实际测试发现,写在里面的代码是可以执行的,可能是因为.eh_frame
段就在代码段的下方,映射的时候和代码段在同一个页里,而权限又是一整页的属性,所以实际上.eh_frame
段是可以执行的。
栈溢出
改读的字节数
增加代码,gets改成read,没有read可以通过系统调用的形式。
增加栈空间 sub rsp,xxxx
增加代码,过滤不可见字符:
- 如果要跳转到库函数,call它的plt地址即可
容易造成栈溢出的函数
void * memcpy ( void * destination, const void * source, size_t num );
char * strcpy ( char * destination, const char * source );
char * strncpy ( char * destination, const char * source, size_t num );
char * gets(char*str);
ssize_t read(int fd, void *buf, size_t count);
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
char * fgets ( char * str, int num, FILE * stream );
char * strcat ( char * destination, const char * source );
scanf(“%s”, &s);
堆溢出
改读的字节数
增加代码,gets改成read,没有read可以通过系统调用的形式。
增加malloc申请的空间
增加代码,过滤不可见字符
off by one/null
改读的字节数
整数溢出
有符号跳转改为无符号。
无符号跳转:
JA ;无符号大于则跳转
JNA ;无符号不大于则跳转
JAE ;无符号大于等于则跳转 同JNB
JNAE ;无符号不大于等于则跳转 同JB
JB ;无符号小于则跳转
JNB ;无符号不小于则跳转
JBE ;无符号小于等于则跳转 同JNA
JNBE ;无符号不小于等于则跳转 同JA
有符号跳转:
JG ;有符号大于则跳转
JNG ;有符号不大于则跳转
JGE ;有符号大于等于则跳转 同JNL
JNGE ;有符号不大于等于则跳转 同JL
JL ;有符号小于则跳转
JNL ;有符号不小于则跳转
JLE ;有符号小于等于则跳转 同JNG
JNLE ;有符号不小于等于则跳转 同JG
数组越界
有符号跳转改为无符号
增加代码,判断index大小
格式化字符串
程序里有puts的话把call printf改成call puts
增加代码,添加合适的参数,将printf(xxx)改为printf(“%s”,xxxxx)
增加代码,把printf改成write,没有write可以通过系统调用的形式。
容易造成格式化字符串漏洞的函数:
int printf ( const char * format, ... );
int fprintf ( FILE * stream, const char * format, ... );
int sprintf ( char * str, const char * format, ... );
条件竞争
把sleep等消耗时间的函数nop掉
增加代码,加锁(麻烦)
命令执行
把命令执行函数nop掉
容易造成命令执行的函数:
FILE *popen(const char *command, const char *type);
int system(const char *command);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags);
不太优雅的方法
nop 掉 free
把free的plt表改成ret
nop 掉 malloc
增加代码,在读的字节中过滤一些特殊的字符
打乱got表
tips
随机应变,一切需要加逻辑的patch都可以把代码放.eh_frame
段里。