我就是一条懒狗,天天白天睡觉。
游戏辅助
本来应该是签到题,但是实际上是脑洞题。
出题人本意可能是考察远程注入,但是题目描述太过于模糊,显得太过于脑洞了。
给出两个程序,一个是游戏辅助(假的,仅含注册码的判断的相关代码),一个是破解这个游戏辅助的补丁。
运行游戏辅助时要求输入注册码,随便输入了一个,不正确。
然后打开补丁,等破解完成后,再次随便输入一个注册码,正确。
补丁的窗口输出:flag{md5(dec(What_you_found))}
游戏辅助的代码很简单,明文比较注册码是否和1_am_n0t_f1ag
相等。
游戏复制窗口中注册码输入1_am_n0t_f1ag
,可以成功注册,没看懂上面的dec()是啥意思,我以为是decrypt,解密的意思,但是比较过程是明文比较的,也没有加密的过程啊。后来题目描述更新了,说是dec是十进制的意思。我无语了。。。
将1_am_n0t_f1ag
取md5提交,并不正确。
正确的What_you_found
不在游戏辅助里面,而是在补丁里面。
看一下游戏补丁的部分代码:
WriteProcessMemory(v4, (char *)&loc_401227 + 1, &Buffer, 2u, 0);
// 向目标进程的内存0x401228处写入
// &Buffer开始的2个字节的数据,0x90 0x90,即两个nop
WriteProcessMemory(v4, (char *)&loc_401014 + 2, &v9, 5u, 0);
// 向目标进程的内存0x401016处写入
// &v9开始的5个字节的数据,B8 A9 F1 00 00
// 即mov eax, 0x0000f1a9
WriteProcessMemory(v4, (char *)&loc_401019 + 2, &v7, 5u, 0);
// 向目标进程的内存0x40101b处写入
// &v7开始的5个字节的数据,BB 38 6E 0F 02
// 即mov ebx, 0x020f6e38
第2个和第3个WriteProcessMemory分别写入5个数据,拿第3个WriteProcessMemory举个例子:
v7 = 0x0F6E38BB;
v8 = 0x00000002;
v7是[bp-14],v8是[bp-10h]
依次写入BB 38 6E 0F 02
上图来自本题一血的炜昊大佬:
为什么运行游戏补丁后,游戏辅助中随便输入一个注册码就能通过检测了呢?我们再来看一下游戏辅助中的0x401228附近。
上上图中,游戏补丁向游戏复制的内存的0x401228中写入了nop nop,正好件将jnz short loc_40125c覆盖,loc_40125C是注册失败的分支。覆盖后,无论输入的字符串和注册码的验证结果是否相同,都不会跳转至失败的分支,而是继续往下运行,即注册成功的分支。
flag是啥?是md5(20f6e38),20fe38是游戏补丁中向游戏辅助的内存0x40101b处写入的数据。别问我问啥偏偏是这个数据,我也不知道,要是知道的话,我当时就做出来了。
考察远程注入这个知识点倒是挺好的,但是你倒是说明白flag到底是啥呀,What you found
,我found的数据可多了,我怎么知道是哪个。。。
simple
看起来似乎很简单,输入长度为16的字符串,进行32轮tea加密,比较密文。
虚假的解密脚本
v8, v9是明文->密文,v10每轮要减去0x61C8864661C88647或者加上0x9E3779B99E3779B9,注意需要手动维持溢出。v3和v4是中间变量,直接化简去掉即可。
轻松写出解密脚本:
def ull(n):
return n & 0xffffffffffffffff
def get_v10(i):
v10 = 0
for i in range(i+1):
#v10 = ull(v10 - 0x61C8864661C88647)
v10 = ull(v10 + 0x9E3779B99E3779B9)
return v10
def hex2bytes(h):
import struct
b = b''
for i in range(7,-1,-1):
b += struct.pack('b', int(h[2+i*2:2+i*2+2], 16))
print(b)
return b
def md5(b):
import hashlib
m = hashlib.md5()
m.update(b)
mm = m.hexdigest()
print(mm)
def solve():
v8 = 0xC3D3413C4B6381F9
v9 = 0x8A6047975A08CCBA
for i in range(31, -1, -1):
v10 = get_v10(i)
v9 = ull(v9 - ull(ull(v8+v10) ^ ull(16*v8) ^ ull(v8>>5)))
v8 = ull(v8 - ull(ull(v9+v10) ^ ull(16*v9) ^ ull(v9>>5)))
print(hex(v8), hex(v9), hex(v10))
b = hex2bytes(hex(v8)) + hex2bytes(hex(v9))
print(b)
md5(b)
solve()
解出的输入应该为flag_is_not_here
,按照题目要求取md5提交,不正确。啊哈,怎么回事??
查看交叉引用,发现这个函数(sub_402436)并没有被call,只是mov而已:
先去call的sub_402880,这个函数贼复杂,直接动调吧。后来才知道,这个函数类似于脱壳,将代码解码出来。
动调
本想动调看看哪里有问题,对于这个ELF64,ida7.0不知道为啥动调失败,好在最后发现ida6.8可以动调它。
经过多次动调总结:在4110f0处按f4,输入长度16的字符串
图1是按f4前,图2是按f4并输入长度为16的字符串后。
按3次f8,即可来到解压出的新代码处,是个堆(heap):
其中有些奇奇怪怪的汇编指令:
dec eax频繁出现,而且这个ELF64的程序,汇编指令中没有出现任何一个rax之类的。出大问题。
我的猜测是:
dec eax
mov eax, [eax+8]
的机器码同样可以反汇编成:
mov rax, [rax+8]
猜测的对不对我不清楚,反正我拿capstone反汇编的结果验证了我的猜测。
capstone 机器码->汇编代码
ida反汇编的结果太影响理解了。
idc脚本提取出这个堆里的代码:
static main()
{
auto i,fp;
fp = fopen("d:\\dump","wb");
auto start = 0x1fd4b20;
auto size = 0x1fd4d00 - 0x1fd4b20;
for(i=start;i<start+size;i++)
{
fputc(Byte(i),fp);
}
fp.close();
}
使用capstone反汇编:
from capstone import *
def dis(b):
shellcode = b
md = Cs(CS_ARCH_X86, CS_MODE_64)
disasm = ''
for i in md.disasm(shellcode, 0x00):
#print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
disasm += "\t%s\t%s\n" %(i.mnemonic, i.op_str)
return disasm
#with open(r'd:\dump', 'rb')as f:
# b = f.read()
b = b'UH\x89\xe5H\x83\xec`dH\x8b\x04%(\x00\x00\x00H\x89E\xf81\xc0H\x89u\xb8\xc6E\xeb\x84\xc6E\xec\xb2\xc6E\xed\xa8\xc6E\xee\xaf\xc6E\xef\xfd\xc6E\xf0\xb4\xc6E\xf1\xb3\xc6E\xf2\xad\xc6E\xf3\xa8\xc6E\xf4\xa9\xc6E\xf5\xfd\xc6E\xf6\xe7\xc6E\xf7\xfd\xc6E\xe1\x9c\xc6E\xe2\x87\xc6E\xe3\x89\xc6E\xe4\x86\xc6E\xe5\x9a\xc6E\xe6\x88\xc6E\xe7\x8d\xc6E\xe8\x90\xc6E\xe9\x91\xc6E\xea\x98\xc7E\xa8\x00\x00\x00\x00\x83}\xa8\x0c\x7f\x1d\x8bE\xa8H\x98\x0f\xb6D\x05\xeb\x83\xf0\xdd\x0f\xb6\xc0\x89\xc7\xe8\x85\xb4\xe6\xfe\x83E\xa8\x01\xeb\xddH\x8bE\xb8H\x89\xc7\xe8s\xb2\xe6\xfeH\x8bE\xb8H\x8b\x00H\x89E\xc0H\x8bE\xb8H\x8b@\x08H\x89E\xc8H\xc7E\xd0\x00\x00\x00\x00H\xb8\x8aJ\x04\xad\x8aJ\x04\xbdH\x89E\xd8H\xb83333333#H1E\xd8\xc7E\xac\x00\x00\x00\x00\x83}\xac\x1f\x7fXH\x8bE\xc8H\xc1\xe0\x04H\x89\xc2H\x8bE\xc8H\xc1\xe8\x05H1\xc2H\x8bE\xc8H\x01\xd0H3E\xd0H\x01E\xc0H\x8bE\xd8H\x01E\xd0H\x8bE\xc0H\xc1\xe0\x04H\x89\xc2H\x8bE\xc0H\xc1\xe8\x05H1\xc2H\x8bE\xc0H\x01\xd0H3E\xd0H\x01E\xc8\x83E\xac\x01\xeb\xa2H\xb8\xaa\x92\x06\x94Y\xb4K\xadH9E\xc0uFH\xb8\xc9\xc6G\xc4N\xfdeVH9E\xc8u6\xc7E\xb0\x00\x00\x00\x00\x83}\xb0\x04\x7f\x1d\x8bE\xb0H\x98\x0f\xb6D\x05\xe1\x83\xf0\xee\x0f\xb6\xc0\x89\xc7\xe8\x8f\xb3\xe6\xfe\x83E\xb0\x01\xeb\xdd\xbf\n\x00\x00\x00\xe8\x7f\xb3\xe6\xfe\xeb3\xc7E\xb4\x00\x00\x00\x00\x83}\xb4\x04\x7f\x1c\x8bE\xb4H\x98\x0f\xb6D\x05\xe6\xf7\xd0\x0f\xb6\xc0\x89\xc7\xe8Z\xb3\xe6\xfe\x83E\xb4\x01\xeb\xde\xbf\n\x00\x00\x00\xe8J\xb3\xe6\xfe\xbf\x00\x00\x00\x00\xe8\xd0\x97\xe6\xfe'
d = dis(b)
print(d)
输出的汇编代码为:
push rbp
mov rbp, rsp
sub rsp, 0x60
mov rax, qword ptr fs:[0x28]
mov qword ptr [rbp - 8], rax
xor eax, eax
mov qword ptr [rbp - 0x48], rsi
mov byte ptr [rbp - 0x15], 0x84
mov byte ptr [rbp - 0x14], 0xb2
mov byte ptr [rbp - 0x13], 0xa8
mov byte ptr [rbp - 0x12], 0xaf
mov byte ptr [rbp - 0x11], 0xfd
mov byte ptr [rbp - 0x10], 0xb4
mov byte ptr [rbp - 0xf], 0xb3
mov byte ptr [rbp - 0xe], 0xad
mov byte ptr [rbp - 0xd], 0xa8
mov byte ptr [rbp - 0xc], 0xa9
mov byte ptr [rbp - 0xb], 0xfd
mov byte ptr [rbp - 0xa], 0xe7
mov byte ptr [rbp - 9], 0xfd
mov byte ptr [rbp - 0x1f], 0x9c
mov byte ptr [rbp - 0x1e], 0x87
mov byte ptr [rbp - 0x1d], 0x89
mov byte ptr [rbp - 0x1c], 0x86
mov byte ptr [rbp - 0x1b], 0x9a
mov byte ptr [rbp - 0x1a], 0x88
mov byte ptr [rbp - 0x19], 0x8d
mov byte ptr [rbp - 0x18], 0x90
mov byte ptr [rbp - 0x17], 0x91
mov byte ptr [rbp - 0x16], 0x98
mov dword ptr [rbp - 0x58], 0
cmp dword ptr [rbp - 0x58], 0xc
jg 0xa1
mov eax, dword ptr [rbp - 0x58]
cdqe
movzx eax, byte ptr [rbp + rax - 0x15]
xor eax, 0xffffffdd
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x58], 1
jmp 0x7e
mov rax, qword ptr [rbp - 0x48]
mov rdi, rax
call 0xfffffffffee6b320
mov rax, qword ptr [rbp - 0x48]
mov rax, qword ptr [rax]
mov qword ptr [rbp - 0x40], rax
mov rax, qword ptr [rbp - 0x48]
mov rax, qword ptr [rax + 8]
mov qword ptr [rbp - 0x38], rax
mov qword ptr [rbp - 0x30], 0
movabs rax, 0xbd044a8aad044a8a
mov qword ptr [rbp - 0x28], rax
movabs rax, 0x2333333333333333
xor qword ptr [rbp - 0x28], rax
mov dword ptr [rbp - 0x54], 0
cmp dword ptr [rbp - 0x54], 0x1f
jg 0x14d
mov rax, qword ptr [rbp - 0x38]
shl rax, 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x38]
shr rax, 5
xor rdx, rax
mov rax, qword ptr [rbp - 0x38]
add rax, rdx
xor rax, qword ptr [rbp - 0x30]
add qword ptr [rbp - 0x40], rax
mov rax, qword ptr [rbp - 0x28]
add qword ptr [rbp - 0x30], rax
mov rax, qword ptr [rbp - 0x40]
shl rax, 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x40]
shr rax, 5
xor rdx, rax
mov rax, qword ptr [rbp - 0x40]
add rax, rdx
xor rax, qword ptr [rbp - 0x30]
add qword ptr [rbp - 0x38], rax
add dword ptr [rbp - 0x54], 1
jmp 0xef
movabs rax, 0xad4bb459940692aa
cmp qword ptr [rbp - 0x40], rax
jne 0x1a3
movabs rax, 0x5665fd4ec447c6c9
cmp qword ptr [rbp - 0x38], rax
jne 0x1a3
mov dword ptr [rbp - 0x50], 0
cmp dword ptr [rbp - 0x50], 4
jg 0x197
mov eax, dword ptr [rbp - 0x50]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1f]
xor eax, 0xffffffee
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x50], 1
jmp 0x174
mov edi, 0xa
call 0xfffffffffee6b520
jmp 0x1d6
mov dword ptr [rbp - 0x4c], 0
cmp dword ptr [rbp - 0x4c], 4
jg 0x1cc
mov eax, dword ptr [rbp - 0x4c]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1a]
not eax
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x4c], 1
jmp 0x1aa
mov edi, 0xa
call 0xfffffffffee6b520
mov edi, 0
call 0xfffffffffee699b0
capstone反汇编的结果中,跳转分支没有明确地指示,call的函数也和ida中的表示不太一样,不过对照着看也不是不行。
汇编分析
分析结果大体如下:
push rbp
mov rbp, rsp
sub rsp, 0x60
mov rax, qword ptr fs:[0x28]
mov qword ptr [rbp - 8], rax
xor eax, eax
mov qword ptr [rbp - 0x48], rsi
mov byte ptr [rbp - 0x15], 0x84 //Your input :
mov byte ptr [rbp - 0x14], 0xb2 //func: s[i] ^ 0xdd
mov byte ptr [rbp - 0x13], 0xa8
mov byte ptr [rbp - 0x12], 0xaf
mov byte ptr [rbp - 0x11], 0xfd
mov byte ptr [rbp - 0x10], 0xb4
mov byte ptr [rbp - 0xf], 0xb3
mov byte ptr [rbp - 0xe], 0xad
mov byte ptr [rbp - 0xd], 0xa8
mov byte ptr [rbp - 0xc], 0xa9
mov byte ptr [rbp - 0xb], 0xfd
mov byte ptr [rbp - 0xa], 0xe7
mov byte ptr [rbp - 9], 0xfd
mov byte ptr [rbp - 0x1f], 0x9c //right
mov byte ptr [rbp - 0x1e], 0x87 //func: s[i] ^ 0xee
mov byte ptr [rbp - 0x1d], 0x89
mov byte ptr [rbp - 0x1c], 0x86
mov byte ptr [rbp - 0x1b], 0x9a
mov byte ptr [rbp - 0x1a], 0x88 //wrong
mov byte ptr [rbp - 0x19], 0x8d //func: ~s[i]
mov byte ptr [rbp - 0x18], 0x90
mov byte ptr [rbp - 0x17], 0x91
mov byte ptr [rbp - 0x16], 0x98
mov dword ptr [rbp - 0x58], 0
cmp dword ptr [rbp - 0x58], 0xc
jg 0xa1 //跳出本段代码
mov eax, dword ptr [rbp - 0x58]
cdqe
movzx eax, byte ptr [rbp + rax - 0x15] //输出"Your input : %16c"
xor eax, 0xffffffdd
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520 //即ida中的sub_412040,输出一个字符
add dword ptr [rbp - 0x58], 1
jmp 0x7e
mov rax, qword ptr [rbp - 0x48]
mov rdi, rax
call 0xfffffffffee6b320 //goto 本段代码开始处
mov rax, qword ptr [rbp - 0x48] //输入字符串赋值给var_40, var_38
mov rax, qword ptr [rax] //即[rbp - 0x40] ~ [rbp - 0x31]
mov qword ptr [rbp - 0x40], rax
mov rax, qword ptr [rbp - 0x48]
mov rax, qword ptr [rax + 8]
mov qword ptr [rbp - 0x38], rax
mov qword ptr [rbp - 0x30], 0 //v28 = 0xbd044a8aad044a8a ^ 0x2333333333333333
movabs rax, 0xbd044a8aad044a8a
mov qword ptr [rbp - 0x28], rax
movabs rax, 0x2333333333333333
xor qword ptr [rbp - 0x28], rax
mov dword ptr [rbp - 0x54], 0
cmp dword ptr [rbp - 0x54], 0x1f //循环32次
jg 0x14d //跳出本段代码
mov rax, qword ptr [rbp - 0x38]
shl rax, 4 //v9 << 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x38]
shr rax, 5 //v9 >> 5
xor rdx, rax //(v9 << 4) ^ (v9 >>5)
mov rax, qword ptr [rbp - 0x38]
add rax, rdx //v9 + ((v9 << 4) ^ (v9 >>5))
xor rax, qword ptr [rbp - 0x30] //(v9 + ((v9 << 4) ^ (v9 >>5))) ^ v30
add qword ptr [rbp - 0x40], rax //v8 = v8 + ((v9 + ((v9 << 4) ^ (v9 >>5))) ^ v30)
mov rax, qword ptr [rbp - 0x28]
add qword ptr [rbp - 0x30], rax //v30 = v30 + v28
mov rax, qword ptr [rbp - 0x40]
shl rax, 4 //v8 << 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x40]
shr rax, 5 //v8 >> 5
xor rdx, rax //(v8 << 4) ^ (v8 >> 5)
mov rax, qword ptr [rbp - 0x40]
add rax, rdx //v8 + ((v8 << 4) ^ (v8 >> 5))
xor rax, qword ptr [rbp - 0x30] //(v8 + ((v8 << 4) ^ (v8 >> 5))) ^ v30
add qword ptr [rbp - 0x38], rax //v9 = v9 + ((v8 + ((v8 << 4) ^ (v8 >> 5))) ^ v30)
add dword ptr [rbp - 0x54], 1
jmp 0xef //跳回本段代码开始处
movabs rax, 0xad4bb459940692aa //judge
cmp qword ptr [rbp - 0x40], rax
jne 0x1a3 //goto wrong
movabs rax, 0x5665fd4ec447c6c9
cmp qword ptr [rbp - 0x38], rax
jne 0x1a3 //goto wrong
mov dword ptr [rbp - 0x50], 0 //输出right
cmp dword ptr [rbp - 0x50], 4
jg 0x197
mov eax, dword ptr [rbp - 0x50]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1f]
xor eax, 0xffffffee
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x50], 1
jmp 0x174
mov edi, 0xa
call 0xfffffffffee6b520
jmp 0x1d6 //goto exit
mov dword ptr [rbp - 0x4c], 0 //输出wrong
cmp dword ptr [rbp - 0x4c], 4
jg 0x1cc
mov eax, dword ptr [rbp - 0x4c]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1a]
not eax
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x4c], 1
jmp 0x1aa
mov edi, 0xa
call 0xfffffffffee6b520
mov edi, 0 //exit
call 0xfffffffffee699b0
Your input:
,right
,wrong
是如何输出的呢?
以right为例:
movzx eax, byte ptr [rbp + rax - 0x1f]
xor eax, 0xffffffee
movzx eax, al
rax是索引,rbp - 0x1f开始的5个数据对应right。
取出数据后与0xee异或,比如第一个数据[rbp - 0x1f] = 0x9c,chr(0x9c ^ 0xee) = 'r'
后面的4个数据也是这样处理,可得字符串right。
加密流程
下面看一下整体代码的流程,配合多次动调分析:
1.首先是Your Input : rightwrong
这些字符所对应的加密数据入栈(数据经过解密处理后才可以得到明文)
2.输出Your Input :
3.将input传值到[rbp - 0x40] ~ [rbp - 0x31]
,我将var_40记为v8,var_38记为v9,以便和ida对应。
4.v28 = 0xbd044a8aad044a8a ^ 0x2333333333333333
5.tea加密,好像是变种,我不清楚。管它是原版还是变种,直接逆它的逻辑就行。共32次循环,每轮循环:
v8 = v8 + ((v9 + ((v9 << 4) ^ (v9 >>5))) ^ v30)
v30 += 0x2333333333333333 ^ 0xbd044a8aad044a8a
v9 = v9 + ((v8 + ((v8 << 4) ^ (v8 >> 5))) ^ v30)
6.判断v8 == 0xad4bb459940692aa
, v9 == 0x5665fd4ec447c6c9
7.输出right或是wrong
8.exit
真正的解密脚本
def ull(n):
return n & 0xffffffffffffffff
def hex2bytes(h):
import struct
b = b''
for i in range(7,-1,-1): #这里不是for i in range(8),也把我小坑了一把
b += struct.pack('b', int(h[2+i*2:2+i*2+2], 16))
print(b)
return b
def md5(b):
import hashlib
m = hashlib.md5()
m.update(b)
mm = m.hexdigest()
print(mm)
def get_v30(i):
v10 = 0
for i in range(i):
v10 = ull(v10 + ull(0x2333333333333333 ^ 0xbd044a8aad044a8a)) #尼玛+和^的运算优先级把我给坑死了,大哭
return v10
def solve():
v8 = 0xad4bb459940692aa
v9 = 0x5665fd4ec447c6c9
for i in range(31, -1, -1):
v30 = get_v30(i+1)
v9 = ull(v9 - ull(ull(ull(v8 + ull(ull(v8 << 4) ^ ull(v8 >> 5))) ^ v30)))
v30 = get_v30(i)
v8 = ull(v8 - ull(ull(v9 + ull(ull(v9 << 4) ^ ull(v9 >>5))) ^ v30))
print(hex(v8), hex(v9), hex(v30))
b = hex2bytes(hex(v8)) + hex2bytes(hex(v9))
print(b)
md5(b)
solve()
# 输入为:ae569d8654d7b54e
# 按照题目要求,flag为其md5:6efa713d07cd5bc15d70da8c6b120e5d
注意大小端,注意+
和^
的运算优先级。注意v30的改变是在v8和v9中间。