pwn里的一些patch心得

  1. 前言
  2. 跳转指令与call命令
  3. 修复UAF问题
  4. 控制if包含范围
  5. read,gets,printf等
  6. 计算偏移地址的函数

前言

最近刚参加完国赛东北赛区的线下赛,觉得patch是个很重要的技巧,所以这里记录一下我自己的patch经验和心得。毕竟如果只靠各种危险代码为nop这种方式梭哈,也太没意思了。

这里先介绍一下万恶之源:

p4nda师傅的blog提到了一种用.eh_frame段来写代码patch的手法,被我偷学来了,这种方法比较有效而且对文件来说改动较小。

这里我觉得64位的patch难度是要小于32位的,因为32位还需要自己手动调整栈空间,感觉很麻烦。所以主要针对64位程序做一下心得总结。

注:以下关于各类opcode的说法都是基于猜测,并没有手册作为依据。只不过对我自己来说暂时够用而已。

跳转指令与call命令

跳转指令就是如jmp, jg,jle等等这些。

call的本质也是跳转,只不过多了一步保存栈结构和保存下一条指令地址入栈而已。

它们的性质都差不多,所以汇编的opcode形式也是极其类似的,都是以指定的操作opcode+offset构成。

只针对地址而言,call的固定长度为5个字节,

而跳转指令若目的地址大于当前地址且为近地址跳转的时候,为2个字节,否则为5个字节。

有两种偏移地址offset,下面介绍分别如何计算:

一种是要到达的地址高于当前地址,这种比较好计算:

offset = target - (start + 5)    

另一种是要到达的地址低于当前地址,计算如下:

offset = 0xffffffff - (start - target) + 1 - 5

如果是printfscanf等这类的库函数,并且在程序中有它的PLT和GOT结构,那么把对应的PLT当作target进行计算即可。

跳转指令因为都是单字节的opcode加偏移的形式,所以有一种情况可以对跳转指令进行替换以实现程序的修复,也就是无符号数有符号数使用不当,导致某些整数型错误的情况:

比如这种情况下,如果输入readNum的时候输入-1也可以满足跳转条件,但是在read的时候便会造成溢出:

call    readNum
add     esp, 10h
cmp     eax, 20h
jle     short loc_8048777
......
loc_8048777:
mov     edi, eax
sub     esp, 4
push    eax
lea     eax, (aNowSayPassword - 804A000h)[ebx] ; "Now, Say password(%u):"
push    eax
push    1
call    ___printf_chk
add     esp, 0Ch
push    edi             ; nbytes
lea     eax, [ebp+buf]
push    eax             ; buf
push    0               ; fd
call    _read

当然修复也非常简单:

jle --> jbe

下面是拜托学弟收集的很多跳转指令:

无符号跳转:

JA ;无符号大于则跳转
JNA ;无符号不大于则跳转
JAE ;无符号大于等于则跳转 同JNB
JNAE ;无符号不大于等于则跳转 同JB
JB ;无符号小于则跳转
JNB ;无符号不小于则跳转
JBE ;无符号小于等于则跳转 同JNA
JNBE ;无符号不小于等于则跳转 同JA

有符号跳转:

JG ;有符号大于则跳转
JNG ;有符号不大于则跳转
JGE ;有符号大于等于则跳转 同JNL
JNGE ;有符号不大于等于则跳转 同JL
JL ;有符号小于则跳转
JNL ;有符号不小于则跳转
JLE ;有符号小于等于则跳转 同JNG
JNLE ;有符号不小于等于则跳转 同JG

 

修复UAF问题

一般来说,pwn里面出现的UAF问题都会很明显,就是free的时候没把预留的指针清除掉,在F5大法下很容易发现,那么如何patch呢。

我的思路是改对应位置的call freecall .eh_frame的起始地址,然后在.eh_frame节区的部分写我们自定义的函数,然后最后写上ret跳回去。不要忘记要先为.eh_frame节区赋予可执行的权限,这点在IDA里就能做到。

自定义函数部分,先写一句call free,注意因为这里偏移值都变化了,所以要自己计算偏移然后写opcode,有的时候keypatch可能会出现错误。

然后去gdb里动态调试一下,观察此时free的堆指针所在的位置,模仿之前的逻辑,找到指针然后将它置0。

比如国赛碰到的这道题:

.text:0000000000001647                 lea     rdx, ds:0[rax*8]
.text:000000000000164F                 lea     rax, qword_2032A0
.text:0000000000001656                 mov     rax, [rdx+rax]
.text:000000000000165A                 mov     rdi, rax        ; ptr
.text:000000000000165D                 call    _free

可以看到一开始rdx作为指针数组的偏移值,rax作为指针数组的基地址。

所以可以在自定义函数里这么写:

call free;
lea rax, qword_2032A0;
add rax, rdx;
mov qword ptr [rax], 0;
ret; 

这里说一句lea rax, xxxx是一个有七个opcode构成的,其中lea rax部分由三字节构成:48 8d 05

后面接四字节的偏移值,计算方式类似于之前的call

offset = target - (start + 7)

然后call free这里也是自己计算偏移写进去,剩下的用keypatch搞定即可。

 

控制if包含范围

这个也是这次比赛中瞎鼓捣出来的,感觉很好玩。

比如这道pwn题漏洞点在这里:

unsigned __int64 sub_12B8()
{
  signed int idx; // [rsp+Ch] [rbp-14h]
  void *ptr; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("index:");
  idx = sub_D5D();
  if ( idx >= 0 && idx <= 9 )
  {
    ptr = *(void **)(16LL * idx + chunk_bss + 8);
    *(_QWORD *)(16LL * idx + chunk_bss) = 0LL;
    *(_QWORD *)(16LL * idx + chunk_bss + 8) = 0LL;
  }
  if ( ptr )
    free(ptr);
  return __readfsqword(0x28u) ^ v3;
}

因为虽然检查了idx的合法性,缺没检查ptr是怎么来的,所以可以与之前的某些输入一起形成一个任意地址的free

 

所以我的修复逻辑便是把if(ptr) free(ptr)这段想办法放到上面的if里面。所以去观察上面的if的反汇编:

.text:00000000000012ED                 cmp     [rbp+idx], 0
.text:00000000000012F1                 js      short loc_135B
.text:00000000000012F3                 cmp     [rbp+idx], 9
.text:00000000000012F7                 jg      short loc_135B

如果把js short loc_135Bjg short loc_135B的偏移值更改,让它把call free也包括进来就行了!

 

这里通过查看ida显示的opcode可知,跳转指令只有两字节,所以我们也写两字节,改为了:

.text:00000000000012ED                 cmp     [rbp+var_14], 0
.text:00000000000012F1                 js      short loc_136F
.text:00000000000012F3                 cmp     [rbp+var_14], 9
.text:00000000000012F7                 jg      short loc_136F

 

135B136F分别对应:

.text:000000000000135B loc_135B:                               ; CODE XREF: sub_12B8+39↑j
.text:000000000000135B                                         ; sub_12B8+3F↑j
.text:000000000000135B                 cmp     [rbp+ptr], 0
.text:0000000000001360                 jz      short loc_136F
.text:0000000000001362                 mov     rax, [rbp+ptr]
.text:0000000000001366                 mov     rdi, rax        ; ptr
.text:0000000000001369                 call    free
.text:000000000000136E                 nop
.text:000000000000136F
.text:000000000000136F loc_136F:                               ; CODE XREF: sub_12B8+A8↑j
.text:000000000000136F                 nop
.text:0000000000001370                 mov     rax, [rbp+var_8]
.text:0000000000001374                 xor     rax, fs:28h
.text:000000000000137D                 jz      short locret_1384
.text:000000000000137F                 call    __stack_chk_fail

 

修复好之后的f5显示为:

unsigned __int64 sub_12B8()
{
  signed int idx; // [rsp+Ch] [rbp-14h]
  void *ptr; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("index:");
  idx = read_int();
  if ( idx >= 0 && idx <= 9 )
  {
    ptr = *(void **)(16LL * idx + chunk_bss + 8);
    *(_QWORD *)(16LL * idx + chunk_bss) = 0LL;
    *(_QWORD *)(16LL * idx + chunk_bss + 8) = 0LL;
    if ( ptr )
      free(ptr);
  }
  return __readfsqword(0x28u) ^ v3;
}

同理其实我推测也可以扩展for的范围,这个有时间测试一下。

read,gets,printf等

这种patch起来相对简单了.

read只需要改变rdx值也就是nbytes的传参值大小,控制为不会栈溢出即可。

gets的话按照uaf的方式修复,改成read来执行。如果程序中没有read也可以通过系统调用的形式起read

printf的话问题就是格式化字符串漏洞嘛,修的话按照上述方法改成puts。其实比较怕的就是程序里没有puts,暂时想不到太完美的patch方式。

计算偏移地址的函数

我实现了两个计算低地址和高地址偏移的函数,省得以后总是手算起来麻烦:

def offset_to_opcode(offset):
    opcode = ""
    for _ in range(4):
        opcode += (hex(offset & 0xff)[2:]).strip('L').rjust(2, "0") + " "
        offset = offset >> 8
    return opcode


def generate_high_addr_opcode(start, target):
    offset = target - (start + 5)
    opcode = offset_to_opcode(offset)
    return opcode


def generate_low_addr_opcode(start, target):
    offset = 0xffffffff - (start - target) + 1 - 5
    opcode = offset_to_opcode(offset)
    return opcode

。。。待续


connect 1037178204@qq.com

文章标题:pwn里的一些patch心得

本文作者:t1an5t

发布时间:2019-06-23, 21:13:57

最后更新:2020-02-05, 21:31:50

原始链接:http://yoursite.com/pwn%E9%87%8C%E7%9A%84%E4%B8%80%E4%BA%9Bpatch%E5%BF%83%E5%BE%97/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录