前言
年底了,2021年最后一场比赛,我就表演个AK RE给大伙助助兴吧。
比赛官网:https://sctf2021.xctf.org.cn
比赛时间:2021-12-25 09:00:00 到 2021-12-27 09:00:00
Reverse
SycGame
main中逻辑很简单,就是循环5次游戏,成功后拿到flag,关键在于sub_24E4的游戏逻辑。
int __fastcall sub_24E4()
{
int v0; // eax
int v1; // ebx
int v2; // er12
char v4; // [rsp+Bh] [rbp-15h]
int i; // [rsp+Ch] [rbp-14h]
srand(0);
v0 = time(0LL);
srand(v0 + srand_seed);
srand_seed += rand();
reset_globals();
init_primes();
size = 20;
problem_size = 4;
set_map();
print_gift();
null();
printf("Tell me sol:");
for ( i = 1; ; ++i )
{
if ( i > 10000 )
fail();
v4 = getchar();
if ( v4 != '\n' )
break;
LABEL_26:
;
}
if ( v4 != 'w' && v4 != 's' && v4 != 'a' && v4 != 'd' && v4 != '0' )//
// directions['w'] = 0;
// directions['s'] = 1;
// directions['a'] = 2;
// directions['d'] = 3;
fail_0();
if ( v4 == '0' )
fail_1();
if ( map[y_offset[directions[v4]]][3005 * y + x_offset[directions[v4]] + x] == -1 )// map[y + y_offset][x + x_offset]
{
if ( map[y_offset[directions[v4]]][3005 * y_offset[directions[v4]]
+ 3005 * y
+ x_offset[directions[v4]]
+ x
+ x_offset[directions[v4]]] == -3 )// map[y + y_offset * 2][x + x_offset * 2]
{
++problem_solved;
map[y_offset[directions[v4]]][3005 * y_offset[directions[v4]]// map[y + y_offset * 2][x + x_offset * 2]
+ 3005 * y
+ x_offset[directions[v4]]
+ x
+ x_offset[directions[v4]]] = -1;
map[y_offset[directions[v4]]][3005 * y + x_offset[directions[v4]] + x] = -2;// map[y + y_offset][x + x_offset]
map[y][x] = map_backup[y][x];
y += y_offset[directions[v4]];
x += x_offset[directions[v4]];
}
else
{
if ( !is_composite[map[y_offset[directions[v4]]][3005 * y_offset[directions[v4]]
+ 3005 * y
+ x_offset[directions[v4]]
+ x
+ x_offset[directions[v4]]]] )
{
putchar('!');
fail_2();
}
if ( map_backup[y_offset[directions[v4]]][3005 * y + x_offset[directions[v4]] + x] == -3 )// map[y + y_offset][x + x_offset]
--problem_solved;
map[y_offset[directions[v4]]][3005 * y_offset[directions[v4]]
+ 3005 * y
+ x_offset[directions[v4]]
+ x
+ x_offset[directions[v4]]] = -1;// map[y + y_offset * 2][x + x_offset * 2]
map[y_offset[directions[v4]]][3005 * y + x_offset[directions[v4]] + x] = -2;// map[y + y_offset][x + x_offset]
map[y][x] = map_backup[y][x];
y += y_offset[directions[v4]];
x += x_offset[directions[v4]];
}
}
else
{
if ( !is_composite[map[y_offset[directions[v4]]][3005 * y + x_offset[directions[v4]] + x]] )// map[y + y_offset][x + x_offset]
fail_2();
map[y_offset[directions[v4]]][3005 * y + x_offset[directions[v4]] + x] = -2;// map[y + y_offset][x + x_offset]
v1 = y;
v2 = x;
map[v1][v2] = random_composite();
y += y_offset[directions[v4]];
x += x_offset[directions[v4]];
}
if ( problem_solved != problem_total )
{
null();
goto LABEL_26;
}
return 1;
}
init_primes 函数计算 3000 以内的质数,保存在数组中; set_map 函数随机生成一个 20 x 20 地图,存放在数组 int[3005][3005]
map 与 map_backup 中,-1为箱子(Box),-2为主角位置(Player),-3为箱子的目标位置(Target),质数为墙,合数为空地; print_gift 将地图的各个值输出,然后输入 wasd 移动,后面就是判断逻辑。
但是出题人写的逻辑与以前玩的推箱子逻辑有些差别(应该叫游戏bug特色),第一点比较容易看出来,就是 Player 不能移动到 Target 位置,否则这个 Target 就没了,不可能过关;另一点比较难发现,是在修改脚本已经解决了第一点后经常出现的,明明路径完全正确,却总在中途 !fail
了,调试了很久才发现,是 Player 踩在一个初始位置为 Box 的位置 (map_backup[pos] 为-1) 时,如果下一步推动箱子,脚下(原位置)就会凭空出现一个箱子,导致之后移动失败。
明白了这两点还不够,因为地图是随机生成的,有概率某个 Box 的生成位置卡在墙边导致无解,如下所示:
$
代表 Box ,右边的$
被卡住出不来,直接无解。
当然,有解的情况也不一定能解出来,这跟自动化推箱子脚本有关系,脚本写得差,跑脚本可能会很久,等跑出来早就 timeout 了。
写脚本这种事肯定是不会自己干的,直接 github 搜就完事了,找一个看起来实现比较简单的:
https://github.com/adhikary97/Sokoban-AI-Solver
还要阅读源码,将上面的两点限制加进去,也就是 Player 不能移动到 Target ,和Player 踩在一个初始为 Box 的位置时不能推动箱子。
脚本如下:
#!/usr/bin/python2
# coding=utf-8
from pwn import *
import traceback
import os
# context.log_level = 'debug'
local = True
'''
def fail():
print('fail.')
exit(-1)
map = [0] * 400
size = 20
# set map
count = 0
pos = 10 * size + 10
while count < 4:
move = 0
# w a s d to move
# move value in [1, -1, size, -size]
if map[pos + move] == -1:
if map[pos + move * 2] == -3:
count += 1
map[pos + move * 2] = -1
map[pos] = map_backup[pos]
else: # map[pos + move * 2] != -3 ->
if map[pos + move * 2] is not composite:
fail()
if map_backup[pos + move] == -3:
count--
map[pos + move * 2] = -1
map[pos] = map_backup[pos]
else: # map[pos + move] != -1 ->
if map[pos + move] is not composite:
fail()
map[pos] = random_composite()
map[pos + move] = -2
pos = pos + move
'''
'''
推箱子,地图规模 20 x 20 ,箱子数 4 ,每次随机生成
合数为路,质数为墙, -1 为箱子, -3 为箱子目标位置
'''
def run():
if local:
p = process('./sycgame')
# p = gdb.debug('./sycgame', 'b puts')
else:
p = remote('124.70.152.166', 1448)
is_prime = [1] * 3005
is_prime[1] = 0
i = 2
while i < 3000:
if is_prime[i]:
j = 2
while j * i < 3005:
is_prime[j * i] = 0
j = j + 1
i = i + 1
# print(is_prime)
size = 20
def save_map_to_path(s, path):
t = '#' * size + '\n'
for y in range(1, size - 1):
t += '#'
for x in range(1, size - 1):
i = y * size + x
if s[i] == -2:
t += '@' # player
elif s[i] == -1:
t += '$' # Box
elif s[i] == -3:
t += '.' # Destination
else:
assert s[i] > 0
if is_prime[s[i]]:
t += '#' # wall
else:
t += ' ' # path
t += '#\n'
t += '#' * size + '\n'
print(t)
with open(path, 'w') as f:
f.write('Level 1\n' + t)
for i in range(5):
print(i)
p.sendlineafter('Ready?(Y/n):', 'Y')
p.recvuntil('gift:\n')
s = [int(j) for j in p.recvline().strip('\n ').split(' ')]
# 试试 https://github.com/adhikary97/Sokoban-AI-Solver
# 修改了一些约束条件
'''
7 ->
box_origin = []
244 +
box_origin.append(coordinates())
# print(box_origin)
288 ->
if self.map[x1][y1].wall or self.map[x1][y1].target or ((p[0], p[1]) in box_origin and (x1, y1) in s.boxes()):
349 ->
if (x - 1, y) in box_pos and (x, y) not in box_origin:
352, 355, 358相似
363 ->
if not (matrix[x - 1][y].wall or matrix[x - 1][y].target) and (x - 1, y) not in box_pos and not matrix[x - 1][y].visited:
365, 367, 369相似
'''
save_map_to_path(s, './levels.txt')
path = os.popen('python3 ./Sokoban-AI-Solver-master/sokoban.py 1 a -t 1').readlines()[-1].strip('\n ').replace('d', 's').replace('r', 'd').replace('u', 'w').replace('l', 'a').replace(' ', '')
print(path)
if 'Timeaimit' in path:
p.close()
return -1
p.sendline(path)
if i == 4: break
line = p.recvline().strip('\n')
print(line)
if 'fail' in line:
p.close()
return -1
p.interactive()
return 0
while True:
print('==============================================================================')
if run() != -1:
break
能连续跑完5轮概率比较小,可能很长时间才能跑通一次。而且可能由于输出缓冲的问题,我跑通了很多次都没拿到 flag ,本地完全没问题(被这个问题卡了很久),后来跟题目负责人沟通,他那儿跑出 flag 后发给我。
flag: SCTF{push_box_goooood_game}
SycOS
题目给了 risc-v 的 kernel, fs.img, sctf ,表明就是 risc-v 逆向, README.md
贴心的说用 qemu ,然后给个链接自己搜去。嗯…… qemu ,还是算了吧,懒得配环境,这不是给了程序吗,就硬逆。手上还没有能逆 risc-v 的 ida ,只好祭出去年安装后至今用过两次的 Ghidra 了。
sctf 拖入 Ghidra ,进入 entry ,一顿分析:
ulonglong rand(void)
{
seed = seed * 1103515245 + 12345;
return (seed << 33) >> 49;
}
void gift(void)
{
ulonglong uVar1;
ulonglong uVar2;
ulonglong uVar3;
ulonglong uVar4;
printf("gift:\n");
seed = 0x1447aabb;
uVar1 = rand();
uVar2 = rand();
uVar3 = rand();
uVar4 = rand();
printf("\t1.%x %x %x %x\n",uVar1,uVar2,uVar3,uVar4);
printf("\t2.Fake Tea\n");
printf("\t3.Based on xv6 2020 labs & util branch.\n");
return;
}
void replace_mem0_index_with_mem1_15_sub_index(ulonglong index)
{
longlong i;
longlong lVar1;
byte buffer [256];
buffer._0_8_ = 0;
memset(buffer + 8,0,248);
lVar1 = (longlong)((int)index << 8);
memcpy(buffer,mem0 + lVar1,0x100);
i = (longlong)((15 - (int)index) * 0x100);
memcpy(mem0 + lVar1,mem1 + i,0x100);
memcpy(mem1 + i,buffer,0x100);
return;
}
void tea_decrypt_mem1(void)
{
longlong i;
int sum;
uint *mem1_ptr;
uint v0;
uint v1;
i = 0;
do {
mem1_ptr = (uint *)(mem1 + i);
v0 = *mem1_ptr;
v1 = mem1_ptr[1];
sum = 0;
do {
v1 = v1 - (v0 * 16 + k2 ^ (v0 >> 5) + k3 ^ v0 + sum);
v0 = v0 - (v1 + sum ^ v1 * 0x10 + k0 ^ (v1 >> 5) + k1);
sum = sum + -0x61c88647;
} while (sum != -0xe443238);
*mem1_ptr = v0;
mem1_ptr[1] = v1;
i = i + 8;
} while (i != 0x1000);
return;
}
void tea_encrypt_mem0(void)
{
longlong i;
int sum;
uint *mem0_ptr;
uint v1;
uint v0;
i = 0;
do {
mem0_ptr = (uint *)(mem0 + i);
v0 = *mem0_ptr;
v1 = mem0_ptr[1];
sum = 0;
do {
sum = sum + -0x61c88647;
v0 = (v1 * 16 + k0 ^ (v1 >> 5) + k1 ^ sum + v1) + v0;
v1 = v1 + (v0 + sum ^ v0 * 16 + k2 ^ (v0 >> 5) + k3);
} while (sum != -0x1c886470);
*mem0_ptr = v0;
mem0_ptr[1] = v1;
i = i + 8;
} while (i != 0x1000);
return;
}
void change_mem0_mem1(void)
{
ulonglong i;
i = 0;
do {
tea_encrypt_mem0();
tea_decrypt_mem1();
replace_mem0_index_with_mem1_15_sub_index(i);
FUN_0000091a(mem0,mem1);
i = SEXT48((int)i + 1);
} while (i != 16);
return;
}
void set_mem_with_input(char *input)
{
longlong j;
ulonglong value;
longlong end;
longlong input_index;
longlong start;
mem0 = (char *)malloc(0x1000);
mem1 = (char *)malloc(0x1000);
memset(mem0,0,0x1000);
memset(mem1,0,0x1000);
end = 128;
start = 0;
input_index = 0;
do {
seed = (longlong)(int)((uint)(byte)input[input_index] + (int)input_index) & 0xffffffff;
j = start;
do {
value = rand();
mem0[j] = (char)value;
j = j + 1;
} while (j != end);
input_index = input_index + 1;
start = start + 0x80;
end = end + 0x80;
} while (input_index != 32);
end = 0x80;
start = 0;
input_index = 0;
do {
seed = (longlong)(int)((uint)(byte)input[input_index + 0x20] + (int)input_index) & 0xffffffff;
j = start;
do {
value = rand();
mem1[j] = (char)value;
j = j + 1;
} while (j != end);
input_index = input_index + 1;
start = start + 0x80;
end = end + 0x80;
} while (input_index != 0x20);
return;
}
void entry(void)
{
int input_len;
ulonglong uVar1;
char input [256];
printf("Welcome SCTF2021! The new xv6 lab designed by P****OS\ncan you solve this ?\ninput:");
gift();
read_str(input,256);
input_len = strlen(input);
input[(longlong)(input_len + -1) & 0xffffffff] = '\0';
input_len = strlen(input);
if (input_len != 64) {
printf("fail!\n");
/* WARNING: Subroutine does not return */
exit(0);
}
set_mem_with_input(input);
change_mem0_mem1();
uVar1 = memcmp(mem0,&DAT_00001ec8,0x1000);
if ((uVar1 == 0) && (uVar1 = memcmp(mem1,&DAT_00000ec8,0x1000), uVar1 == 0)) {
printf("yes,you are right.!\n");
/* WARNING: Subroutine does not return */
exit(0);
}
printf("fail! Try!");
/* WARNING: Subroutine does not return */
exit(0);
}
gift 函数输出几个伪随机数,都是固定值;还有 Fake Tea ,最初不知道是什么,等分析完后才知道说的是魔改 tea 加密。
大部分都恢复出来了,一些函数(如 exit, malloc )跟入是 ecall 系统调用,是根据逻辑猜测得到的。只剩下一个函数, FUN_0000091a ,跟入是序号 22 的系统调用,不知道做什么。
sctf 就这些,然后要找系统调用,又得分析 kernel 。同样拖入 Ghidra ,但是不好找关键位置,这时候得祭出 strings 大法:strings ./kernel
,找到关键点:
...
argraw
%d %s: unknown sys call %d
bcache
buffer
bget: no buffers
bwrite
brelse
freeing free block
balloc: out of blocks
bmap: out of range
iget: no inodes
invalid file system
icache
inode
ialloc: no inodes
...
有个%d %s: unknown sys call %d
,正是系统调用的报错信息, Ghidra 里找到 0x800083f0 ,查看引用,找到关键位置:
void FUN_80002b0c(void)
{
int iVar1;
longlong lVar2;
undefined8 uVar3;
longlong lVar4;
lVar2 = FUN_800019de();
lVar4 = *(longlong *)(lVar2 + 0x58);
iVar1 = (int)*(undefined8 *)(lVar4 + 0xa8);
if ((iVar1 - 1U < 0x16) && (*(code **)(&DAT_80008428 + (longlong)iVar1 * 8) != (code *)0x0)) {
uVar3 = (**(code **)(&DAT_80008428 + (longlong)iVar1 * 8))();
*(undefined8 *)(lVar4 + 0x70) = uVar3;
return;
}
printk("%d %s: unknown sys call %d\n");
*(undefined8 *)(*(longlong *)(lVar2 + 0x58) + 0x70) = 0xffffffffffffffff;
return;
}
所以系统调用表在 0x80008428 ,需要的22号调用就是 0x80008428 + 22 * 8 = 0x800084d8 ,找到函数 FUN_800060c6
undefined8 FUN_800060c6(void)
{
undefined8 uVar1;
undefined8 *puVar2;
undefined8 *puVar3;
ulonglong uVar4;
longlong lVar5;
char *mem1;
char *mem0;
undefined8 *puStack56;
FUN_80000c10(&DAT_800250a8);
uVar4 = (ulonglong)DAT_80025020;
if (((*(ushort *)(DAT_80025010 + 2) ^ DAT_80025020) & 7) != 0) {
do {
lVar5 = (longlong)*(int *)(uVar4 * 8 + DAT_80025010 + 4);
if ((&DAT_80023030)[(lVar5 + 0x200) * 0x10] != '\0') {
panic("virtio_disk_intr status");
puStack56 = &DAT_80025000;
lVar5 = get_ecall_arg_to_memory64(0,(longlong *)&mem0);
uVar1 = 0xffffffffffffffff;
if (-1 < lVar5) {
lVar5 = get_ecall_arg_to_memory64(1,(longlong *)&mem1);
uVar1 = 0xffffffffffffffff;
if (-1 < lVar5) {
lVar5 = FUN_800019de();
puVar2 = (undefined8 *)FUN_80000ff8(*(undefined8 *)(lVar5 + 0x50),mem0,0);
lVar5 = FUN_800019de();
puVar3 = (undefined8 *)FUN_80000ff8(*(undefined8 *)(lVar5 + 0x50),mem1,0);
uVar1 = *puVar2;
*puVar2 = *puVar3;
*puVar3 = uVar1;
sfence.vma(0,0);
uVar1 = 1;
}
}
return uVar1;
}
lVar5 = (lVar5 + 0x200) * 0x10;
*(undefined4 *)(*(longlong *)(&DAT_80023028 + lVar5) + 4) = 0;
FUN_80002370(*(undefined8 *)(&DAT_80023028 + lVar5));
uVar4 = (longlong)(int)(DAT_80025020 + 1) & 7;
DAT_80025020 = (ushort)uVar4;
} while (((ulonglong)*(ushort *)(DAT_80025010 + 2) & 7) != uVar4);
}
_DAT_10001064 = _DAT_10001060 & 3;
uVar1 = FUN_80000cc4(&DAT_800250a8);
return uVar1;
}
关键在于中间的两次 FUN_80000ff8 ,再跟入就什么都看不懂了,不过后面是一个交换,猜测下应该是将 mem0 和 mem1 内存交换了,尝试了下还真是。
编写逆向程序:
// clear && gcc ./main.c && ./a.out
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <assert.h>
char* mem0;
char* mem1;
unsigned int k0 = 0x11222233, k1 = 0xAABBCCDD, k2 = 0x1A2B3C4D, k3 = 0xCC1122AA;
typedef unsigned long long ulonglong;
typedef long long longlong;
typedef char byte;
typedef unsigned int uint;
void replace_mem0_index_with_mem1_15_sub_index_inv(ulonglong index) {
longlong i;
longlong lVar1;
byte buffer [256];
memset(buffer, 0, 256);
lVar1 = index << 8;
i = (15 - index) * 0x100;
memcpy(buffer,mem1 + i,0x100);
memcpy(mem1 + i,mem0 + lVar1,0x100);
memcpy(mem0 + lVar1,buffer,0x100);
return;
}
void tea_decrypt_mem1_inv(void) {
longlong i;
int sum;
uint *mem1_ptr;
uint v0;
uint v1;
i = 0;
do {
mem1_ptr = (uint *)(mem1 + i);
v0 = *mem1_ptr;
v1 = mem1_ptr[1];
sum = -0xe443238;
do {
sum = sum - -0x61c88647;
v0 += (v1 + sum) ^ ((v1 << 4) + k0) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ ((v0 >> 5) + k3) ^ (v0 + sum);
} while (sum != 0);
*mem1_ptr = v0;
mem1_ptr[1] = v1;
i = i + 8;
} while (i != 0x1000);
return;
}
void tea_encrypt_mem0_inv(void) {
longlong i;
int sum;
uint *mem0_ptr;
uint v1;
uint v0;
i = 0;
do {
mem0_ptr = (uint *)(mem0 + i);
v0 = *mem0_ptr;
v1 = mem0_ptr[1];
sum = -0x1c886470;
do {
v1 -= (v0 + sum) ^ ((v0 << 4) + k2) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ ((v1 >> 5) + k1) ^ (sum + v1);
sum = sum - -0x61c88647;
} while (sum != 0);
*mem0_ptr = v0;
mem0_ptr[1] = v1;
i = i + 8;
} while (i != 0x1000);
return;
}
ulonglong seed;
void _srand(ulonglong _seed) {
seed = _seed;
}
uint _rand() {
seed = seed * 1103515245 + 12345;
return (seed << 33) >> 49;
}
int main() {
struct stat sb;
mem0 = (char*) malloc(0x1000);
mem1 = (char*) malloc(0x1000);
if (mem0 == NULL || mem1 == NULL) {
perror("malloc");
exit(-1);
}
/*
char input[65] = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
puts(input);
for (int i = 0; i < 32; i++) {
srand(input[i] + i);
for (int j = 128 * i; j < 128 * i + 128; j++) {
mem0[j] = rand();
}
}
for (int i = 0; i < 32; i++) {
srand(input[i + 32] + i);
for (int j = 128 * i; j < 128 * i + 128; j++) {
mem1[j] = rand();
}
}
for (int i = 0; i < 16; i++) {
tea_encrypt_mem0();
tea_decrypt_mem1();
replace_mem0_index_with_mem1_15_sub_index();
}
/**/
int fd = open("./sctf", O_RDONLY);
if (fd < 0) {
perror("open");
exit(-1);
}
if (fstat(fd, &sb) == -1) {
perror("fstat");
exit(-1);
}
unsigned char* mapped = (char*) mmap(0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (mapped == (unsigned char*) -1) {
perror("mmap");
exit(-1);
}
memcpy(mem0, mapped + 0x1F78, 0x1000);
memcpy(mem1, mapped + 0xF78, 0x1000);
assert(mem0[0] == 0x29 && mem0[1] == 0x3A);
if (munmap(mapped, sb.st_size) == -1) {
perror("munmap");
exit(-1);
}
for (int i = 15; i >= 0; i--) {
// FUN_0000091a_inv(mem0, mem1);
char* temp = mem0;
mem0 = mem1;
mem1 = temp;
/**/
replace_mem0_index_with_mem1_15_sub_index_inv(i);
tea_decrypt_mem1_inv();
tea_encrypt_mem0_inv();
}
/*
for (int i = 0; i < 32; i++) {
printf("0x%02x, ", mem0[i] & 0xff);
if ((i + 1) % 16 == 0) putchar('\n');
}
putchar('\n');
/*
_srand('S');
for (int i = 0; i < 32; i++) {
printf("0x%02x, ", _rand() & 0xff);
if ((i + 1) % 16 == 0) putchar('\n');
}
putchar('\n');
/**/
for (int i = 0; i < 32; i++) {
for (int j = 0; j < 128; j++) {
_srand(j + i);
for (int k = 0; k < 128; k++)
if ((_rand() & 0xff) != (mem0[k + 128 * i] & 0xff))
goto continue_search;
putchar(j);
goto scan_next;
continue_search:
;
}
printf("\nNot Found.\n");
goto finish;
scan_next:
;
}
for (int i = 0; i < 32; i++) {
for (int j = 0; j < 128; j++) {
_srand(j + i);
for (int k = 0; k < 128; k++)
if ((_rand() & 0xff) != (mem1[k + 128 * i] & 0xff))
goto continue_search1;
putchar(j);
goto scan_next1;
continue_search1:
;
}
printf("\nNot Found.\n");
goto finish;
scan_next1:
;
}
finish:
putchar('\n');
/**/
free(mem0);
free(mem1);
return 0;
}
flag: SCTF{xv6_nice_lab_6666_YOU_ARE_6666666_OrzzzzzzzzzzzzzrO_wowowo}
godness dance
跟其它几道题目比起来,这道题就像是来搞笑的。。。
main 前半部分:
__printf_chk(1, "Input:");
do
{
v5 = v4++;
__isoc99_scanf("%c", v5);
}
while ( v4 != v14 );
v6 = input;
do
{
v7 = *v6++;
++count_src[v7 - 'a'];
}
while ( v6 != v14 );
for ( i = 0LL; i != 26; ++i )
{
if ( count_src[i] != count_dst[i] )
CountWrong();
}
输入 28 个字符 [a-z] ,统计字符个数,除了 i 和 u 是 2 个,其余都是 1 个。
main 后半部分:
sub_1400(&v12, 28u, 200);
for ( j = 1LL; j != 29; ++j )
{
if ( count_sort_result_index[j] != dword_4020[j] )
Wrong();
}
sub_1400 前部分:
v3 = _200;
v4 = _28;
if ( _200 >= 0 )
memset(input_count_sum, 0, 4LL * _200 + 4);
if ( (int)_28 <= 0 )
{
if ( v3 <= 0 )
return;
LABEL_7:
v7 = input_count_sum[0];
v8 = 1LL;
do
{
v7 += input_count_sum[v8];
input_count_sum[v8++] = v7;
}
while ( v3 >= (int)v8 );
if ( (int)_28 <= 0 )
return;
goto LABEL_10;
}
v5 = 1LL;
do
{
v6 = input_sub_1[v5];
input_buffer[v5++] = v6;
++input_count_sum[v6];
}
while ( _28 - 1 + 2LL != v5 );
if ( v3 > 0 )
goto LABEL_7;
LABEL_10:
v9 = (int)_28;
do
{
v10 = input_buffer[v9];
v11 = input_count_sum[v10];
count_sort_result_index[v11] = v9--;
input_count_sum[v10] = v11 - 1;
}
while ( (int)v9 > 0 );
学过算法的话跟着流程走一遍就知道是计数排序,计算的 count_sort_result_index 是排好序后数在原数组中的下标。而且动调一下,发现后面部分没有修改 count_sort_result_index ,很简单:
index = [2, 26, 17, 28, 24, 11, 21, 10, 16, 20, 19, 18, 3, 8, 6, 12, 9, 14, 13, 22, 4, 27, 15, 23, 1, 25, 7, 5]
index = [i - 1 for i in index]
s = 'abcdefghiijklmnopqrstuuvwxyz'
t = [0] * 28
for i in range(len(index)):
t[index[i]] = s[i]
print(''.join(t))
# waltznymphforquickjigsvexbud
CplusExceptionEncrypt
这道题是利用 C++ 的异常处理控制执行流程,问题在于 F5 后的代码只会解析到 throw 抛异常,后面的处理看不到。
printf("---------------------Welcome_to_SCTF_2021---------------------\n");
printf("Please input your flag: \n");
scanf("%s", data);
length = strlen(data);
if ( length == 32 )
{
w = 0;
v0 = *(_DWORD *)data;
v1 = *(_DWORD *)&data[4];
v2 = *(_DWORD *)&data[8];
v3 = *(_DWORD *)&data[12];
sum1 = 0;
sum2 = 0;
rbx3 = _cxa_allocate_exception(0x20ui64);
std::allocator<char>::allocator(&v8);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(rbx3, "init_part", &v8);
std::allocator<char>::~allocator(&v8);
_cxa_throw(
rbx3,
(struct type_info *)&`typeinfo for'std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>,
refptr__ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev);
}
printf("length error!\n");
这个时候就想怎么能将整个流程恢复出来,让 F5 能正常解析,一个很自然的想法就是修改指令,修改 throw 为 jmp ,就找 throw 对应的 catch 段,这里的很容易找,在 ida Graph View 里可以清晰看到:
(也有可能看到的不是这样的,而是 call cxa_throw 这条指令与上面的块合在一起,并且没有指向 catch 块的箭头,暂不知道具体原因,可能是 ida 的问题,关闭 ida 保存到 i64 后重新打开就没了。这种情况下可以删除 i64 或者重命名,让ida打开时重新开始解析 exe ,这样就能看到了)
catch 段的 rdx 应该是表明由哪个 catch 块处理,这里只有一个,就应该是直接跳转到 loc_402A5F 进行处理, patch 为 jmp :
再 F5 ,这里的处理解析出来了:
printf("---------------------Welcome_to_SCTF_2021---------------------\n");
printf("Please input your flag: \n");
scanf("%s", data);
length = strlen(data);
if ( length == 32 )
{
w = 0;
inp[0] = *(_DWORD *)data;
inp[1] = *(_DWORD *)&data[4];
inp[2] = *(_DWORD *)&data[8];
inp[3] = *(_DWORD *)&data[12];
v0 = *(_DWORD *)data;
v1 = *(_DWORD *)&data[4];
v2 = *(_DWORD *)&data[8];
v3 = *(_DWORD *)&data[12];
sum1 = 0;
sum2 = 0;
*(_QWORD *)dst = 0i64;
*(_QWORD *)&dst[2] = 0i64;
*(_QWORD *)out1 = 0i64;
*(_QWORD *)&out1[8] = 0i64;
rbx3 = _cxa_allocate_exception(0x20ui64);
std::allocator<char>::allocator(&v18);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(rbx3, "init_part", &v18);
std::allocator<char>::~allocator(&v18);
v9 = _cxa_get_exception_ptr(rbx3);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&init, v9);
_cxa_begin_catch(rbx3);
qmemcpy(key, "Welcome_to_sctf!", sizeof(key));
k0_0 = *(_DWORD *)key;
k1_0 = *(_DWORD *)&key[4];
k2_0 = *(_DWORD *)&key[8];
k3_0 = *(_DWORD *)&key[12];
/*
set cmp_arr
/**/
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&init);
_cxa_end_catch();
for ( i = 0; i <= 31; ++i )
{
cnt = 0;
srand(0x53435446u);
do
{
control1 = rand();
switch ( control1 )
{
case 0x5208:
v6 = (struct_of_step3 *)_cxa_allocate_exception(1ui64);
struct_of_step3::struct_of_step3(v6);
_cxa_throw(v6, (struct type_info *)&`typeinfo for'struct_of_step3, 0i64);
case 0x6591:
v4 = (struct_of_step1 *)_cxa_allocate_exception(1ui64);
struct_of_step1::struct_of_step1(v4);
_cxa_throw(v4, (struct type_info *)&`typeinfo for'struct_of_step1, 0i64);
case 0x10A9:
v5 = (struct_of_step2 *)_cxa_allocate_exception(1ui64);
struct_of_step2::struct_of_step2(v5);
_cxa_throw(v5, (struct type_info *)&`typeinfo for'struct_of_step2, 0i64);
}
++cnt;
}
while ( cnt != 3 );
}
v7 = (last_struct *)_cxa_allocate_exception(1ui64);
text_84(v7);
_cxa_throw(v7, (struct type_info *)&`typeinfo for'last_struct, 0i64);
}
printf("length error!\n");
return 0;
}
效果不错,美中不足的就是前面还留下了一些 exception 相关的函数,都是没用的。这些并不影响分析流程,也就不管了。
继续分析,中间有 srand ,那么 rand 生成的数是完全可预测的,写个程序跑一下就得到了(动调也可以),然后又要处理三个 throw , Graph View 里看,很不幸,第一个 case 0x5208: 的 throw 块就没有了指向 catch 块的箭头,这就得手动找一下。
这里有一个 starts at 40273D ,也就是说 try 块是从 0x40273D 开始的,而相应的 catch 块也会有这样的标识,鼠标点下 40273D 使其高亮,然后拖动图在周围找一下高亮,定位到 catch 块:
可以看到这个 catch 块有多个标识: owned by 40273D ,说明有多个 catch 块,而继续定位另两个 case 的 throw 指令,发现指向这块的 throw 正是 case 0x6591 的 throw 块,或许第一个 case 的 throw 没有箭头指向 catch 就是因为有一个 throw 占用了这个箭头。。。
多个 catch 的分发器,由 rdx 的值决定分发给哪个 catch 块,至于 rdx 的值与 catch 块的对应关系,这个暂时还不知道,可能是程序启动时由某个函数注册的吧。要确定上面三个 throw 到这里 catch 块的 rdx 的值,通过动调就可以。
后面的大部分也还是上面的异常处理隐藏流程, patch 为 jmp 就都能恢复了,整体恢复出来如下:
printf("---------------------Welcome_to_SCTF_2021---------------------\n");
printf("Please input your flag: \n");
scanf("%s", data);
length = strlen(data);
if ( length == 32 )
{
for ( w = 0; w <= 1; ++w )
{
if ( !w )
{
inp[0] = *(_DWORD *)data;
inp[1] = *(_DWORD *)&data[4];
inp[2] = *(_DWORD *)&data[8];
inp[3] = *(_DWORD *)&data[12];
}
if ( w == 1 )
{
inp[0] = *(_DWORD *)&data[16];
inp[1] = *(_DWORD *)&data[20];
inp[2] = *(_DWORD *)&data[24];
inp[3] = *(_DWORD *)&data[28];
}
v0 = inp[0];
v1 = inp[1];
v2 = inp[2];
v3 = inp[3];
sum1 = 0;
sum2 = 0;
*(_QWORD *)dst = 0i64;
*(_QWORD *)&dst[2] = 0i64;
*(_QWORD *)out1 = 0i64;
*(_QWORD *)&out1[8] = 0i64;
rbx9 = _cxa_allocate_exception(0x20ui64);
std::allocator<char>::allocator(&v62);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
rbx9,
"init_part",
&v62);
std::allocator<char>::~allocator(&v62);
v13 = _cxa_get_exception_ptr(rbx9);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&init, v13);
_cxa_begin_catch(rbx9);
qmemcpy(key, "Welcome_to_sctf!", sizeof(key));
k0_0 = *(_DWORD *)key;
k1_0 = *(_DWORD *)&key[4];
k2_0 = *(_DWORD *)&key[8];
k3_0 = *(_DWORD *)&key[12];
/*
set cmp_arr
/**/
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&init);
_cxa_end_catch();
for ( i = 0; i <= 31; ++i )
{
cnt = 0;
srand(0x53435446u);
do
{
control1 = rand(); // 0x6591
// 0x10A9
// 0x5208
// 0x4be2
switch ( control1 )
{
case 0x5208: // 2
v6 = (struct_of_step3 *)_cxa_allocate_exception(1ui64);
struct_of_step3::struct_of_step3(v6);
_cxa_begin_catch(v32);
v33 = add(sum1, i);
v34 = shr_5(v0);
v35 = add(v34, k1_0);
v36 = add(v0, sum1);
v37 = shl_4(v0);
v38 = add(v37, k0_0);
v39 = xor(v38, v36);
v40 = xor(v39, v35);
v1 += xor(v40, v33);
v41 = add(sum2, i);
v42 = shr_5(v2);
v43 = add(v42, k1_0);
v44 = add(v2, sum1);
v45 = shl_4(v2);
v46 = add(v45, k0_0);
v47 = xor(v46, v44);
v48 = xor(v47, v43);
v3 += xor(v48, v41);
_cxa_end_catch();
break;
case 0x6591: // 0
v4 = (struct_of_step1 *)_cxa_allocate_exception(1ui64);
struct_of_step1::struct_of_step1(v4);
_cxa_begin_catch(v14);
sum1 = add(sum1, delta);
sum2 = add(sum2, delta);
_cxa_end_catch();
break;
case 0x10A9: // 1
v5 = (struct_of_step2 *)_cxa_allocate_exception(1ui64);
struct_of_step2::struct_of_step2(v5);
_cxa_begin_catch(v15);
v16 = add(sum1, i);
v17 = shr_5(v1);
v18 = add(v17, k3_0);
v19 = add(v1, sum1);
v20 = shl_4(v1);
v21 = add(v20, k2_0);
v22 = xor(v21, v19);
v23 = xor(v22, v18);
v0 += xor(v23, v16);
v24 = add(sum2, i);
v25 = shr_5(v3);
v26 = add(v25, k3_0);
v27 = add(v3, sum2);
v28 = shl_4(v3);
v29 = add(v28, k2_0);
v30 = xor(v29, v27);
v31 = xor(v30, v26);
v2 += xor(v31, v24);
_cxa_end_catch();
break;
}
++cnt;
}
while ( cnt != 3 );
}
v7 = (last_struct *)_cxa_allocate_exception(1ui64);
text_84(v7);
_cxa_begin_catch(v49);
dst[0] = v0 ^ HIBYTE(delta);
dst[1] = v1 ^ BYTE2(delta);
dst[2] = v2 ^ BYTE1(delta);
dst[3] = v3 ^ (unsigned __int8)delta;
_cxa_end_catch();
m = 0;
for ( j = 0; j <= 3; ++j )
{
for ( k = 0; k <= 3; ++k )
out1[m++] = *((_BYTE *)&dst[j] + k);
}
x = 0;
srand(0x53435446u);
while ( x != 2 )
{
v8 = rand(); // 0x6591
// 0x10A9
// 0x5208
// 0x4be2
if ( v8 == 0x10A9 ) // 1
{
v11 = (enc_next_struct *)_cxa_allocate_exception(0x18ui64);
temp_4 = *enc_next_struct::enc_next_struct(v11, (uint8_t *)&init, out1, ciphertext);
_cxa_begin_catch(v11);
enc_next(temp_4.ourroundkeys, temp_4.ourplaintext, temp_4.ourciphertext);
_cxa_end_catch();
}
else if ( v8 == 0x6591 ) // 0
{
v9 = (enc_next_ready_struct *)_cxa_allocate_exception(0x10ui64);
v10 = enc_next_ready_struct::enc_next_ready_struct(v9, key, (uint8_t *)&init);
v50 = v10->ourroundkeys;
temp_3.ourciphertext = v10->ourciphertext;
temp_3.ourroundkeys = v50;
_cxa_begin_catch(v9);
enc_next_ready(temp_3.ourciphertext, temp_3.ourroundkeys);
_cxa_end_catch();
}
++x;
}
if ( !w )
{
for ( i_0 = 0; i_0 <= 15; ++i_0 )
encdata[i_0] = ciphertext[i_0];
}
if ( w == 1 )
{
for ( i_1 = 0; i_1 <= 15; ++i_1 )
encdata[i_1 + 16] = ciphertext[i_1];
}
}
for ( j_0 = 0; j_0 <= 31; ++j_0 )
{
if ( encdata[j_0] != cmp_arr[j_0] )
{
printf("Sorry!Your flag is wrong!!!!\n");
exit(0);
}
}
printf("\ncongratulations!!!!your flag is \nSCTF{%s}", data);
}
else
{
printf("length error!\n");
}
return 0;
enc_next_ready 与 enc_next 如下:
void __cdecl enc_next(uint8_t *roundkeys, uint8_t *plaintext, uint8_t *ciphertext)
{
void *v3; // rbx
_DWORD *v4; // rax
uint8_t *v5; // rax
uint8_t *v6; // rax
void *v7; // rax
uint8_t *v8; // rax
std::__cxx11::string temp_2; // [rsp+20h] [rbp-60h] BYREF
uint8_t tmp[16]; // [rsp+40h] [rbp-40h] BYREF
char v11; // [rsp+5Eh] [rbp-22h] BYREF
uint8_t t; // [rsp+5Fh] [rbp-21h]
double temp_1; // [rsp+60h] [rbp-20h]
int temp_0; // [rsp+6Ch] [rbp-14h]
char temp; // [rsp+73h] [rbp-Dh]
int a; // [rsp+74h] [rbp-Ch]
int cnt; // [rsp+78h] [rbp-8h]
uint8_t j; // [rsp+7Eh] [rbp-2h]
uint8_t i; // [rsp+7Fh] [rbp-1h]
v4 = _cxa_allocate_exception(4ui64);
*v4 = 1;
a = *(_DWORD *)_cxa_begin_catch(v4);
for ( i = 0; i <= 15u; ++i )
{
v6 = roundkeys++;
ciphertext[i] = *v6 ^ plaintext[i] ^ 0x66;
}
_cxa_end_catch();
j = 1;
LABEL_2:
if ( j <= 9u )
{
for ( cnt = 0; ; ++cnt )
{
if ( cnt == 4 )
{
++j;
goto LABEL_2;
}
if ( cnt == 1 )
{
*(_DWORD *)_cxa_allocate_exception(4ui64) = 666;
temp_0 = *(_DWORD *)_cxa_begin_catch(v3);
inv_shift_rows(tmp);
_cxa_end_catch();
continue;
}
if ( cnt > 1 )
{
if ( cnt == 2 )
{
*(_QWORD *)_cxa_allocate_exception(8ui64) = 0x4050AA3D70A3D70Ai64;
temp_1 = *(double *)_cxa_begin_catch(v3);
for ( i = 0; i <= 0xFu; i += 4 )
{
t = tmp[i + 2] ^ tmp[i + 1] ^ tmp[i] ^ tmp[i + 3];
ciphertext[i] = t ^ tmp[i] ^ mul2(tmp[i] ^ tmp[i + 1]);
ciphertext[i + 1] = t ^ tmp[i + 1] ^ mul2(tmp[i + 1] ^ tmp[i + 2]);
ciphertext[i + 2] = t ^ tmp[i + 2] ^ mul2(tmp[i + 2] ^ tmp[i + 3]);
ciphertext[i + 3] = t ^ tmp[i + 3] ^ mul2(tmp[i + 3] ^ tmp[i]);
}
goto LABEL_29;
}
if ( cnt == 3 )
{
v3 = _cxa_allocate_exception(0x20ui64);
std::allocator<char>::allocator(&v11);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
v3,
"66666\n",
&v11);
std::allocator<char>::~allocator(&v11);
v7 = _cxa_get_exception_ptr(v3);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&temp_2, v7);
_cxa_begin_catch(v3);
for ( i = 0; i <= 15u; ++i )
{
v8 = roundkeys++;
ciphertext[i] ^= *v8;
}
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&temp_2);
_cxa_end_catch();
}
}
else if ( !cnt )
{
*(_BYTE *)_cxa_allocate_exception(1ui64) = '6';
temp = *(_BYTE *)_cxa_begin_catch(v3);
for ( i = 0; i <= 15u; ++i )
tmp[i] = INV_S[ciphertext[i]];
LABEL_29:
_cxa_end_catch();
continue;
}
}
}
for ( i = 0; i <= 15u; ++i )
ciphertext[i] = S[ciphertext[i]];
shift_rows(ciphertext);
for ( i = 0; i <= 0xFu; ++i )
{
v5 = roundkeys++;
ciphertext[i] ^= *v5;
}
}
void __cdecl enc_next_ready(uint8_t *key, uint8_t *roundkeys)
{
uint8_t *v2; // rdx
uint8_t *v3; // rax
char *v4; // rax
char v5; // r8
char v6; // cl
uint8_t *v7; // rax
uint8_t temp_1; // [rsp+5h] [rbp-1Bh]
uint8_t temp_2; // [rsp+6h] [rbp-1Ah]
uint8_t temp_3; // [rsp+7h] [rbp-19h]
uint8_t i; // [rsp+17h] [rbp-9h]
uint8_t ia; // [rsp+17h] [rbp-9h]
uint8_t *last4bytes; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15u; ++i )
{
v2 = key++;
v3 = roundkeys++;
*v3 = *v2;
}
last4bytes = roundkeys - 4;
for ( ia = 0; ia <= 9u; ++ia )
{
temp_3 = S[*last4bytes];
temp_1 = S[last4bytes[2]];
temp_2 = S[last4bytes[3]];
*roundkeys = *(roundkeys - 16) ^ S[last4bytes[1]] ^ RC[ia];
roundkeys[1] = *(roundkeys - 15) ^ temp_1;
roundkeys[2] = *(roundkeys - 14) ^ temp_2;
roundkeys[3] = *(roundkeys - 13) ^ temp_3;
roundkeys[4] = *(roundkeys - 12) ^ last4bytes[4];
roundkeys[5] = *(roundkeys - 11) ^ last4bytes[5];
roundkeys[6] = *(roundkeys - 10) ^ last4bytes[6];
roundkeys[7] = *(roundkeys - 9) ^ last4bytes[7];
roundkeys[8] = *(roundkeys - 8) ^ last4bytes[8];
roundkeys[9] = *(roundkeys - 7) ^ last4bytes[9];
roundkeys[10] = *(roundkeys - 6) ^ last4bytes[10];
roundkeys[11] = *(roundkeys - 5) ^ last4bytes[11];
roundkeys[12] = *(roundkeys - 4) ^ last4bytes[12];
roundkeys[13] = *(roundkeys - 3) ^ last4bytes[13];
roundkeys[14] = *(roundkeys - 2) ^ last4bytes[14];
v4 = (char *)(last4bytes + 15);
last4bytes += 16;
v5 = *v4;
v6 = *(roundkeys - 1);
v7 = roundkeys + 15;
roundkeys += 16;
*v7 = v6 ^ v5;
}
}
看得出来是 TEA 加密后异或 delta 再 AES 加密,调试了下发现后面的 AES 与标准 AES 不太一样,也只好手动写逆了。
逆向程序:
// clear && gcc -g ./main.c && ./a.out
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef unsigned char uint8_t;
unsigned char S[256] = { 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 };
unsigned char INV_S[256] = {82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, 124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, 250, 195, 78, 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, 114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, 108, 112, 72, 80, 253, 237, 185, 218, 94, 21, 70, 87, 167, 141, 157, 132, 144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69, 6, 208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, 58, 145, 17, 65, 79, 103, 220, 234, 151, 242, 207, 206, 240, 180, 230, 115, 150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, 252, 86, 62, 75, 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, 160, 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12, 125};
unsigned char RC[10] = {1, 2, 4, 8, 16, 32, 64, 128, 27, 54};
void shift_rows(uint8_t *state) {
uint8_t temp; // [rsp+Fh] [rbp-1h]
uint8_t tempa; // [rsp+Fh] [rbp-1h]
uint8_t tempb; // [rsp+Fh] [rbp-1h]
uint8_t tempc; // [rsp+Fh] [rbp-1h]
temp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = temp;
tempa = state[2];
state[2] = state[10];
state[10] = tempa;
tempb = state[6];
state[6] = state[14];
state[14] = tempb;
tempc = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = tempc;
}
void inv_shift_rows(uint8_t *state) {
uint8_t temp; // [rsp+Fh] [rbp-1h]
uint8_t tempa; // [rsp+Fh] [rbp-1h]
uint8_t tempb; // [rsp+Fh] [rbp-1h]
uint8_t tempc; // [rsp+Fh] [rbp-1h]
temp = state[13];
state[13] = state[9];
state[9] = state[5];
state[5] = state[1];
state[1] = temp;
tempa = state[14];
state[14] = state[6];
state[6] = tempa;
tempb = state[10];
state[10] = state[2];
state[2] = tempb;
tempc = state[3];
state[3] = state[7];
state[7] = state[11];
state[11] = state[15];
state[15] = tempc;
}
uint8_t mul2(uint8_t a) {
uint8_t result; // al
if ( (a & 0x80u) == 0 )
result = 2 * a;
else
result = (2 * a) ^ 0x1B;
return result;
}
uint8_t muln(uint8_t a, uint8_t n) {
uint8_t result = 0;
while (n) {
if (n & 1) result ^= a;
if (a & 0x80) a = (a << 1) ^ 0x1B;
else a <<= 1;
n >>= 1;
}
return result;
}
void enc_next_ready(uint8_t *key, uint8_t *roundkeys) {
uint8_t *v2; // rdx
uint8_t *v3; // rax
char *v4; // rax
char v5; // r8
char v6; // cl
uint8_t *v7; // rax
uint8_t temp_1; // [rsp+5h] [rbp-1Bh]
uint8_t temp_2; // [rsp+6h] [rbp-1Ah]
uint8_t temp_3; // [rsp+7h] [rbp-19h]
uint8_t i; // [rsp+17h] [rbp-9h]
uint8_t ia; // [rsp+17h] [rbp-9h]
uint8_t *last4bytes; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15u; ++i ) {
v2 = key++;
v3 = roundkeys++;
*v3 = *v2;
}
last4bytes = roundkeys - 4;
for ( ia = 0; ia <= 9u; ++ia ) {
temp_3 = S[*last4bytes];
temp_1 = S[last4bytes[2]];
temp_2 = S[last4bytes[3]];
*roundkeys = *(roundkeys - 16) ^ S[last4bytes[1]] ^ RC[ia];
roundkeys[1] = *(roundkeys - 15) ^ temp_1;
roundkeys[2] = *(roundkeys - 14) ^ temp_2;
roundkeys[3] = *(roundkeys - 13) ^ temp_3;
roundkeys[4] = *(roundkeys - 12) ^ last4bytes[4];
roundkeys[5] = *(roundkeys - 11) ^ last4bytes[5];
roundkeys[6] = *(roundkeys - 10) ^ last4bytes[6];
roundkeys[7] = *(roundkeys - 9) ^ last4bytes[7];
roundkeys[8] = *(roundkeys - 8) ^ last4bytes[8];
roundkeys[9] = *(roundkeys - 7) ^ last4bytes[9];
roundkeys[10] = *(roundkeys - 6) ^ last4bytes[10];
roundkeys[11] = *(roundkeys - 5) ^ last4bytes[11];
roundkeys[12] = *(roundkeys - 4) ^ last4bytes[12];
roundkeys[13] = *(roundkeys - 3) ^ last4bytes[13];
roundkeys[14] = *(roundkeys - 2) ^ last4bytes[14];
v4 = (char *)(last4bytes + 15);
last4bytes += 16;
v5 = *v4;
v6 = *(roundkeys - 1);
v7 = roundkeys + 15;
roundkeys += 16;
*v7 = v6 ^ v5;
}
}
void dec_next_ready(uint8_t *key, uint8_t *roundkeys) {
enc_next_ready(key, roundkeys);
}
void enc_next(uint8_t *roundkeys, uint8_t *plaintext, uint8_t *ciphertext) {
double *v3; // rbx
double *v4; // rax
uint8_t *v5; // rax
uint8_t *v6; // rax
void *v7; // rax
uint8_t *v8; // rax
uint8_t tmp[16]; // [rsp+40h] [rbp-40h] BYREF
char v11; // [rsp+5Eh] [rbp-22h] BYREF
uint8_t t; // [rsp+5Fh] [rbp-21h]
double temp_1; // [rsp+60h] [rbp-20h]
int temp_0; // [rsp+6Ch] [rbp-14h]
char temp; // [rsp+73h] [rbp-Dh]
int a; // [rsp+74h] [rbp-Ch]
int cnt; // [rsp+78h] [rbp-8h]
uint8_t j; // [rsp+7Eh] [rbp-2h]
uint8_t i; // [rsp+7Fh] [rbp-1h]
for ( i = 0; i <= 15u; ++i ) {
v6 = roundkeys++;
ciphertext[i] = *v6 ^ plaintext[i] ^ 0x66;
}
for ( j = 1; j <= 9; j++ ) {
for ( i = 0; i <= 15u; ++i )
tmp[i] = INV_S[ciphertext[i]];
inv_shift_rows(tmp);
for ( i = 0; i <= 15; i += 4 ) {
t = tmp[i + 2] ^ tmp[i + 1] ^ tmp[i] ^ tmp[i + 3];
ciphertext[i] = t ^ tmp[i] ^ mul2(tmp[i] ^ tmp[i + 1]);
ciphertext[i + 1] = t ^ tmp[i + 1] ^ mul2(tmp[i + 1] ^ tmp[i + 2]);
ciphertext[i + 2] = t ^ tmp[i + 2] ^ mul2(tmp[i + 2] ^ tmp[i + 3]);
ciphertext[i + 3] = t ^ tmp[i + 3] ^ mul2(tmp[i + 3] ^ tmp[i]);
}
for ( i = 0; i <= 15u; ++i ) {
v8 = roundkeys++;
ciphertext[i] ^= *v8;
}
}
for ( i = 0; i <= 15u; ++i )
ciphertext[i] = S[ciphertext[i]];
shift_rows(ciphertext);
for ( i = 0; i <= 0xFu; ++i ) {
v5 = roundkeys++;
ciphertext[i] ^= *v5;
}
}
void dec_next(uint8_t *roundkeys, uint8_t *ciphertext, uint8_t *plaintext) {
double *v3; // rbx
double *v4; // rax
uint8_t *v5; // rax
uint8_t *v6; // rax
void *v7; // rax
uint8_t *v8; // rax
uint8_t tmp[16]; // [rsp+40h] [rbp-40h] BYREF
char v11; // [rsp+5Eh] [rbp-22h] BYREF
uint8_t t; // [rsp+5Fh] [rbp-21h]
double temp_1; // [rsp+60h] [rbp-20h]
int temp_0; // [rsp+6Ch] [rbp-14h]
char temp; // [rsp+73h] [rbp-Dh]
int a; // [rsp+74h] [rbp-Ch]
int cnt; // [rsp+78h] [rbp-8h]
int j; // [rsp+7Eh] [rbp-2h]
int i; // [rsp+7Fh] [rbp-1h]
roundkeys += 176 - 1;
for ( i = 15; i >= 0; i-- ) {
v5 = roundkeys--;
ciphertext[i] ^= *v5;
}
inv_shift_rows(ciphertext);
for ( i = 15; i >= 0; i-- )
ciphertext[i] = INV_S[ciphertext[i]];
for ( j = 1; j <= 9; j++ ) {
for ( i = 15; i >= 0; i-- ) {
v8 = roundkeys--;
ciphertext[i] ^= *v8;
}
for ( i = 0; i <= 15; i += 4 ) {
tmp[i] = muln(ciphertext[i], 0xE) ^ muln(ciphertext[i + 1], 0xB) ^ muln(ciphertext[i + 2], 0xD) ^ muln(ciphertext[i + 3], 0x9);
tmp[i + 1] = muln(ciphertext[i], 0x9) ^ muln(ciphertext[i + 1], 0xE) ^ muln(ciphertext[i + 2], 0xB) ^ muln(ciphertext[i + 3], 0xD);
tmp[i + 2] = muln(ciphertext[i], 0xD) ^ muln(ciphertext[i + 1], 0x9) ^ muln(ciphertext[i + 2], 0xE) ^ muln(ciphertext[i + 3], 0xB);
tmp[i + 3] = muln(ciphertext[i], 0xB) ^ muln(ciphertext[i + 1], 0xD) ^ muln(ciphertext[i + 2], 0x9) ^ muln(ciphertext[i + 3], 0xE);
}
shift_rows(tmp);
for ( i = 15; i >= 0; i-- )
ciphertext[i] = S[tmp[i]];
}
for ( i = 15; i >= 0; i-- ) {
v6 = roundkeys--;
plaintext[i] = *v6 ^ ciphertext[i] ^ 0x66;
}
}
void aes_encrypt(char* data, char* key) {
uint8_t roundkeys[256];
char src[16];
memcpy(src, data, 16);
enc_next_ready(key, roundkeys);
enc_next(roundkeys, src, data);
}
void aes_decrypt(char* data, char* key) {
// todo: implement this method
uint8_t roundkeys[256];
char src[16];
memcpy(src, data, 16);
dec_next_ready(key, roundkeys);
dec_next(roundkeys, src, data);
}
int main() {
unsigned int delta = 0x73637466;
// char data[33] = "0123456789abcdef";
// cmp_arr
char data[] = {
0xBE, 0x1C, 0xB3, 0xF3, 0xA1, 0xF4, 0xE4, 0x63,
0x11, 0xE1, 0x1C, 0x6B, 0x54, 0x0A, 0xDF, 0x74,
0xF2, 0x93, 0x55, 0xDA, 0x48, 0xFC, 0xA2, 0x3C,
0x89, 0x63, 0x2E, 0x7F, 0x8D, 0xA4, 0x6D, 0x4E,
0
};
unsigned int* data_ptr = (unsigned int*) data;
unsigned int v0, v1, v2, v3;
char key[17] = "Welcome_to_sctf!";
unsigned int* tea_key_ptr = (unsigned int*) key;
unsigned int k0 = tea_key_ptr[0], k1 = tea_key_ptr[1], k2 = tea_key_ptr[2], k3 = tea_key_ptr[3];
unsigned int sum1 = 0, sum2 = 0;
/*
char test_data[17] = "aaaaaaaaaaaaaaaa";
aes_encrypt(test_data, key);
for (int i = 0; i < 16; i++) {
printf("0x%02x ", test_data[i] & 0xff);
}
// 0x33 0x18 0x46 0x7d 0x4a 0x1b 0x41 0xe5 0xc7 0x6e 0xf6 0x7a 0xd4 0x8a 0x6b 0x8c
putchar('\n');
/**/
/*
// tea
for (int i = 0; i < 32; i++) {
sum1 += delta;
sum2 += delta;
v0 += ((v1 << 4) + k2) ^ (v1 + sum1) ^ ((v1 >> 5) + k3) ^ (sum1 + i);
v2 += ((v3 << 4) + k2) ^ (v3 + sum2) ^ ((v3 >> 5) + k3) ^ (sum2 + i);
v1 += ((v0 << 4) + k0) ^ (v0 + sum1) ^ ((v0 >> 5) + k1) ^ (sum1 + i);
v3 += ((v2 << 4) + k0) ^ (v2 + sum1) ^ ((v2 >> 5) + k1) ^ (sum2 + i);
}
data_ptr[0] = v0 ^ ((delta >> 24) & 0xff);
data_ptr[1] = v1 ^ ((delta >> 16) & 0xff);
data_ptr[2] = v2 ^ ((delta >> 8) & 0xff);
data_ptr[3] = v3 ^ (delta & 0xff);
/**/
for (int loop = 0; loop < 2; loop++) {
aes_decrypt((char*) data_ptr, key);
data_ptr[3] ^= (delta & 0xff);
data_ptr[2] ^= ((delta >> 8) & 0xff);
data_ptr[1] ^= ((delta >> 16) & 0xff);
data_ptr[0] ^= ((delta >> 24) & 0xff);
v0 = data_ptr[0];
v1 = data_ptr[1];
v2 = data_ptr[2];
v3 = data_ptr[3];
for (int i = 0; i < 32; i++) {
sum1 += delta;
sum2 += delta;
}
for (int i = 31; i >= 0; i--) {
v3 -= ((v2 << 4) + k0) ^ (v2 + sum1) ^ ((v2 >> 5) + k1) ^ (sum2 + i);
v1 -= ((v0 << 4) + k0) ^ (v0 + sum1) ^ ((v0 >> 5) + k1) ^ (sum1 + i);
v2 -= ((v3 << 4) + k2) ^ (v3 + sum2) ^ ((v3 >> 5) + k3) ^ (sum2 + i);
v0 -= ((v1 << 4) + k2) ^ (v1 + sum1) ^ ((v1 >> 5) + k3) ^ (sum1 + i);
sum2 -= delta;
sum1 -= delta;
}
data_ptr[0] = v0;
data_ptr[1] = v1;
data_ptr[2] = v2;
data_ptr[3] = v3;
data_ptr += 4;
}
puts(data);
/**/
return 0;
}
flag: SCTF{5277cc2af8f1155f7a61030f46fdf9df}
BabyDriver
babydriver.exe 拖入 ida , initialize_pointers 中注册了异常处理函数 sub_140071500 ,进入函数,有个参数,直接改类型为 PEXCEPTION_POINTERS ,逻辑一下就出来了:
int __fastcall sub_140071500(PEXCEPTION_POINTERS e)
{
int result; // eax
if ( e->ExceptionRecord->ExceptionCode == (unsigned int)EXCEPTION_ACCESS_VIOLATION )
{
e->ContextRecord->Rip = *(_QWORD *)e->ContextRecord->Rsp + 7i64;
e->ContextRecord->Rsp += 8i64;
result = EXCEPTION_CONTINUE_EXECUTION;
}
else if ( e->ExceptionRecord->ExceptionCode == (unsigned int)EXCEPTION_INT_DIVIDE_BY_ZERO )
{
funcs[e->ContextRecord->Rax]();
e->ContextRecord->Rip += 8i64;
result = EXCEPTION_CONTINUE_EXECUTION;
}
else
{
result = EXCEPTION_CONTINUE_SEARCH;
}
return result;
}
这里先放着,待会要用的。
再回到 main ,直接一个 MEMORY[0]()
,看汇编:
.text:0000000140071318 xor eax, eax
.text:000000014007131A call rax
.text:000000014007131C xor eax, eax
.text:000000014007131E jmp loc_1400714DA
这 call 明显会触发 EXCEPTION_ACCESS_VIOLATION ,被上面的函数处理,*(_QWORD *)e->ContextRecord->Rsp
就是返回地址 0x14007131C ,再 + 7 就是 0x140071323 ,之后 rsp += 8 将返回地址弹出栈,所以什么都没做,直接将 call 指令 patch 为 jmp loc_14007131C:
.text:0000000140071318 xor eax, eax
.text:000000014007131A jmp short loc_140071323 ; Keypatch modified this from:
.text:000000014007131A ; call rax
.text:000000014007131C ; ---------------------------------------------------------------------------
.text:000000014007131C xor eax, eax
.text:000000014007131E jmp loc_1400714DA
.text:0000000140071323 ; ---------------------------------------------------------------------------
再 F5 , main 就恢复了:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
__int64 input_len; // [rsp+48h] [rbp-60h]
HANDLE hFile; // [rsp+50h] [rbp-58h]
DWORD NumberOfBytesRead; // [rsp+60h] [rbp-48h] BYREF
char enc[16]; // [rsp+68h] [rbp-40h] BYREF
char input[21]; // [rsp+78h] [rbp-30h] BYREF
input[0] = 0;
memset(&input[1], 0, 20ui64);
enc[0] = 0;
memset(&enc[1], 0, 15ui64);
initialize_pointers();
if ( query(0, 0i64, 0) )
{
hFile = CreateFileA("key.bin", GENERIC_READ, FILE_SHARE_READ, 0i64, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0i64);
if ( hFile == (HANDLE)-1i64 )
{
printf("Can't not find the key file\n");
result = 0;
}
else
{
NumberOfBytesRead = 0;
if ( ReadFile(hFile, Buffer, 0x258u, &NumberOfBytesRead, 0i64) )
{
printf("Please input the flag:");
scanf("%s", input);
input_len = -1i64;
do
++input_len;
while ( input[input_len] );
if ( test_charset(input, input_len)
&& run_function(input, input_len, enc)
&& (query(2, (__int64)enc, 16u), Sleep(500u), query(1, (__int64)enc, 16u), enc[0]) )
{
printf("Success!!!\n");
result = 0;
}
else
{
printf("You fail.\n");
result = 0;
}
}
else
{
printf("Can't not read the key file\n");
result = 0;
}
}
}
else
{
printf("Driver not running");
result = 0;
}
return result;
}
query 函数很奇怪:
_BOOL8 __fastcall query(int a1, __int64 a2, unsigned int a3)
{
struct _SYSTEMTIME SystemTime; // [rsp+28h] [rbp-40h] BYREF
__int64 v5[3]; // [rsp+38h] [rbp-30h] BYREF
__int64 v6; // [rsp+50h] [rbp-18h]
v5[0] = a1;
v5[1] = a2;
v5[2] = a3;
v6 = 1i64;
memset(&SystemTime, 0, sizeof(SystemTime));
GetSystemTime(&SystemTime);
*(_QWORD *)qword_14009DED8 = v5;
SetSystemTime(&SystemTime);
return v6 == 0;
}
按理说 return v6 == 0 只会返回 0 ,那么 main 中就会输出 “Driver not running” 并结束,想到题目名叫 BabyDriver ,那就还得分析另一个文件 sctf.sys 。同样拖入 ida ,进入 DriverEntry ,跟入 sub_140001078 :
__int64 __fastcall sub_140001078(PDRIVER_OBJECT a1)
{
sub_1400010CC(sub_140001000);
a1->DriverUnload = (PDRIVER_UNLOAD)sub_1400010A0;
return 0i64;
}
跟入 sub_1400010CC :
__int64 __fastcall sub_1400010CC(__int64 (__fastcall *a1)(__int64))
{
UNICODE_STRING v3; // [rsp+20h] [rbp-40h] BYREF
struct _OBJECT_ATTRIBUTES ObjectAttributes; // [rsp+30h] [rbp-30h] BYREF
PCALLBACK_OBJECT CallbackObject; // [rsp+78h] [rbp+18h] BYREF
CallbackObject = 0i64;
v3.Buffer = L"\\Callback\\SetSystemTime";
*(_DWORD *)&v3.Length = 0x30002E;
memset(&ObjectAttributes, 0, sizeof(ObjectAttributes));
ObjectAttributes.Length = 48;
ObjectAttributes.ObjectName = &v3;
ObjectAttributes.Attributes = 80;
if ( ExCreateCallback(&CallbackObject, &ObjectAttributes, 1u, 1u) < 0 )
return 0xC0000225i64;
CallbackRegistration = ExRegisterCallback(CallbackObject, (PCALLBACK_FUNCTION)CallbackFunction, 0i64);
if ( !CallbackRegistration )
return 0xC0000225i64;
func_140001000 = a1;
return 0i64;
}
原来 Driver 注册了 SetSystemTime 函数的回调函数 CallbackFunction :
KIRQL __fastcall sub_14000123C(__int64 a1)
{
_BYTE *v1; // rcx
KIRQL result; // al
if ( *(_DWORD *)a1 == 2 )
{
v1 = *(_BYTE **)(a1 + 8);
v1[15] = v1[14];
v1[14] = v1[13];
v1[13] = v1[12];
v1[12] = 0x10;
result = KeGetCurrentIrql();
}
return result;
}
void __fastcall CallbackFunction(PVOID CallbackContext, PVOID Argument1, PVOID Argument2)
{
struct _KPROCESS *v3; // rdi
void *v4; // rax
void *v5; // rbx
PEB *v6; // rax
KIRQL v7; // al
unsigned __int64 v8; // rdi
_BYTE *v9; // rcx
v3 = IoGetCurrentProcess();
v4 = (void *)PsGetProcessWow64Process(v3);
v5 = 0i64;
if ( v4 )
{
v5 = (void *)*((unsigned int *)v4 + 5);
*((_DWORD *)v4 + 5) = 0;
}
else
{
v6 = PsGetProcessPeb(v3);
if ( v6 )
{
v5 = v6->SubSystemData;
v6->SubSystemData = 0i64;
}
}
if ( MmIsAddressValid(v5) )
{
v7 = sub_14000123C((__int64)v5);
v8 = v7;
if ( v7 >= 2u )
__writecr8(0i64);
*((_QWORD *)v5 + 3) = (int)func_140001000(v5);
if ( (unsigned __int8)v8 >= 2u )
{
KeGetCurrentIrql();
__writecr8(v8);
}
if ( *(_DWORD *)v5 == 2 )
{
v9 = (_BYTE *)*((_QWORD *)v5 + 1);
v9[12] = v9[13];
v9[13] = v9[14];
v9[14] = v9[15];
v9[15] = 0;
}
}
}
这里调用了 sub_1400010CC 传入的参数函数 sub_140001000,并将返回值赋值给 *((_QWORD *)v5 + 3)
。
__int64 __fastcall sub_140001000(void *a1)
{
int v1; // er8
unsigned int *v2; // r9
__int64 i; // rcx
v1 = 0;
if ( *(_DWORD *)a1 )
{
if ( *(_DWORD *)a1 == 1 )
{
**((_BYTE **)a1 + 1) = byte_140003070;
}
else if ( *(_DWORD *)a1 == 2 )
{
v2 = (unsigned int *)*((_QWORD *)a1 + 1);
for ( i = 0i64; i < 32; i += 8i64 )
{
if ( *(_QWORD *)(i + 0x140003008i64) * (unsigned __int64)*v2 % 0x30F21223F13Bi64 != *(_QWORD *)(i + 0x140003028i64) )
break;
++v1;
++v2;
}
byte_140003070 = v1 == 4;
}
}
else
{
byte_140003070 = 0;
}
return 0i64;
}
这里的返回值为 0 ,联想到 main 中的 query 函数,返回 v6 == 0 就为 1 了,所以 CallbackFunction 中的 v5 就是 query 函数中的 v5 , v5[3] 就刚好是 v6 。
明白了这两者的关系,就先分析 sub_140001000 吧,这个文件里只有这一个函数比较可疑了。
*(_DWORD *)a1
存放的值表明函数需要做什么,如果为 0 ,执行byte_140003070 = 0;
,如果为 1 ,执行**((_BYTE **)a1 + 1) = byte_140003070;
,如果为 2 ,执行中间的四轮判断,是a[i] * x[i] % p == b[i]
这样的形式, a, b, p 是固定值, x 是传入的(unsigned int *)*((_QWORD *)a1 + 1)
,就能猜到 main 中将输入操作后得到 4 个 int ,再在这里执行判断。这样就先计算出这 4 个 int 。先在 0x140003008 找到这 8 个数:
.data:0000000140003000 ; const __int64 qword_140003000
.data:0000000140003000 qword_140003000 dq 53816244564283 ; DATA XREF: sub_140001000+2F↑r
.data:0000000140003008 dq 649430213, 895805425, 751586893, 3859015203
.data:0000000140003028 dq 49033969837712, 36224070408864, 1911652611622, 32147829792607
编写简单脚本反解:
#!/usr/bin/python2
from gmpy2 import invert
# a[i] * x[i] % p = b[i]
p = 53816244564283
a = [649430213, 895805425, 751586893, 3859015203]
b = [49033969837712, 36224070408864, 1911652611622, 32147829792607]
x = [0] * 4
for i in range(4):
x[i] = int(invert(a[i], p)) * b[i] % p
# assert(x[i] * a[i] % p == b[i])
print(hex(x[i]))
'''
0xd4933333
0x7bde8f00
0x31d84f77
0xbcd47c10 -> 0xbcd47c
'''
并且注意到 x[3] & 0xff == 0x10,而在 sub_14000123C 中多加了一个 0x10 ,所以实际上的输入 x[3] 只有 24 位,最后的 0x10 是回调函数里补的。也就是说,转成 uchar , x 只有 15 字节。
再回到 main , query(0) 后读取文件 key.bin ,但是并没有给这个文件。
之后输入 flag , test_charset 函数判断输入的字符是否在 ‘0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{|}’ 中,并将该字符换成字符在此串中的下标。
紧接着来到 run_function ,又是MEMORY[0]()
,不多说,改成 jmp ,再 F5 :
_BOOL8 __fastcall run_function(char *input, unsigned int a2, char *enc)
{
int i; // [rsp+20h] [rbp-28h]
int j; // [rsp+24h] [rbp-24h]
int v6; // [rsp+28h] [rbp-20h]
for ( i = 0; i < 600; ++i )
{
v6 = (unsigned __int8)Buffer[i];
for ( j = 0; j < 8; ++j )
{
if ( (v6 & 1) != 0 )
{
_input = input;
_enc = enc;
}
v6 >>= 1;
}
}
return point == 0;
}
看起来很奇怪,什么都没做?这里需要看一下汇编:
.text:000000014007186A mov eax, [rsp+48h+j]
.text:000000014007186E mov ecx, [rsp+48h+i]
.text:0000000140071872 lea eax, [rax+rcx*8]
.text:0000000140071875 cdqe
.text:0000000140071877 xor edx, edx
.text:0000000140071879 xor ecx, ecx
.text:000000014007187B div rcx
.text:000000014007187E mov [rsp+48h+var_18], rax
.text:0000000140071883
.text:0000000140071883 loc_140071883: ; CODE XREF: run_function+75↑j
.text:0000000140071883 mov eax, [rsp+48h+var_20]
有一个除零异常,被 ida 忽略掉了,但是这是关键,要用到注册的异常处理函数第二个 if EXCEPTION_INT_DIVIDE_BY_ZERO ,调用函数funcs[e->ContextRecord->Rax]()
,读汇编计算 rax = i * 8 + j ,所以真实的逻辑应该是:
_BOOL8 __fastcall run_function(char *input, unsigned int a2, char *enc)
{
int i; // [rsp+20h] [rbp-28h]
int j; // [rsp+24h] [rbp-24h]
int v6; // [rsp+28h] [rbp-20h]
for ( i = 0; i < 600; ++i )
{
v6 = (unsigned __int8)Buffer[i];
for ( j = 0; j < 8; ++j )
{
if ( (v6 & 1) != 0 )
{
_input = input;
_enc = enc;
funcs[i * 8 + j]();
}
v6 >>= 1;
}
}
return point == 0;
}
.data 段中查看 funcs ,是个大小为 3600 的函数数组,这不可能一个一个去分析。随便找几个查看,函数都是这样的形式:
_enc[e] |= ((_input[a] >> b) & c) << d;
point -= f;
其中_enc
和_input
是全局变量,由上面函数的参数赋值;point
也是全局变量,初始值为 3100 ; a~f 都是代码中的常值。
这样就写个脚本提取出 a~f 这几个参数,处理过程中发现 a 的取值为 [0, 19] , b 的取值为 {0, 2, 4} , c 是固定的 3 , d 的取值为 {0, 2, 4, 6} , e 的取值为 [0, 14] ,这样计算一下不重复的组合刚好是 20 3 4 15 == 3600 ,也就是 funcs 数组的大小;由于 c 是固定的 3 ,也就是每个函数将_input
中的 2bit 复制到_enc
的 2bit 上,而_enc
和_input
的 2bit 数又是相等的: 20 3 == 60 == 4 * 15 ,这样自然就抽象出一个算法问题:
两个集合 A、B 均有 60 个元素
A 中任一元素到 B 中任一元素均有一给定的映射权值
求 A 到 B 的一一映射,使得映射权值和为给定值(3100)
问了下打算法的同学,这个求解不太乐观,还是得自己想办法。
看了下权值,大部分的都是3位数,而权值平均数 3100 / 60 < 60 ,可能是求一个最小值,这样就有思路了,先提取出权值矩阵,找出每行的最小值及下标,然后观察发现权值和为 3044 ,并且只有一个重复的下标值 20 ,缺少的下标值为 21 ,再将相关的值输出出来:
>>> values[13][20]
101
>>> values[50][20]
64
>>> values[13][21]
157
>>> values[50][21]
151
values[13][21]
与 values[13][20]
刚好相差 56 ,也就是 3100 – 3044 ,所以将 13 映射到 21 ,这样就找到了解,再反过来求解 flag 。
#!/usr/bin/python2
from gmpy2 import invert
# a[i] * x[i] % p = b[i]
p = 53816244564283
a = [649430213, 895805425, 751586893, 3859015203]
b = [49033969837712, 36224070408864, 1911652611622, 32147829792607]
x = [0] * 4
for i in range(4):
x[i] = int(invert(a[i], p)) * b[i] % p
# assert(x[i] * a[i] % p == b[i])
# print(hex(x[i]))
'''
0xd4933333
0x7bde8f00
0x31d84f77
0xbcd47c10 -> 0xbcd47c
'''
assert((x[3] & 0xff) == 0x10)
x[3] >>= 8
# convert x to byte array
y = []
for i in x:
y.append(i & 0xff)
y.append((i >> 8) & 0xff)
y.append((i >> 16) & 0xff)
y.append((i >> 24) & 0xff)
assert(y[-1] == 0)
x = y[: -1]
del y
def qword(data, index):
x = 0
for i in range(8):
x |= ord(data[i + index]) << (i * 8)
return x
def dword(data, index):
x = 0
for i in range(4):
x |= ord(data[i + index]) << (i * 8)
return x
with open('./babydriver.exe', 'rb') as f:
data = f.read()
def parse_func_fail(i, j):
print 'fail:', i, j
exit(-1)
funcs = []
size = 3600
for i in range(size):
addr = qword(data, 8 * i + 0x93630) - 0x140000C00
s = ''
while True:
s += data[addr]
if s[-4: -1] == '\xCC\xCC\xCC': break
addr += 1
if s[4: 9] != '\xB8\x01\x00\x00\x00': # mov eax, 1
parse_func_fail(i, 0)
if s[9: 12] != '\x48\x6B\xC0': # imul rax, 0x??
parse_func_fail(i, 1)
a = ord(s[12])
s = s[13: ]
if s[11: 13] == '\xC1\xF8': # sar eax, 0x??
b = ord(s[13])
s = s[14: ]
elif s[11: 14] == '\x88\x04\x24':
b = 0
s = s[11:]
else:
parse_func_fail(i, 2)
if s[7: 9] != '\x83\xE0': # and eax, 0x??
parse_func_fail(i, 3)
c = ord(s[9])
s = s[10: ]
if s[7: 9] == '\xC1\xE0': # shl eax, 0x??
d = ord(s[9])
s = s[10: ]
elif s[7: 10] == '\x88\x04\x24':
d = 0
s = s[7: ]
else:
parse_func_fail(i, 4)
if s[8: 11] != '\x48\x6B\xC0': # imul rax, 0x??
parse_func_fail(i, 5)
e = ord(s[11])
s = s[12: ]
if s[15: 17] != '\x0B\xC1': # or eax, ecx
parse_func_fail(i, 6)
s = s[17: ]
if s[26: 28] == '\x48\x2D': # sub rax, 0x????????
f = dword(s, 28)
s = s[32: ]
elif s[26: 29] == '\x48\x83\xE8': # sub rax, 0x??
f = ord(s[29])
s = s[30: ]
elif s[26: 29] == '\x48\xFF\xC8': # dec rax
f = 1
s = s[29: ]
else:
parse_func_fail(i, 7)
if s[11] != '\xC3':
parse_func_fail(i, 8)
'''
enc[e] |= ((input[a] >> b) & c) << d;
point -= f
'''
assert(c == 3)
# funcs.append([a, b, c, d, e, f])
# change order to sort
funcs.append([e, d, a, b, f, i])
funcs.sort()
point_total = 3100
# 3 * 20 == 60 == 4 * 15
# 3 * 20 * 4 * 15 == 3600
# for i in range(size): print i, funcs[i]
values = []
for i in range(60):
values.append([])
for j in range(60):
values[-1].append(funcs[60 * i + j][4])
'''
with open('./values', 'w') as f:
f.write(str(values))
'''
result = [i for i in range(60)]
'''
calculate result
'''
result = [59, 43, 45, 46, 7, 27, 9, 11, 26, 42, 31, 25, 0, 21, 24, 34, 54, 55, 22, 39, 40, 44, 5, 41, 28, 1, 38, 48, 58, 29, 16, 51, 49, 15, 33, 8, 4, 50, 53, 6, 23, 12, 19, 13, 30, 17, 37, 3, 47, 32, 20, 36, 52, 18, 2, 14, 56, 35, 10, 57]
flag = [0] * 20
for i in range(60):
flag[result[i] / 3] |= ((x[i / 4] >> (i % 4 * 2)) & 3) << (result[i] % 3 * 2)
table = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{|}'
for i in range(len(flag)):
flag[i] = table[flag[i]]
print(''.join(flag))
最后一个 ‘{‘ 转成 ‘}’ 即可。
flag: SCTF{Dr1ver|Tim3|10}