【2023春节】解题领红包 WP
本帖最后由 十一七 于 2023-2-5 09:53 编辑# 【2023春节】解题领红包
## Windows 初级题 1/23
cpp逆向,简单移位
```python
bytearray(])
```
## Android初级题 1/24
Java逆向,简单位操作
```python
bytearray()
```
## Android初级题 1/25
frida hook
```bash
objection -g com.zj.wuaipojie2023_1 explore
android hooking watch class_method com.zj.wuaipojie2023_1.C.cipher --dump-return
```
## Windows中级题 1/26
魔改upx,oep定位,dump&fix,关闭ASLR。
MFC程序,SEH异常处理,调用结构类似以下:
```cpp
void DivException(int a1, int a2, int* a3) {
__try {
*a3 = a1 / a2;
}
__except (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) { }
}
void DbgBrk() {
__try {
DebugBreak();
}
__except (GetExceptionCode() == EXCEPTION_BREAKPOINT) {
getUSER32BaseAddr(); // +0x2410
}
}
int entry() {
__try {
RaiseException(1u, 0, 0, 0);
}
__except (filterLoadApiTable() /* +0x2DC0 */) {
DbgBrk(); // +0x2FC0
}
__finally {
DivException(1, 0, 0); // +0x2D80
}
divZero(); // +0x2FF0
return 0;
}
```
这里注意到了`divZero`并没有对应注册SEH。
而是在`_tmainCRTStartup`初始化程序时,调用`SetUnhandledExceptionFilter`注册了UEH,具体位置在偏移`0x100B`上。
同时,由于`SetUnhandledExceptionFilter`内部调用了`ntdll!NtQueryInformationProcess`检测调试器,在这里结合`divZero`实现了反调试。
将`call divZero` patch 为`call TopLevelExceptionFilter` 完成反反调试
流程分析中的几个点:
通过PEB获取`USER32.dll`基址,进而拿到函数表。
API表位于偏移`0x17C90`
存储解密字符串表指针位于`0x17D30`,内容为宽字符形式
粗略分析,疑似check两处:`0x2110`和`0x2A50`
tea算法两处
- tea encrypt `0x1D70`
- tea decrypt `0x26E0`
### 懒虫方案
猜能省大量时间(
frida hook俩函数,修改返回值 & 打表
发现只有修改`0x2A50`函数返回值为4时,弹出`Success`,逻辑部分简单描述如下
1. 十六进制转字符数组
2. tea解密
3. 与明文对比
调试拿到明文
frida hook拿到`k`和`delta`
```js
const addr = Module.findBaseAddress("【2023春节】解题领红包之五_dump_SCY.exe");
function buf2hex(buffer) {
return [...new Uint8Array(buffer)]
.map(x => '0x'+x.toString(16).padStart(2, '0').toUpperCase())
.join(' ');
}
Interceptor.attach(addr.add(0x26e0), {
onEnter: function (args) {
console.log("[+] onEnter");
console.log("[+] v: " + buf2hex(args.readByteArray(8)));
console.log("[+] k: " + buf2hex(args.readByteArray(16)));
console.log("[+] delta: " + args);
console.log("[+] sum: " + args);
},
onLeave: function (retval) {
console.log("[+] retval: " + retval);
}
});
```
跑一遍标准tea,转成十六进制拿到flag
```cpp
void tea_encrypt(uint32_t*a1, uint32_t *a2, uint32_t a3, uint32_t a4)
{
unsigned int v5; //
unsigned int v6; //
unsigned int i; //
v5 = *a1;
v6 = a1;
for ( i = 0; i < 0x20; ++i )
{
a4 += a3;
v5 += (a2 + (v6 >> 5)) ^ (a4 + v6) ^ (*a2 + 16 * v6);
v6 += (a2 + (v5 >> 5)) ^ (a4 + v5) ^ (a2 + 16 * v5);
}
*a1 = v5;
a1 = v6;
}
int main() {
char inp[] = "flag{!!!_HAPPY_NEW_YEAR_2023!!!}";
auto *v = (uint32_t*) inp;
uint32_t k = {0x8C7BD7F8, 0x18F7AFF0, 0xA57387E8, 0x31EF5FE0};
uint32_t delta = 0x8c7bd7f7;
uint32_t sum = 0x8f7afee0;
for (int i = 0; i < 4; ++i) {
tea_encrypt(v+i*2, k, delta, 0);
printf("%08X%08X", v, v);
}
printf("\n");
}
```
### 正常方案
程序调用`DialogBoxParamW`创建对话框,`0x11D0`为`DialogFunc`
`WM_INITDIALOG`初始化窗口
单击按钮,首先执行`WM_CREATE`处内容
分析`0x2110`部分看似进行了tea encrypt并进行了check,实际上只是混淆视听,其真正的用途是传递`sum`
而后调用`PostMessageW`执行`WM_CUT`部分,此时`lParam`即为`sum`
进入default分支,`v6`恒为0,能看到真正的check在`0x2A50`处
check算法主要是根据`uid`,计算`delta`,以及`k`,而后tea解密,大致算法如下
```cpp
v11 = 0x11111111;
for ( i = 0; i < 14; ++i )
v11 += 0x11111111;
for ( delta = v11 + uid; delta >= 0; delta = 2 * delta + 9 )
;
for ( n = 0; n < 4; ++n )
k = (n + 1) * (delta + 1);
tea_decrypt(&flag, k, delta, sum)
```
附keygen
```python
from ctypes import c_int32
import struct
def calc_delta(uid):
i = c_int32(uid - 1)
while i.value >= 0:
i.value = 2 * i.value + 9
return i.value & 0xffffffff
def calc_key(delta):
k = * 4
for i in range(4):
k = (i + 1) * (delta + 1)
k &= 0xffffffff
return k
def tea_encrypt(v, k, delta):
v0, v1 = v
k0, k1, k2, k3 = k
sums = 0
for i in range(32):
sums += delta
sums &= 0xffffffff
v0 += ((v1 << 4) + k0) ^ (v1 + sums) ^ ((v1 >> 5) + k1)
v0 &= 0xffffffff
v1 += ((v0 << 4) + k2) ^ (v0 + sums) ^ ((v0 >> 5) + k3)
v1 &= 0xffffffff
return v0, v1
def keygen(uid):
FLAG = struct.unpack('<8L', b'flag{!!!_HAPPY_NEW_YEAR_2023!!!}')
flag = * 8
delta = calc_delta(uid)
k = calc_key(delta)
for i in range(4):
flag, flag = tea_encrypt(FLAG, k, delta)
return "".join(f"{x:08x}" for x in flag)
if __name__ == '__main__':
print(keygen(1150835))
```
## Android 中级题 1/27
ndk逆向,java层没啥用
流程完全没有串起来,需要脑洞
根据符号猜作者意图:找到RealKey解密`assets/aes.png`
算法为AES-128-ECB PKSC7 padding
JNI注册了函数`get_RealKey`,结尾调用的`strcmp`上有个不明显的hint:`thisiskey`,其意为执行`getRealKey`的算法得到`RealKey`
```python
k = b'|wfkuqokj4548366'
xmmword_3030 =
v5 = [(i + j) & 0xff for i, j in zip(k, xmmword_3030)]
real = bytearray(v5)
print(real) # bytearray(b'wuaipojie2023114')
```
AES解出图片
```python
import base64
from Crypto.Cipher import AES
def pad(s):
return s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
def unpad(s):
return s[:-ord(s)]
cipherText = open('com.zj.wuaipojie2023_2/assets/aes.png', 'rb').read()
cipherText = base64.urlsafe_b64decode(cipherText)
ctx = AES.new(key=b'wuaipojie2023114', mode=AES.MODE_ECB)
dec = unpad(ctx.decrypt(cipherText))
pic = bytearray.fromhex(dec.decode('utf-8'))
with open('aes.dec.png', 'wb') as f:
f.write(pic)
```
然后misc行为:文件末尾塞了一个png图片
扫码得到flag
## Android 高级题 1/28
ndk逆向,JNI动态注册`checkSn`,ollvm sub fla bcf拉满,流程混淆的稀碎
我选择~~摆烂~~unidbg trace
贴个片段
```java
public static void main(String[] args) {
emulator = AndroidEmulatorBuilder.for64Bit().setRootDir(new File("target/rootfs")).setProcessName("com.zj.wuaipojie2023_2").build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);
vm = emulator.createDalvikVM();
vm.setVerbose(true);
new AndroidModule(emulator, vm).register(memory);
DalvikModule dm = vm.loadLibrary(new File("lib52pojie.so"), false);
vm.setJni(new MyJni());
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
// PrintStream printStream = null;
// try {
// printStream = new PrintStream(new FileOutputStream("log.txt"));
// } catch (FileNotFoundException e) {
// throw new RuntimeException(e);
// }
//
// emulator.traceCode().setRedirect(printStream);
String uid = "01150835";
String flag = "MWYxODIxOThmYWFmM2ZlYjYxM2Q5ZDVlZTExMzg4MTM=";
List<Object> list = new ArrayList<>();
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm, uid)));
list.add(vm.addLocalObject(new StringObject(vm, flag)));
Debugger dbg = emulator.attach();
// dbg.addBreakPoint(0x4001064c);
Number number = module.callFunction(emulator, 0xe830, list.toArray());
System.out.println("result: " + number.intValue());
}
```
分析下jni注册,`checkSn`偏移`E830`
调用一次checkSn大概86w行,ida+trace日志+unidbg调试分析
改变同一位置字节、不同位置字节
对比两者trace差异,简单贴几个关键偏移
```cpp
0x184B8 -> md5_transform
0x1B32C -> 0x17834 -> md5(const uint8_t* uid)
0x1ECD8 -> zuc_F(pzuc_context context)
0x1D454 -> zuc_init(pzuc_context context, const uint8_t* key, const uint8_t* iv)
0x10668 -> md5_hexdigests(uid) xor zuc_encrypt(base64_decode(flag)) == 0
```
md5、base64为标准算法
zuc算法为流加密,参考代码 https://github.com/zhiyuan-lin/zuc/blob/master/c/zuc.c
`key`与时间戳相关,`iv`来自偏移`4D22B`,具体组成如下
```python
char key={};
char iv={};
char buf={};
int t = timestamp/20000000; // 0x185f69b0a37 / 20000000 == 83743
sprintf(buf, "%d", t);
memcpy(key, 0x4D220, 0xA); // 0x3d,0x99,0x40,0x0e,0x05,0x50,0x7c,0x81,0x2e,0x3f
memcpy(key+0xA, buf, 0x5); // 0x38,0x33,0x37,0x34,0x35,0x00
memcpy(iv, 0x4D22B, 0x5); // 0xf2,0x3d,0xa0,0x96,0x02,0x2b,0x26,0x63,0xe1,0x5f,0xc1,0x28,0x6a,0x33,0x70,0x25
```
> 流加密,即`crypt(crypt(inp)) == inp`
直接偷懒
uid、flag输入格式
```python
uid # 8位,不足补0
flag = base64.b64encode(md5(uid).hexdigest()) # 44 bytes after base64 encode
```
unidbg跑起来dump
```java
final byte[] res = new byte;
final int[] i = {0};
dbg.addBreakPoint(0x40010668, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
res++] = (byte) emulator.getContext().getLongByReg(Arm64Const.UC_ARM64_REG_X15);
return true;
}
});
```
然后转成base64,输回去就成了
至于keygen,分析这一坨太费时间,略
##Web 初/中/高
应该改名misc+web+古典crypto套娃题
直接挂个草稿吧(不完全)
`2023challenge.52pojie.cn` adg dns重定向 `52pojie.cn`
```bash
flag1
放视频
flag1{52pojiehappynewyear}
----------------------------------------------------------------------------
flag2
视频二维码
https://2023challenge.52pojie.cn/?flag=flag2{878a48f2}
flag2{878a48f2}
----------------------------------------------------------------------------
flag3
视频右下角水印 iodj3(06i95dig) 凯撒 offset 3
flag3(06f95afd)
----------------------------------------------------------------------------
----------------------------------------------------------------------------
flag5
morse code
flag5{eait}
----------------------------------------------------------------------------
flag6
flag6{590124}
视频开头 拨号盘
https://blog.xhyeax.com/2019/02/03/friend-ctf/
---------------------------------------------------------------------------
flag7
网页 二进制
flag7{5d06be63}
----------------------------------------------------------------------------
flag8
音频频谱
flag8{c394d7}
----------------------------------------------------------------------------
flag9
倒着放音频
flag9{21c5f8}
----------------------------------------------------------------------------
----------------------------------------------------------------------------
flag11
网页下方brainfuck
flag11{63418de7}
----------------------------------------------------------------------------
flagA
curl "https://2023challenge.52pojie.cn/?uid=1150835" \
-H "X-52PoJie-Uid: 1150835" \
-v
----------------------------------------------------------------------------
flagB
dns解析
dig 2023challenge.52pojie.cn @8.8.8.8 -t txt
2023challenge.52pojie.cn. 600 IN TXT "\"_52pojie_2023_happy_new_year=flagB{substr(md5(uid+\\\"_happy_new_year_\\\"+floor(timestamp/600)),0,8)}\""
----------------------------------------------------------------------------
flagC
jwt伪造
eyJ1aWQiOiIxMTUwODM1Iiwicm9sZSI6ImFkbWluIn0
curl 'https://2023challenge.52pojie.cn/home' \
-H 'Cookie: 2023_challenge_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxMTUwODM1Iiwicm9sZSI6ImFkbWluIn0.U2Yd0PGhcJq_D0QAicnA8xTb1menooqWBaLCt65y1Cw' \
-v
```
## END
最后吐槽一下,其他不说,Android能不能换个花样,去年也是ollvm
> 难度不够ollvm来凑(
## Refer
- https://www.anquanke.com/post/id/208404
- https://learn.microsoft.com/en-us/windows/win32/api/
- https://github.com/zhiyuan-lin/zuc
- https://blog.xhyeax.com/2019/02/03/friend-ctf/ 侃遍天下无二人 发表于 2023-2-6 10:54
一看到大量的trace就头疼,不知道从哪找起差异,大佬可以录个视频演示下吗,安卓高级题这里是不是相当于做 ...
视频大概率是不会录的(
简单写几个分析trace的技巧:
控制变量法,trace两遍对比差异
跟踪输入数据在寄存器/堆、栈之间的传递
指令运行、函数调用的次数
这里并不是爆破,只是将zuc加密后结果作为输入又执行了一遍zuc加密,得到的就是flag(流加密的特性)通俗的解释就是负负得正。 基本上看不懂,膜拜 安卓高级题太难了,大佬厉害 过来支持一下{:1_925:} ?????2023啥时候出的春节红包题啊!根本没有刷到啊,我丢啊,从除夕就开始关注的,现在直接出的答案? 太屌了,一个没看懂{:1_908:} 太厉害了,新手学习中.... 只能膜拜,默默学习吧
初级还能搞搞 中高级还得不断学习:lol 一看到大量的trace就头疼,不知道从哪找起差异,大佬可以录个视频演示下吗,另外想知道安卓高级题这里是不是相当于做了逐位爆破