keypatch基本使用

keypatch是用来辅助打patch的一个插件。IDA本身有简单的patch功能,可以写十六进制和汇编语句。keypatch提供的功能主要是可以即时显示汇编语句的十六进制,你可以知道你的汇编占多少字节,打完patch后在它把原汇编备注在旁边,它还可以帮你计算跳转偏移。

image-20220521155821544

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代码的地址

例如:修复前:

image-20220521162816325

修复后:

image-20220521162827089

image-20220521162849688

再次翻阅时发现我写的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

image-20220521170419550

还有一个发现是,虽然.eh_frame段在IDA的Segmentation里看到的权限只有R,但是实际测试发现,写在里面的代码是可以执行的,可能是因为.eh_frame段就在代码段的下方,映射的时候和代码段在同一个页里,而权限又是一整页的属性,所以实际上.eh_frame段是可以执行的。

image-20220521165034862

栈溢出

改读的字节数

增加代码,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

nop 掉 malloc

增加代码,在读的字节中过滤一些特殊的字符

打乱got表

tips

随机应变,一切需要加逻辑的patch都可以把代码放.eh_frame段里。

参考

http://p4nda.top/2018/07/02/patch-in-pwn/

https://tianstcht.github.io/pwn里的一些patch心得/