Reverse(十二)
decode_me
https://ctf.bugku.com/challenges/detail/id/413.html
ida64反编译,如下主函数很简洁明了
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // bl
char buf; // [rsp+17h] [rbp-29h] BYREF
ssize_t v6; // [rsp+18h] [rbp-28h]
int fd; // [rsp+24h] [rbp-1Ch]
unsigned int v8; // [rsp+28h] [rbp-18h]
char v9; // [rsp+2Fh] [rbp-11h]
if ( argc != 2 )
exit(1);
fd = open(argv[1], 0, 0LL);
if ( fd == -1 )
exit(1);
v8 = 0;
v9 = 0;
while ( v8 <= 0x224 )
{
v6 = read(fd, &buf, 1uLL); // 每次读一个字符到buf
if ( v6 <= 0 )
break;
v3 = byte_404060[v8]; // 可以获取
v9 |= v3 ^ sub_4012D8((unsigned int)buf, v8++);
}
if ( !v9 ) // 需保证v9=0
puts("[+] Correct one!");
return 0;
}
分析sub_4012D8函数,其中a1是读取的字符,a2是下标
__int64 __fastcall sub_4012D8(__int64 a1, __int64 a2)
{
__int64 v3; // [rsp+0h] [rbp-8h]
v3 = sub_401291();
return v3 ^ sub_401240(a2);
}
发现a1貌似没被用?看下sub_401291函数,可以看到不对劲,只能去分析汇编代码
// positive sp value has been detected, the output may be wrong!
void sub_401291()
{
__asm { jmp off_404388[rax] } // 要去分析off_404388
}
上述汇编代码为
sub_401291 proc near
mov rax, 0FFFFFFFFFFFFFFF8h
jmp short loc_4012AF
loc_4012AF:
add rax, 8 ; rax置0,从0开始下标取函数
jmp off_404388[rax] ; 此时跳转loc_4012AC函数
loc_4012AC:
push rbx ; 入栈,追朔rbx值,可以保存的正是主函数里的数组逐个遍历赋的值
jmp short $+2 ; 一条语句一般就是2个字节,所以$+2代表下一条指令,由下图可知再次跳转到loc_4012AF
每次rax都会+8,因此每次jmp的函数不同;但查看图状视图可以看到除了loc_4012C1最后return其他均回到loc_4012AF
下面按顺序分析每个函数作用
loc_40129A:
jmp loc_4012A0
loc_4012A0:
jmp short loc_4012AF ; 直接返回了
loc_4012A2:
lea rbx, unk_404288 ; 一个256字节大小的数组,将地址赋给了rbx
jmp short loc_4012AF
loc_4012CD:
push rax ; 入栈,保存rax值
mov al, dil ; rdi低八位赋给rax低八位,rdi存的buf值
xlat ; 由下图可知这个指令根据偏移查表,bx存放的前面unk_404288初始地址,al存放正是主函数buf字符的ascii值,相等于偏移量
mov rbx, rax ; 查找到的数组元素值赋给rbx
pop rax ; 出栈,恢复rax值
jmp short loc_4012AF
loc_4012C1:
mov rax, rbx ; 将最终数组找到的值赋给rax
pop rbx ; 出栈,恢复主函数里的数组逐个遍历赋的值
retn ; 退出函数
综上sub_401291函数作用是,传入文件读取的字符ascii值(buf),取unk_404288[buf]
接下来该计算v3 ^ sub_401240(a2),查看伪代码发现还是很怪,得分析汇编代码
__int64 __fastcall sub_401240(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5)
{
__int128 v6; // rax
v6 = 0LL;
BYTE8(v6) = 1;
return off_4043C8(a1, a2, *((_QWORD *)&v6 + 1), a4, a5);
}
分析汇编代码发现和上一个十分相似,直接全部贴上来
sub_401240 proc near ; CODE XREF: sub_4012D8+19↓p
jmp short loc_40126F
mov rsp, rbp
pop rbp
retn
loc_401247:
xor r8, r8 ; r8清0
jmp short loc_40125D
mov rax, 0FFFFFFFFFFFFFFF8h
jmp short loc_40125D
loc_401255:
xadd r8, rdx ; 先交换值再相加
loop loc_401255 ; 很重要,每次cx=cx-1直到cx=0,而cx正好在前面loc_401283中=a2+1
jmp short $+2
loc_40125D:
add rax, 8 ; rax恢复为0,开始遍历取函数
jmp off_4043C8[rax] ; 具体顺序如下图
mov rax, rsi
jmp short loc_40125D
loc_40126C:
xchg rax, r8 ; 交换值
retn
loc_40126F:
xor rax, rax ; rax置0,由于运算结果等于0,ZF标志位置1
cqo ; 扩展双字,即从64位扩展到128位,具体是将eax寄存器的值进行扩展,高位置于rdx中,因此rdx=0
stc ; CF位置1
setz dl ; ZF位标志赋给dl,即rdx=1
sub rax, 8 ; 雷同
jmp short loc_40125D
add rsp, 28h
retn
loc_401283: ; 第一次这里
add rdi, rcx ; 朔源发现在sub_4012D8中赋值rdi为a2的值,也就是主函数里的v8;rcx是一个地址值,做的处理是rdi=rdi+rcx
sub rcx, rdi ; rcx=rcx-rdi=rcx-(rdi+rcx)=-rdi
neg rcx ; rcx取补码即rdi
inc rcx ; rcx++=rdi+1,也就是a2+1
jmp short loc_40125D
综上,上述汇编代码c语言逻辑如下
int sub_401240(int a2) {
int r8 = 0;
int rdx = 1;
for (int i = 0; i < a2 + 1; i++) {
int temp = r8;
r8 += rdx;
rdx = r8;
}
}
脚本逆向
byte_404060 = [0x7B, 0xED, 0x51, 0x57, 0xFD, 0x11, 0x5E, 0x41, 0x6E, 0xAB, 0xA2, 0x0C, 0xF0, 0x2A, 0x29, 0x97, 0xD9, 0x67, 0x2A, 0x24, 0x9D, 0x64, 0xBF, 0x74, 0x42, 0x7D, 0x80, 0x8B, 0xEA, 0x63, 0x25, 0x4B, 0x0E, 0xAB, 0x85, 0x2C, 0x32, 0x67, 0x5A, 0x87, 0x2F, 0xA4, 0x67, 0x25, 0x8D, 0x0C, 0xB5, 0xA4, 0xD9, 0xEE, 0xCE, 0xBE, 0xA7, 0xB0, 0xF9, 0x19, 0xF1, 0x2D, 0x83, 0x72, 0xF1, 0x1B, 0xB1, 0xF7, 0x01, 0x9A, 0xFA, 0xEF, 0xDE, 0xC4, 0x9F, 0x98, 0x7D, 0xCE, 0x3A, 0x91, 0xF9, 0x84, 0x85, 0xFC, 0x8E, 0x18, 0xB0, 0x4A, 0x75, 0x71, 0x6C, 0x47, 0x81, 0x34, 0xD9, 0xF6, 0x4C, 0x47, 0x8F, 0xDF, 0x1E, 0x37, 0xFA, 0x8F, 0x29, 0x73, 0x0F, 0x9E, 0xBE, 0x0C, 0x4A, 0xAA, 0xD1, 0xFB, 0x09, 0x07, 0x47, 0x22, 0x57, 0x1A, 0xBD, 0x90, 0xBC, 0xE2, 0xCD, 0x33, 0xBA, 0xC8, 0x37, 0xFB, 0xA7, 0x7F, 0x0E, 0xEC, 0xC7, 0xDC, 0x41, 0x98, 0xF1, 0x49, 0xD5, 0x54, 0xB6, 0x5F, 0x20, 0xFB, 0x59, 0xB1, 0x32, 0xE3, 0xC9, 0xFE, 0x69, 0x30, 0x71, 0xF9, 0xB0, 0xAF, 0xC6, 0x4C, 0x05, 0x61, 0x40, 0x24, 0x41, 0x20, 0xF7, 0x41, 0xDE, 0xF5, 0x2B, 0x18, 0x83, 0x02, 0x89, 0x40, 0x9B, 0x04, 0x4B, 0x5D, 0x2E, 0x58, 0x91, 0xCA, 0x35, 0x1A, 0x76, 0x20, 0x75, 0xA7, 0xCE, 0x91, 0xFA, 0x34, 0x6D, 0x71, 0x79, 0xCD, 0x40, 0x1F, 0xCE, 0x46, 0x75, 0xCA, 0x76, 0x4F, 0x95, 0xE1, 0x36, 0x1D, 0x9A, 0x17, 0xFF, 0x84, 0x17, 0x15, 0x5E, 0x6D, 0x89, 0x6C, 0x33, 0xA8, 0xDE, 0x08, 0x66, 0x92, 0xE7, 0x27, 0x1A, 0x95, 0xEB, 0x48, 0xB7, 0xF6, 0xF1, 0xF1, 0x15, 0x37, 0x71, 0x02, 0x70, 0x27, 0x8D, 0x1C, 0x4D, 0xB5, 0x20, 0x9B, 0x1E, 0x0A, 0x7E, 0xCF, 0x18, 0xFB, 0xF2, 0x9E, 0x65, 0xA1, 0x6D, 0xC3, 0x81, 0xAA, 0x6C, 0x77, 0xAE, 0xFD, 0xBD, 0x2B, 0xFD, 0xE9, 0xCD, 0x8C, 0xC1, 0x90, 0xB4, 0x68, 0xE9, 0x3F, 0xD2, 0xAF, 0x52, 0x45, 0xCE, 0xE9, 0x01, 0xBA, 0x21, 0xC5, 0x4E, 0x7D, 0xAD, 0xD4, 0x2D, 0xB9, 0x9E, 0x81, 0xBD, 0xC3, 0x92, 0xAD, 0x3B, 0x28, 0x05, 0x5B, 0xE5, 0x41, 0xFE, 0x50, 0x85, 0x04, 0xE7, 0xB4, 0x78, 0x83, 0xC3, 0x4C, 0x9A, 0x3D, 0xDE, 0xF8, 0xBB, 0x50, 0xCE, 0xBD, 0x19, 0x73, 0x5A, 0xCB, 0x52, 0x9B, 0x4E, 0xF3, 0x31, 0xF3, 0x9D, 0xBD, 0x9E, 0x5D, 0x6D, 0x38, 0xB2, 0xEA, 0x74, 0xDB, 0xF6, 0x3E, 0x9B, 0xA9, 0xE9, 0x99, 0x81, 0x49, 0x9A, 0xE2, 0x89, 0x17, 0x89, 0x1A, 0x4D, 0x37, 0xDE, 0xA4, 0xFD, 0x18, 0xFC, 0x93, 0x2E, 0x61, 0xC9, 0x1E, 0x6E, 0xDD, 0x3D, 0xD3, 0x11, 0x6F, 0x03, 0x0C, 0x76, 0xB4, 0x41, 0x14, 0xFE, 0xB1, 0xE6, 0x07, 0x9D, 0x4C, 0xF9, 0xF3, 0x51, 0x68, 0xE9, 0xCA, 0xF5, 0x59, 0x60, 0xDB, 0xA1, 0x6B, 0xAB, 0x2A, 0xD8, 0x61, 0xD1, 0x53, 0x1B, 0x81, 0x3A, 0x6D, 0xA2, 0x74, 0xE7, 0xD5, 0xBA, 0x4C, 0xA9, 0x64, 0x25, 0x4E, 0xBD, 0xAB, 0x31, 0xFB, 0x95, 0xD2, 0x4E, 0x09, 0xAF, 0x6B, 0xF1, 0x41, 0x38, 0xFD, 0x19, 0x1F, 0x2E, 0x99, 0xCC, 0xBC, 0x3A, 0xBE, 0x55, 0xE1, 0xBE, 0xC4, 0x83, 0x4C, 0x45, 0x7B, 0xD5, 0xC4, 0xE2, 0x92, 0xB7, 0xB5, 0x11, 0xC4, 0x27, 0x98, 0xBE, 0x69, 0xCD, 0x0C, 0x41, 0x26, 0x22, 0xA9, 0x86, 0xBF, 0xEB, 0x1C, 0xCD, 0x65, 0xDA, 0x37, 0x0F, 0x80, 0xE7, 0xE2, 0x1E, 0xEB, 0x39, 0x8F, 0xFB, 0x92, 0x4C, 0x76, 0x3D, 0x4A, 0x0F, 0x39, 0x98, 0x4D, 0xEB, 0x43, 0x65, 0xD5, 0xA0, 0x80, 0x03, 0x1A, 0x66, 0x8B, 0x80, 0xBC, 0x73, 0x06, 0xA4, 0xBB, 0xA2, 0x79, 0x68, 0x0E, 0x10, 0x9A, 0xBD, 0x01, 0xD3, 0xD0, 0xF2, 0xF4, 0x04, 0x04, 0x17, 0x1C, 0xE8, 0x3D, 0x0E, 0x56, 0x5E, 0xA3, 0x5D, 0x1B, 0x26, 0x7B, 0x5A, 0xB7, 0x3B, 0x59, 0x60, 0x1B, 0x1D, 0x2F, 0xE9, 0x75, 0x14, 0x24, 0x41, 0x8B, 0x7E, 0xE1, 0x3D, 0x00, 0x00, 0x00]
unk_404288 = [0x24, 0xDD, 0xC1, 0x89, 0x0A, 0xCF, 0x83, 0xE2, 0xC6, 0xFD, 0x7F, 0x46, 0x44, 0x25, 0x9D, 0x2E, 0x27, 0x20, 0xB8, 0xE1, 0x35, 0x39, 0x33, 0xB1, 0x90, 0xC8, 0x30, 0xA9, 0x98, 0x75, 0x73, 0xEA, 0xF8, 0x36, 0x40, 0x23, 0xA4, 0x29, 0x69, 0x5D, 0x15, 0x91, 0xC4, 0x0B, 0xE4, 0xAC, 0x37, 0x95, 0x3C, 0x63, 0x52, 0x85, 0xE0, 0x1E, 0xF3, 0xFF, 0x82, 0x84, 0xB5, 0xBB, 0x97, 0x92, 0x96, 0x12, 0x8E, 0x4A, 0xD6, 0x49, 0x7A, 0x9B, 0x8B, 0x34, 0x0C, 0x8F, 0xD5, 0xEF, 0x2C, 0x5A, 0x3A, 0xCA, 0xC5, 0x6E, 0xA0, 0x67, 0x0D, 0x8C, 0x1F, 0xF5, 0x07, 0x1D, 0x5B, 0x4F, 0x9F, 0xAB, 0x05, 0xA6, 0x81, 0x53, 0x3F, 0xFB, 0xF6, 0xEC, 0x0F, 0x4E, 0x42, 0x9C, 0x08, 0x6B, 0xBA, 0xF2, 0x4B, 0x5F, 0x19, 0x14, 0x54, 0xC2, 0x4C, 0x1C, 0x5C, 0x71, 0xFE, 0xA2, 0xA1, 0x1B, 0x03, 0xD8, 0x64, 0x11, 0x26, 0x6D, 0x0E, 0x18, 0xCB, 0x70, 0xEB, 0x51, 0x62, 0x68, 0x2D, 0x43, 0x59, 0x01, 0x00, 0x77, 0x58, 0xAA, 0xBD, 0xE6, 0x31, 0xEE, 0x87, 0xDF, 0x1A, 0x2F, 0x55, 0xB6, 0xF0, 0x6A, 0x50, 0x32, 0x65, 0x06, 0xB4, 0x76, 0xD9, 0x22, 0x6C, 0xBE, 0xE8, 0xA5, 0x9A, 0x6F, 0x48, 0x57, 0xC9, 0x04, 0xF9, 0xCD, 0xD4, 0xDB, 0x21, 0xE9, 0x2B, 0xAE, 0xDE, 0x7C, 0x7B, 0x45, 0xF4, 0xC0, 0x09, 0x79, 0x93, 0xFA, 0xE5, 0x4D, 0xA3, 0xCE, 0xBF, 0xB3, 0x7D, 0x56, 0x3B, 0x94, 0xB0, 0x86, 0x61, 0x9E, 0xE3, 0xFC, 0xD0, 0x8A, 0x2A, 0x66, 0x80, 0xB7, 0xDA, 0xF7, 0x16, 0xC7, 0x38, 0xA7, 0xC3, 0xD3, 0xD1, 0x5E, 0x47, 0x8D, 0xA8, 0x10, 0x41, 0xDC, 0x60, 0x88, 0x78, 0x3D, 0xED, 0xBC, 0x3E, 0xF1, 0xAD, 0xAF, 0x72, 0x17, 0x02, 0x7E, 0xD7, 0xB9, 0x99, 0x28, 0x74, 0xCC, 0xB2, 0xE7, 0xD2, 0x13]
def sub_401240(a2):
r8 = 0
rdx = 1
for i in range(a2+1):
temp = rdx
rdx = r8
r8 += temp
return r8
def sub_401291(a1):
return unk_404288[a1]
#def sub_4012D8(a1, a2):
# v3 = sub_401291(a1)
# return v3 ^ sub_401240(a2)
flag = ''
for i in range(0x225):
v3 = byte_404060[i]
buf = v3 ^ (sub_401240(i)%256)
for j in range(len(unk_404288)):
if buf == unk_404288[j]:
flag += chr(j)
print(flag) # shellmates{x86_has_WA4AY_Too_M4nY_IN$TRUC710N$}
签退-ctfshow
uncompyle6反编译:uncompyle6 -o re3.py re3.pyc
# uncompyle6 version 3.9.0
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: re3.py
# Compiled at: 2020-03-06 17:43:28
import string
c_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '()'
flag = 'BozjB3vlZ3ThBn9bZ2jhOH93ZaH9' # 长度正好4的倍数,末尾无.说明原来字节数为3的倍数
def encode(origin_bytes):
c_bytes = [ ('{:0>8}').format(str(bin(b)).replace('0b', '')) for b in origin_bytes ] # 字节转为二进制
resp = ''
nums = len(c_bytes) // 3
remain = len(c_bytes) % 3
integral_part = c_bytes[0:3 * nums]
while integral_part:
tmp_unit = ('').join(integral_part[0:3]) # 每次取前三个字节(二进制字符串)拼成长度为24的二进制格式字符串
tmp_unit = [ int(tmp_unit[x:x + 6], 2) for x in [0, 6, 12, 18] ] # 每个字节分别转为二进制数字
resp += ('').join([ c_charset[i] for i in tmp_unit ]) # 下标取字符数组
integral_part = integral_part[3:] # 去除前三个字节
if remain:
remain_part = ('').join(c_bytes[3 * nums:]) + (3 - remain) * '0' * 8 # 补0使得其长度同样为24
tmp_unit = [ int(remain_part[x:x + 6], 2) for x in [0, 6, 12, 18] ][:remain + 1]
resp += ('').join([ c_charset[i] for i in tmp_unit ]) + (3 - remain) * '.'
return rend(resp)
def rend(s):
def encodeCh(ch):
f = lambda x: chr((ord(ch) - x + 2) % 26 + x) # 全部按字母顺序右移2位
if ch.islower():
return f(97)
if ch.isupper():
return f(65)
return ch
return ('').join(encodeCh(c) for c in s)
解密脚本(ps:题目给的太简单正好3的倍数即无点号,可以直接不考虑remain写代码,这里我全部做了代码逆向,并附上了测试案例)
def decode(c):
c = rrend(c)
# 查找.出现次数
index, remain = 0, 0
while True:
index = c.find('.', index)
if index == -1:
break
remain += 1
index += 1
remain_part, resp = '', ''
if remain: # 如果有.
remain = 3 - remain
tmp_unit = ('').join([bin(c_charset.index(i))[2:].zfill(6) for i in c[-4:-4+remain+1]]) + (3 - remain) * '0' * 6
remain_part = ('').join([chr(int(tmp_unit[x:x+8], 2)) for x in [0, 8, 16]][:remain+1])
integral_part = c[:-4] if remain else c
while integral_part:
tmp_unit = ('').join([bin(c_charset.index(i))[2:].zfill(6) for i in integral_part[0:4]]) # 每次取前四个字符(4*6)转为二进制格式字符串
tmp_unit = [int(tmp_unit[x:x + 8], 2) for x in [0, 8, 16]]
resp += ('').join([ chr(i) for i in tmp_unit ])
integral_part = integral_part[4:] # 去除前4个字符
resp += remain_part
return resp
def rrend(s):
def decodeCh(ch):
f = lambda x: chr((ord(ch) - x- 2) % 26 + x) # 全部按字母顺序左移2位
if ch.islower():
return f(97)
if ch.isupper():
return f(65)
return ch
return ('').join(decodeCh(c) for c in s)
print(decode(flag))
# test
c = encode(bytearray('xherlock', 'utf-8'))
print(c)
print(decode(c))
c = encode(bytearray('ctfshow', 'utf-8'))
print(c)
print(decode(c))
'''
out:
flag{c_t_f_s_h_0_w_!}
gIjneozxA2u.
xherlock
A3Toe2jxfy..
ctfshow
'''
真的是签到
https://ctf.show/challenges#%E7%9C%9F%E7%9A%84%E6%98%AF%E7%AD%BE%E5%88%B0-207
压缩包伪加密:找504b0102后面的0900改为0000
aspack脱壳:https://down.52pojie.cn/Tools/Unpackers/这里找aspack unpacker
最后upx脱壳,ida反编译得到核心代码函数
int sub_4012F0()
{
int v1[23]; // [esp+30h] [ebp-168h] BYREF
size_t i; // [esp+8Ch] [ebp-10Ch]
char Str[264]; // [esp+90h] [ebp-108h] BYREF
sub_401990();
sub_401510();
scanf("%s", Str);
if ( strlen(Str) == 18 ) // 长度为18
{
i = strlen(Str) - 1;
memcpy(v1, &unk_402000, 0x48u);
while ( 1 )
{
Str[i] ^= i;
if ( !i )
break;
--i;
}
strrev(Str);
for ( i = 0; i != 18; ++i )
{
if ( Str[i] != v1[i] )
{
printf("%s\n", "try again");
return 0;
}
}
printf("%s\n", "You Win");
}
else
{
printf("%s\n", "try again");
}
return 0;
}
u = [0x6c, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xb6, 0xbf, 0xa0, 0xcf, 0x7c, 0x71, 0x6a, 0x6c, 0x70, 0x64, 0x75, 0x63][::-1]
flag = []
for i in range(len(u)):
flag.append(u[i]^i)
print(bytes(flag).decode('gb2312'))
android-app-100
jeb加载分析:
package com.example.ctf2;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
Button a;
EditText b;
TextView c;
int d;
String e;
static {
System.loadLibrary("adnjni");
}
public MainActivity() {
super();
this.d = 123;
this.e = "Code";
}
public native int IsCorrect(String arg1) { // 这个判断要去上面jni调用的c库里分析
}
public void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(2130903040);
this.a = this.findViewById(2131230721); // 按钮
this.b = this.findViewById(2131230720);
this.c = this.findViewById(2131230722);
this.e = Build.SERIAL; // 获取手机序列号,应该是判断不是模拟器
this.d = 114366;
this.a.setOnClickListener(new a(this)); // 监听点击事件, 里面的a是一个类
}
public native int processObjectArrayFromNative(String arg1) {
}
}
package com.example.ctf2;
import android.util.Log;
import android.view.View$OnClickListener;
import android.view.View;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
class a implements View$OnClickListener {
a(MainActivity arg1) {
this.a = arg1; // 这是传入的参数也就是输入字符串
super();
}
public void onClick(View arg8) {
new String(" ");
String v0 = this.a.b.getText().toString();
Log.v("EditText", this.a.b.getText().toString()); // b是文本按钮
new String("");
int v1 = this.a.processObjectArrayFromNative(v0);
int v2 = this.a.IsCorrect(v0); // 保证v2==1
v0 = String.valueOf(this.a.d + v1) + " "; // d=114366
try {
MessageDigest v1_1 = MessageDigest.getInstance("MD5");
v1_1.update(v0.getBytes());
byte[] v1_2 = v1_1.digest(); // v0的md5值
StringBuffer v3 = new StringBuffer();
int v0_2;
for(v0_2 = 0; v0_2 < v1_2.length; ++v0_2) {
v3.append(Integer.toString((v1_2[v0_2] & 255) + 256, 16).substring(1));
}
if(v2 == 1 && this.a.e != "unknown") {
this.a.c.setText("Sharif_CTF(" + v3.toString() + ")");
}
if(v2 == 1 && this.a.e == "unknown") {
this.a.c.setText("Just keep Trying :-)");
}
if(v2 == 0) {
this.a.c.setText("Just keep Trying :-)");
}
return;
}
catch(NoSuchAlgorithmException v0_1) {
v0_1.printStackTrace();
return;
}
}
}
ida分析对应的so文件,第一个Java_com_example_ctf2_MainActivity_IsCorrect函数,发现传入while大循环的只有v10,v10要么等于0,要么非0,经过测试只有v10=0时返回的v11=1,即需要保证v9=v12="ef57f3fe3cf603c03890ee588878c0ec5"
int __fastcall Java_com_example_ctf2_MainActivity_IsCorrect(int a1, int a2, int a3)
{
int v4; // r6
int v5; // r0
int v6; // r1
char *v9; // [sp+14h] [bp-4Ch]
int v10; // [sp+1Ch] [bp-44h]
int v11; // [sp+20h] [bp-40h]
char v12[60]; // [sp+24h] [bp-3Ch] BYREF
v9 = (char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); // 较难理解但猜测是输入的字符串
strcpy(v12, "ef57f3fe3cf603c03890ee588878c0ec");
v4 = 0;
v12[33] = '5'; // 注意中间少个v12[32]=0,相当于v12="ef57f3fe3cf603c03890ee588878c0ec"
v10 = j_strcmp(v9, v12);
v5 = 1701458332;
while ( 1 ) //
{
while ( 1 )
{
while ( 1 )
{
v6 = v5;
v5 = 809244963;
if ( v6 <= 1701458331 )
break;
v5 = -333293478;
if ( v10 )
v5 = -158041539;
}
if ( v6 <= 809244962 )
break;
(*(void (__fastcall **)(int, int, char *))(*(_DWORD *)a1 + 680))(a1, a3, v9);
v11 = v4;
v5 = -326599761;
}
if ( v6 != -158041539 ) // 必须返回v11=1,因此这里if必须进去过一次
{
v4 = 1;
v5 = -158041539;
if ( v6 != -333293478 )
break;
}
}
return v11;
}
综上输入的字符串="ef57f3fe3cf603c03890ee588878c0ec",接着看第二个函数Java_com_example_ctf2_MainActivity_processObjectArrayFromNative
int __fastcall Java_com_example_ctf2_MainActivity_processObjectArrayFromNative(int a1, int a2, int a3)
{
int v3; // r0
int v4; // r0
int v5; // r0
char v6; // r1
char v8[40]; // [sp-50h] [bp-A8h] BYREF
int v9; // [sp-28h] [bp-80h] BYREF
int v10; // [sp-20h] [bp-78h] BYREF
int v11; // [sp-18h] [bp-70h] BYREF
int v12; // [sp-10h] [bp-68h] BYREF
_DWORD v13[6]; // [sp-8h] [bp-60h] BYREF
const char *v14; // [sp+10h] [bp-48h]
int v15; // [sp+14h] [bp-44h]
int v16; // [sp+18h] [bp-40h]
int v17; // [sp+1Ch] [bp-3Ch]
int v18; // [sp+20h] [bp-38h]
int v19; // [sp+24h] [bp-34h]
_DWORD *v20; // [sp+2Ch] [bp-2Ch]
int *v21; // [sp+30h] [bp-28h]
int *v22; // [sp+34h] [bp-24h]
int *v23; // [sp+38h] [bp-20h]
int *v24; // [sp+3Ch] [bp-1Ch]
char v25; // [sp+40h] [bp-18h]
int v26; // [sp+44h] [bp-14h]
v16 = a3;
v15 = a1;
v3 = -1661035768;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( v3 <= -1303766071 )
v3 = 2063008300;
if ( v3 > 441419317 )
break;
v3 = 1800572839;
if ( !v25 )
v3 = 441419318;
}
if ( v3 > 867851767 )
break;
v3 = 1405326207;
}
if ( v3 <= 1405326206 )
break;
if ( v3 == 1405326207 )
{
*v24 = j_lrand48();
(*(void (__fastcall **)(_DWORD, int, int))(*(_DWORD *)*v20 + 680))(*v20, *v21, *v23);
v26 = *v22;
v3 = 867851768;
}
else if ( v3 == 1800572839 )
{
*v22 = 92060626;
v3 = 441419318;
}
else
{
v20 = v13;
v21 = &v12;
v22 = &v11;
v23 = &v10;
v24 = &v9;
v13[0] = v15;
v12 = v16;
v17 = 0;
v11 = 0;
v4 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)v15 + 676))(v15, v16, 0);
*v23 = v4;
v14 = (const char *)*v23;
v13[5] = v8;
v18 = 101;
qmemcpy(v8, "ef57f3fe3cf603c03890ee588878c0ec", 32);
v19 = 53;
v13[4] = 55;
v13[3] = 99;
v8[32] = v17; // 正是0,和上一个函数里一样
v8[33] = 53;
v5 = j_strcmp(v14, v8); // 相等因此v5=0
v6 = 1;
if ( v5 )
v6 = v17;
v25 = v6;
v3 = -18897425;
}
}
return v26;
}
跑一遍上述代码,注释掉错误信息后得到返回值v26=92060626
因此回到上面java代码中的onclick(已简化和填充数据),稍微改写下即可打印flag
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class sharif {
public static void main(String args[]) {
String v0 = "";
v0 = String.valueOf(114366 + 92060626) + " "; // d=114366
try {
MessageDigest v1_1 = MessageDigest.getInstance("MD5");
v1_1.update(v0.getBytes());
byte[] v1_2 = v1_1.digest(); // v0的md5值
StringBuffer v3 = new StringBuffer();
int v0_2;
for (v0_2 = 0; v0_2 < v1_2.length; ++v0_2) {
v3.append(Integer.toString((v1_2[v0_2] & 255) + 256, 16).substring(1));
}
System.out.println("Sharif_CTF(" + v3.toString() + ")");
return;
} catch(NoSuchAlgorithmException v0_1) {
v0_1.printStackTrace();
return;
}
}
}
NET(APK-逆向2)
题目名称原来是APK但不太对的样子,因为exe是NET编写的所以写个NET标题,dnspy打开
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
namespace Rev_100
{
// Token: 0x02000002 RID: 2
internal class Program
{
// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
private static void Main(string[] args)
{
string hostname = "127.0.0.1";
int port = 31337;
TcpClient tcpClient = new TcpClient();
try
{
Console.WriteLine("Connecting...");
tcpClient.Connect(hostname, port);
}
catch (Exception)
{
Console.WriteLine("Cannot connect!\nFail!");
return;
}
Socket client = tcpClient.Client;
string text = "Super Secret Key";
string text2 = Program.read();
client.Send(Encoding.ASCII.GetBytes("CTF{"));
foreach (char x in text)
{
client.Send(Encoding.ASCII.GetBytes(Program.search(x, text2)));
}
client.Send(Encoding.ASCII.GetBytes("}"));
client.Close();
tcpClient.Close();
Console.WriteLine("Success!");
}
// Token: 0x06000002 RID: 2 RVA: 0x0000213C File Offset: 0x0000033C
private static string read()
{
string fileName = Process.GetCurrentProcess().MainModule.FileName; // 获取的正是当前exe文件的名字
string[] array = fileName.Split(new char[]
{
'\\'
});
string path = array[array.Length - 1];
string result = "";
using (StreamReader streamReader = new StreamReader(path))
{
result = streamReader.ReadToEnd();
}
return result;
}
// Token: 0x06000003 RID: 3 RVA: 0x000021B0 File Offset: 0x000003B0
private static string search(char x, string text)
{
int length = text.Length;
for (int i = 0; i < length; i++)
{
if (x == text[i])
{
int value = i * 1337 % 256;
return Convert.ToString(value, 16).PadLeft(2, '0');
}
}
return "??";
}
}
}
两种方法:
- 直接开端口
- 写python脚本f.open读取exe,略
serial-150-SUCTF
ida64反编译发现伪代码未成功
但是在linux里可以正常运行,只好远程linux调试查看,首先在main开头处下断点,启动调试后会停在开头
调试过程中发现ida警告RIP指向了已经定义指令的地址,合理猜测这是伪代码无法生成的原因
同意yes然后F8会重新编译出正确代码,如下可以看到字符串提示输入key,以及打印指令
接着又会碰到警告RIP,同样做法,同时也会发现中间有部分db代码无用
接着运行需要输入key,linux输入1234,F8发现输入已入栈
再次让ida修复RIP指向地址指令,如下,作用是获取输入key长度和16比较(合理猜测输入长度为16,否则就不会跳转)
.text:0000000000400A19 lea rax, [rbp-200h]
.text:0000000000400A20 mov rdi, rax
.text:0000000000400A23 call _strlen
.text:0000000000400A23
.text:0000000000400A28 cmp rax, 10h
.text:0000000000400A2C jz short near ptr loc_400A3B+1
如图不断运行发现来到了字符串打印说明key错误的地方
重新运行,输入abcd...,够16位跳转后的汇编代码如下,发现提取了key第一个字符和'E'比较,合理猜测首字母为E
.text:0000000000400A3C loc_400A3C: ; CODE XREF: .text:0000000000400A2C↑j
.text:0000000000400A3C movzx eax, byte ptr [rbp-200h]
.text:0000000000400A43 cmp al, 45h ; 'E'
.text:0000000000400A45 jz short near ptr loc_400A54+1
修改ZF标志位跳转,如下,作用是提取第一个字符和最后一个字符相加,和0x98比较
.text:0000000000400A55 movzx eax, byte ptr [rbp-200h] ; CODE XREF: .text:0000000000400A45↑j
.text:0000000000400A5C movsx edx, al
.text:0000000000400A5F movzx eax, byte ptr [rbp-1F1h]
.text:0000000000400A66 movsx eax, al
.text:0000000000400A69 add eax, edx
.text:0000000000400A6B cmp eax, 9Bh
.text:0000000000400A70 jz short near ptr loc_400A7F+1
修改ZF标志位跳转,如下,发现回到了第二个字符比较,和前面逻辑一样
.text:0000000000400A80 movzx eax, byte ptr [rbp-1FFh] ; CODE XREF: .text:0000000000400A70↑j
.text:0000000000400A87 cmp al, 5Ah ; 'Z'
.text:0000000000400A89 jz short near ptr loc_400A98+1
.text:0000000000400A99 movzx eax, byte ptr [rbp-1FFh] ; CODE XREF: .text:0000000000400A89↑j
.text:0000000000400AA0 movsx edx, al
.text:0000000000400AA3 movzx eax, byte ptr [rbp-1F2h]
.text:0000000000400AAA movsx eax, al
.text:0000000000400AAD add eax, edx
.text:0000000000400AAF cmp eax, 9Bh
.text:0000000000400AB4 jz short near ptr loc_400AC3+1
所以循环个8轮,即可得到整个key:EZ9dmq4c8g9G7bAV
上面这种反抗反编译的做法可以称为花指令
https://www.52pojie.cn/thread-1536489-1-1.html
echo-server
ida反编译无法阅读代码,分析发现使用了花指令混淆,首先分析main
; Attributes: bp-based frame fuzzy-sp
; int __cdecl main()
main proc near
; __unwind {
push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
sub esp, 10h
mov eax, ds:stdin
mov dword ptr [esp+4], 0 ; buf
mov [esp], eax ; stream
call _setbuf
mov eax, ds:stdout
mov dword ptr [esp+4], 0 ; buf
mov [esp], eax ; stream
call _setbuf
mov ds:dword_804A088, 1
mov dword ptr [esp], offset s ; " **************\n "...
call _puts
call near ptr loc_80487C1+3 ;这里有些奇怪+3
mov eax, 0
leave
retn
; } // starts at 80488E0
main endp
call near ptr loc_80487C1+3
这条指令会来到下图,提示sp-analysis failed,说明存在花指令
call的地址很不正常,因此按U取消定义转为机器代码,这时伪代码可以看到跳转地址0x80487C1
int __cdecl sub_804875D(unsigned __int8 *a1, unsigned int a2)
{
unsigned __int8 *v2; // eax
char v3; // zf
int result; // eax
unsigned __int8 *v5; // [esp+18h] [ebp-10h]
unsigned int i; // [esp+1Ch] [ebp-Ch]
v5 = a1;
if ( a1 )
{
for ( i = 0; i < a2; ++i )
{
v2 = v5++;
printf("%02X", *v2);
}
}
else
{
printf("NULL");
}
result = putchar(10);
if ( !v3 )
{
if ( v3 )
JUMPOUT(0x80487C1);
}
return result;
}
双击查看0x80487C1,可以明显看到花指令
下方unwind处有大量数据,在unwind上按下C转为code,如下,发现前半部分恢复成功,但是中间jmp指令跳到了自身中间,因此按下U将其转为机器码
看到了0xEB可知其是一个花指令,因此在其后按下C转为c代码
依然出现了奇怪jmp地址,但是上面的jz恒成立跳转,因此不再转为机器代码,同时发现后面mov里引用了这个地址的数据,因此按D转为数据
再往后看发现还有一处标红,同样操作转为正确代码
重新检查发现下面代码还存在问题:
.text:08048848 A1 88 A0 04 08 mov eax, ds:dword_804A088
.text:0804884D 85 C0 test eax, eax
.text:0804884F 74 15 jz short loc_8048866 ; eax是否为0来判断
.text:0804884F
.text:08048851
.text:08048851 loc_8048851: ; CODE XREF: .text:08048857↓j
.text:08048851 66 B8 EB 05 mov ax, 5EBh
.text:08048855 31 C0 xor eax, eax
.text:08048857 74 F9 jz short near ptr loc_8048851+1 ; 一定会跳转
检查dword_804A088引用发现被硬编码为1,因此ZF=0,跳转不成立会接着运行loc_8048851
再由于xor eax, eax
值一定为0,所以ZF=1,跳转到loc_8048851+1,由于此时进入了原有指令中间,因此U再C重新生成汇编代码
.text:08048851 66 db 66h ; f
.text:08048852 ; ---------------------------------------------------------------------------
.text:08048852
.text:08048852 loc_8048852: ; CODE XREF: .text:08048857↓j
.text:08048852 B8 EB 05 31 C0 mov eax, 0C03105EBh
.text:08048857 74 F9 jz short loc_8048852
由上述代码可知会不断循环(ZF=1,mov不改变ZF)跳转。因此要想办法绕开这个永久循环,可以从最外边的JZ改为IMP(经试验JNZ不行!!!)
修改方法首先右键assemble看JMP对应机器代码EB,然后右键patch-change bytes(下图是改成JNZ的,懒得换图了)
最后对上面所有db 0E8h进行NOP,不然ida仍无法生成伪代码;此时F5可以看到反编译出的伪代码了
unsigned int sub_80487C4()
{
size_t v0; // eax
size_t v1; // eax
unsigned __int8 *v3; // [esp+14h] [ebp-74h]
unsigned __int8 s; // [esp+18h] [ebp-70h] BYREF
_BYTE v5[3]; // [esp+19h] [ebp-6Fh] BYREF
unsigned int v6; // [esp+7Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
memset(&s, 0, 0x14u);
read(0, &s, 0x14u);
if ( !strncmp((const char *)&s, "F1@gA", 5u) )
{
puts("You are very close! Now patch me~");
v0 = strlen((const char *)&s);
v3 = (unsigned __int8 *)MD5(v5, v0, 0);
sub_804875D(v3, 0x10u);
}
else
{
v1 = strlen((const char *)&s);
sub_804875D(&s, v1 - 1);
}
fflush(stdout);
return __readgsdword(0x14u) ^ v6;
}
最后还要说,什么破代码对环境需求这么高,死活搞不定环境,只能静态分析咋加密的,分析又晕了,直接拿F1@gA
md5加密不对,直到查看到平台上其他大佬的分析
反编译+patch后核心代码
memset(cStack_74,0,0x14); read(0,cStack_74,0x14); iVar2 = strncmp((char *)cStack_74,"F1@gA",5); if (iVar2 == 0) { puts("You are very close! Now patch me~"); sVar3 = strlen((char *)cStack_74); puVar4 = MD5(cStack_74 + 1,sVar3,(uchar *)0x0); FUN_0804875d_base16(puVar4,0x10); }
已知输入的字符串是"F1@gA",但是用read函数读取,末尾加了一个"n",因此strlen会返回6。因此MD5函数从输入字符串的第二字节开始取6字节,最后一个字节是memset填充的0,因此实际用来算MD5的字符串是
"1@gA\n\x00"
,这样得到的MD5才是程序算出的结果。有点反直觉。
然后最省事的python脚本
import hashlib
md5_object = hashlib.md5()
md5_object.update(b'1@gA\n\x00')
md5_result = md5_object.hexdigest()
print(md5_result.upper()) # F8C60EB40BF66919A77C4BD88D45DEF4
re5-packed-movement
首先upx脱壳(命令行)发现ida反编译全是mov,属于movfuscator混淆。找到了github项目demovfuscator,但是题目真的让人头大,好不容易配好了环境,放进去没法跑起来,也找不到原因,用的太少了,项目不太成熟
因此只能采用硬分析去看ida里的数据,因为movfuscator还难以处理复杂c代码,所以原始flag很可能明文存储
首先字符串搜索,查到wrong flag
查询引用竟然发现有70个,可以猜测是逐字符串对比
根据单个字符比对,必然有一行是mov xx, 'x'这样的格式,去找发现,mov R2开头的都是这种格式。Alt+B进行二进制搜索
结果正是flag:
使用idapython编写脚本提取(新版本的idc)
start = 0x080493DB
end = 0x0805F8CC
flag = ''
for i in range(end-start+1):
if get_wide_dword(start+i) == 0x206805c7:
flag += chr(get_wide_byte(start+i+6))
print(flag)
76号
upx+ida+花指令(找到有问题的jmp UC大法,nop掉0xeb),创建函数得到如下main代码
int __cdecl main()
{
int v0; // ebx
int v2; // [esp+18h] [ebp-Ch] BYREF
void *v3; // [esp+1Ch] [ebp-8h] BYREF
v2 = 0;
v3 = 0;
__printf_chk(1, "Password: ");
v0 = getline(&v3, &v2, stdin);
if ( v0 >= 0 && (v0 = sub_8048580((int)v3, 0)) != 0 )
__printf_chk(1, "Correct!");
else
__printf_chk(1, "Incorrect!");
free(v3);
return v0;
}
核心判断是sub_8048580函数,需要保证返回值不为0
_BOOL4 __cdecl sub_8048580(int a1, int a2)
{
char v3; // al
_BOOL4 result; // eax
char v5[128]; // [esp+Ch] [ebp-A0h] BYREF
unsigned int v6; // [esp+8Ch] [ebp-20h]
v6 = __readgsdword(0x14u);
while ( 1 ) // 要想办法使得return不为0
{
memset(v5, 0, sizeof(v5));
v3 = *(_BYTE *)(a1 + a2); // a2相当于偏移值或index,a1基地址
v5[(v3 + 64) % 128] = 1;
switch ( v3 ) // 看字符ascii值跳转
{
case '\n':
return a2 == 13 && v5[74] != 0; // 必须到达字符串结尾且返回不为0
case '0':
if ( a2 || !v5[112] ) // v3='0'时v5[112]=v5[('0'+64)%128]=1恒成立
return 0;
a2 = 1;
continue;
case '1':
if ( a2 == 14 && v5[113] )
goto LABEL_12;
return 0;
case '2':
if ( a2 == 20 && v5[114] )
goto LABEL_15;
return 0;
case '3':
if ( a2 != 89 || !v5[115] )
return 0;
a2 = 90;
continue;
case '4':
if ( a2 != 15 || !v5[116] )
return 0;
a2 = 16;
continue;
case '5':
if ( a2 != 14 || !v5[117] )
return 0;
LABEL_12:
a2 = 15;
continue;
case '6':
if ( a2 != 12 || !v5[118] )
return 0;
a2 = 13;
continue;
case '7':
if ( a2 != 5 || !v5[119] )
return 0;
a2 = 6;
continue;
case '8':
result = 0;
if ( v5[121] )
return a2 == 33 || a2 == 2;
return result;
case '9':
if ( a2 != 1 || !v5[121] )
return 0;
a2 = 2;
continue;
case 'a':
if ( a2 != 35 || !v5[33] )
return 0;
a2 = 36;
continue;
case 'b':
if ( a2 != 11 || !v5[34] )
return 0;
a2 = 12;
continue;
case 'c':
if ( a2 != 32 || !v5[33] )
return 0;
a2 = 33;
continue;
case 'd':
if ( a2 != 3 || !v5[36] )
return 0;
a2 = 4;
continue;
case 'e':
if ( a2 != 7 || !v5[37] )
return 0;
a2 = 8;
continue;
case 'f':
if ( !v5[38] || a2 != 8 && a2 != 4 )
return 0;
goto LABEL_53;
case 'g':
return a2 == 12 && v5[52] != 0;
case 'h':
if ( a2 != 13 || !v5[39] )
return 0;
a2 = 14;
continue;
case 'i':
if ( a2 != 9 || !v5[41] )
return 0;
a2 = 10;
continue;
case 'j':
if ( a2 != 10 || !v5[42] )
return 0;
a2 = 11;
continue;
case 'k':
return a2 == 12 && v5[43] != 0;
case 'l':
if ( a2 != 19 || !v5[44] )
return 0;
a2 = 20;
continue;
case 'm':
if ( a2 != 17 || !v5[45] )
return 0;
a2 = 18;
continue;
case 'n':
return a2 == 18 && v5[45] != 0;
case 'o':
if ( !v5[46] || a2 != 6 && a2 != 28 )
return 0;
LABEL_53:
++a2;
continue;
case 'p':
if ( a2 != 30 || !v5[48] )
return 0;
a2 = 31;
break;
case 'q':
if ( a2 != 29 || !v5[49] )
return 0;
a2 = 30;
break;
case 'r':
if ( a2 != 20 || !v5[50] )
return 0;
LABEL_15:
a2 = 21;
break;
case 's':
if ( a2 != 25 || !v5[51] )
return 0;
a2 = 26;
break;
case 't':
return a2 == 24 && v5[50] != 0;
case 'u':
if ( a2 != 26 || !v5[53] )
return 0;
a2 = 27;
break;
case 'v':
if ( a2 != 2 || !v5[54] )
return 0;
a2 = 3;
break;
case 'w':
if ( a2 != 6 || !v5[55] )
return 0;
a2 = 7;
break;
case 'x':
if ( a2 != 22 || !v5[56] )
return 0;
a2 = 23;
break;
case 'y':
if ( a2 != 23 || !v5[57] )
return 0;
a2 = 24;
break;
case 'z':
return a2 == 21 && v5[33] != 0;
default:
return 0;
}
}
}
如上sub_8048580是一个冗长的switch判断,但是已知开头a2=0,结尾为'n',可以手动推密码
开始a2=0,因此只能a1[a2]='0',其他的都会返回0,此时经过switch a2=1,再去找满足a2=1时不返回0的判断分支,依次类推
但是到最后一位时即'b'后,a2=12此时存在好几种同时满足条件的值,尝试后发现'g'是正确的(flag{09vdf7wefijbg})
题目有bug,存在多种情况可以得到password correct
Windows_Reverse2-DDCTF
重点是Aspack手动脱壳,发现之前也是ollydbg脱不了壳,使用x32dbg就可以
- 载入exe F9 运行直到pushad
- F8到下一句call,并下硬件断点(根据ESP定律)
- F9运行直到硬件断点处,此时看到的jne+push+ret是aspack的标志
- F8直到ret返回,此时是一个call+jump,此处为真正的程序入口点(OEP)
- 在此处dump内存到文件,x32dbg的Scylla用来dump的教程:https://www.cnblogs.com/-rvy-/p/16927032.html
首先插件打开scylla,先dump到一个新文件,再分别点击左侧的IAT Autosearch和Get Imports,最后点击右侧的Fix Dump和Dump,会自动保存为之前dump文件名+_SCY的文件,此时还不能正常运行,根据https://blog.csdn.net/weixin_53349587/article/details/123454162的做法,使用CFF Explorer可以修复程序重定位信息
- 打开ida发现已脱壳
int __cdecl main(int argc, const char **argv, const char **envp)
{
char Buffer[1024]; // [esp+8h] [ebp-C04h] BYREF
char v5[1024]; // [esp+408h] [ebp-804h] BYREF
char v6[1024]; // [esp+808h] [ebp-404h] BYREF
memset(v5, 0, sizeof(v5));
memset(v6, 0, sizeof(v6));
printf("input code:");
scanf("%s", v5);
if ( !sub_6611F0(v5) ) // 先验证v5
{
printf("invalid input\n");
exit(0);
}
sub_661240(v5, (int)v6); // v5加密成v6
memset(Buffer, 0, sizeof(Buffer));
sprintf(Buffer, "DDCTF{%s}", v6); // v6 = 'reverse+'
if ( !strcmp(Buffer, aDdctfReverse) ) // 'DDCTF{reverse+}'
printf("You've got it !!! %s\n", Buffer);
else
printf("Something wrong. Try again...\n");
return 0;
}
char __usercall sub_6611F0@<al>(const char *a1@<esi>)
{
int v1; // eax
int v2; // edx
int v3; // ecx
char v4; // al
v1 = strlen(a1);
v2 = v1;
if ( v1 && v1 % 2 != 1 ) // v1偶数,说明长度为偶数位
{
v3 = 0;
if ( v1 <= 0 )
return 1;
while ( 1 )
{
v4 = a1[v3];
if ( (v4 < '0' || v4 > '9') && (v4 < 'A' || v4 > 'F') ) // 字符范围
break;
if ( ++v3 >= v2 )
return 1; // 必须在这里返回
}
}
return 0;
}
分析sub_661240函数,a1是我们输入的字符串,a2是准备接收的字符串
int __usercall sub_661240@<eax>(const char *a1@<esi>, void *a2)
{
int v2; // edi
int v3; // edx
char v4; // bl
char v5; // al
char v6; // al
unsigned int v7; // ecx
char v9; // [esp+Bh] [ebp-405h]
char v10[1024]; // [esp+Ch] [ebp-404h] BYREF
v2 = strlen(a1);
memset(v10, 0, sizeof(v10));
v3 = 0;
if ( v2 > 0 )
{
v4 = v9;
do // 都是字符串转十六进制数字
{
v5 = a1[v3]; // 奇数位
if ( (unsigned __int8)(v5 - '0') > 9u )
{
if ( (unsigned __int8)(v5 - 'A') <= 5u )
v9 = v5 - 55;
}
else
{
v9 = a1[v3] - 48;
}
v6 = a1[v3 + 1]; // 偶数位
if ( (unsigned __int8)(v6 - '0') > 9u )
{
if ( (unsigned __int8)(v6 - 'A') <= 5u )
v4 = v6 - 55;
}
else
{
v4 = a1[v3 + 1] - 48;
}
v7 = (unsigned int)v3 >> 1; // 0...len/2-1
v3 += 2;
v10[v7] = v4 | (16 * v9); // 相当于16*v9+v4
}
while ( v3 < v2 );
}
return sub_661000(v2 / 2, a2);
}
由上分析可知,v10长度为我们输入的一半,每次取两个字符做一个加密运算,但是分析sub_661000发现只传入了一半长度值和未被赋值的a2
结合代码和汇编发现v10被赋值给了ecx,同样传到了函数中,只是ida未识别出来
如下图,ecx正是v2,赋给了v4,后面都是通过v4进行赋值比较
int __cdecl sub_661000(int a1, void *a2)
{
char *v2; // ecx
int v3; // ebp
char *v4; // edi
int v5; // esi
unsigned __int8 v6; // bl
int i; // esi
int v8; // edi
int v9; // edi
size_t v10; // esi
void *v11; // edi
const void *p_Src; // eax
unsigned __int8 v14; // [esp+14h] [ebp-38h] BYREF
unsigned __int8 v15; // [esp+15h] [ebp-37h]
unsigned __int8 v16; // [esp+16h] [ebp-36h]
char v17; // [esp+18h] [ebp-34h]
char v18; // [esp+19h] [ebp-33h]
char v19; // [esp+1Ah] [ebp-32h]
char j; // [esp+1Bh] [ebp-31h]
void *v21; // [esp+1Ch] [ebp-30h]
char v22[4]; // [esp+20h] [ebp-2Ch] BYREF
void *Src; // [esp+24h] [ebp-28h] BYREF
size_t Size; // [esp+34h] [ebp-18h]
unsigned int v25; // [esp+38h] [ebp-14h]
int v26; // [esp+48h] [ebp-4h]
v3 = a1;
v4 = v2;
v21 = a2;
std::string::string(v22);
v5 = 0;
v26 = 0;
if ( a1 )
{
do
{
*(&v14 + v5) = *v4;
v6 = v15;
++v5;
--v3;
++v4;
if ( v5 == 3 )
{
v17 = v14 >> 2;
v18 = (v15 >> 4) + 16 * (v14 & 3);
v19 = (v16 >> 6) + 4 * (v15 & 0xF);
j = v16 & 0x3F;
for ( i = 0; i < 4; ++i )
std::string::operator+=(v22, (unsigned __int8)byte_663020[(unsigned __int8)*(&v17 + i)] ^ 0x76);
v5 = 0;
}
}
while ( v3 );
if ( v5 )
{
if ( v5 < 3 )
{
memset(&v14 + v5, 0, 3 - v5);
v6 = v15;
}
v18 = (v6 >> 4) + 16 * (v14 & 3);
v17 = v14 >> 2;
v19 = (v16 >> 6) + 4 * (v6 & 0xF);
v8 = 0;
for ( j = v16 & 0x3F; v8 < v5 + 1; ++v8 )
std::string::operator+=(v22, (unsigned __int8)byte_663020[(unsigned __int8)*(&v17 + v8)] ^ 0x76); // byte_663020为64位
if ( v5 < 3 )
{
v9 = 3 - v5;
do
{
std::string::operator+=(v22, '='); // base64的特征
--v9;
}
while ( v9 );
}
}
}
v10 = Size;
v11 = v21;
memset(v21, 0, Size + 1);
p_Src = Src;
if ( v25 < 0x10 )
p_Src = &Src;
memcpy(v11, p_Src, v10);
v26 = -1;
return std::string::~string();
}
发现函数应该是base64加密,同时编码表做了替换,但是异或0x76即可变成正常编码表
最后只需base64解密'reverse+',中间看起来复杂,实际上是混淆的结果,得到十六进制转为大写(题目输入限制了范围)