TSCTF-J [SUCTF 2019] SignIn

[SUCTF 2019] SignIn

IDA载入,主函数如下
请添加图片描述

sub_96A

请添加图片描述
看不懂只能写脚本实现一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<string.h>
#define _DWORD int
int main()
{
int result;
char a1[100];
scanf("%s",a1);
char a2[100];
char byte_202010[]={0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61,0x62, 0x63, 0x64, 0x65, 0x66};
int v3; // [rsp+18h] [rbp-18h]
int i; // [rsp+1Ch] [rbp-14h]

v3 = 0;
for ( i = 0; ; i += 2 )
{
result = strlen(a1);
if ( v3 >= result )
break;
a2[i] = byte_202010[a1[v3] >> 4];
a2[i+1]= byte_202010[a1[v3++] & 0xF];
}
printf("%s",a2);
}

发现是一个把字符串转16进制的函数。后面是RSA加密

1
2
3
4
5
__gmpz_init_set_str(v7, "ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35", 16LL);//用来与运算结果比较,应该是密文C
__gmpz_init_set_str(v6, v9, 16LL);//明文M
__gmpz_init_set_str(v4, "103461035900816914121390101299049044413950405173712170434161686539878160984549", 10LL);//公共模数N
__gmpz_init_set_str(v5, "65537", 10LL);//公钥E
__gmpz_powm(v6, v6, v5, v4);//M^E%N

RSA解密

首先分解模数N得到P,Q。
请添加图片描述
有了P、Q、E就可以通过求模逆元计算出私钥D,从而得到明文。

1
2
3
4
5
6
7
8
9
10
import  gmpy2
c=0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
n=103461035900816914121390101299049044413950405173712170434161686539878160984549
p=282164587459512124844245113950593348271
q=366669102002966856876605669837014229419
e=65537
d=gmpy2.invert(e,(p-1)*(q-1))
m = gmpy2.powmod(c,d,n)
print(m)
#185534734614696481020381637136165435809958101675798337848243069

转字符串得到flag

符号执行(二)

学习如何优化angr脚本,题目defcamp_r100

指定入口地址

这种方法可以避免angr反复执行程序初始化操作,让程序从main开始运行

1
state=p.factory.blank_state(addr=0x4007E8)

改写库函数

载入程序时,阻止angr自动载入依赖的库函数

1
p = angr.Project("r100",auto_load_libs=False)

auto_load_libs默认为True,但angr对libc库做了优化,不需要再加载。
输出函数对算法的分析没有影响,可以让其直接返回。

1
p.hook_symbol('printf',angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](),replace=True)

hook掉printf使之返回[‘stubs’][‘ReturnUnconstrained’](一个无约束的符号)
本题中,输入的flag长度为12,可以直接hook掉fgets函数在应该指向的内存地址放置12字节的输入。

1
2
3
4
5
6
7
class my_fgets(angr.SimProcedure):
def run(self,s,num,f):
simfd=self.state.posix.get_fd(0)
data,real_size=simfd.read_data(12)
self.state.memory.store(s,data)
return 12
p.hook_symbol('fgets',my_fgets(),replace=True)

baby-re(DEFCON 2016 quals)

angr学习
题目链接 https://github.com/angr/angr-doc/tree/master/examples/defcon2016quals_baby-re

IDA打开分析,程序要求输入12个数字,经过CheckSolution进行检查
请添加图片描述
进入CheckSolution,发现函数非常复杂且无法反编译,编写符号执行脚本解题。

1
2
3
4
5
6
import angr
p=angr.Project("baby-re")
s=p.factory.simulation_manager(p.factory.full_init_state())
s.one_active.options.add(angr.options.LAZY_SOLVES)
s.explore(find=0x40292C,avoid=0x402941)
print(s.found[0].posix.dumps(0))

运行结果
请添加图片描述

得到flag
请添加图片描述

总结:虽然成功解题但脚本运行效率太低,官方脚本中hook了库函数来提高效率,将在以后学习。

官方脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python2
from __future__ import print_function

# Authors: David Manouchehri <manouchehri@protonmail.com>
# P1kachu <p1kachu@lse.epita.fr>
# Audrey Dutcher <audrey@rhelmot.io>
# DEFCON CTF Qualifier 2016
# Challenge: baby-re
# Write-up: http://hack.carleton.team/2016/05/21/defcon-ctf-qualifier-2016-baby-re/
# Runtime: ~15 seconds (thanks lazy solves!)

import angr
import claripy

def main():
proj = angr.Project('./baby-re', auto_load_libs=False)

# let's provide the exact variables received through the scanf so we don't have to worry about parsing stdin into a bunch of ints.
flag_chars = [claripy.BVS('flag_%d' % i, 32) for i in range(13)]
class my_scanf(angr.SimProcedure):
def run(self, fmt, ptr): # pylint: disable=arguments-differ,unused-argument
self.state.mem[ptr].dword = flag_chars[self.state.globals['scanf_count']]
self.state.globals['scanf_count'] += 1

proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)

sm = proj.factory.simulation_manager()
sm.one_active.options.add(angr.options.LAZY_SOLVES)
sm.one_active.globals['scanf_count'] = 0

# search for just before the printf("%c%c...")
# If we get to 0x402941, "Wrong" is going to be printed out, so definitely avoid that.
sm.explore(find=0x4028E9, avoid=0x402941)

# evaluate each of the flag chars against the constraints on the found state to construct the flag
flag = ''.join(chr(sm.one_found.solver.eval(c)) for c in flag_chars)
return flag

def test():
assert main() == 'Math is hard!'

if __name__ == '__main__':
print(main())

符号执行

符号执行

简单的来说,符号执行就是在运行程序时,用符号来替代真实值。符号执行相较于真实值执行的优点在于,当使用真实值执行程序时,我们能够遍历的程序路径只有一条,而使用符号进行执行时,由于符号是可变的,我们就可以利用这一特性,尽可能的将程序的每一条路径遍历,这样的话,必定存在至少一条能够输出正确结果的分支,每一条分支的结果都可以表示为一个离散关系式,使用约束求解引擎即可分析出正确结果,这就是符号执行的简单阐述。

angr

angr是适用范围最广的符号执行工具,pip install angr即可安装。

defcamp_r100

请添加图片描述
请添加图片描述
请添加图片描述
程序的逻辑是输入一个字符串,在sub_4006FD中检验
官方脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import angr

def main():
p = angr.Project("r100")
simgr = p.factory.simulation_manager(p.factory.full_init_state())
simgr.explore(find=0x400844, avoid=0x400855)

return simgr.found[0].posix.dumps(0).strip(b'\0\n')

def test():
assert main().startswith(b'Code_Talkers')

if __name__ == '__main__':
print(main())

p = angr.Project(“r100”)
装载需要分析的程序,这样操作之后,p就是你的主二进制文件以及它依赖的所有库的代表。

simgr = p.factory.simulation_manager(p.factory.full_init_state())
创建一个simulation_manager用来模拟执行并设置好状态

simgr.explore(find=0x400844, avoid=0x400855)
让程序运行到0x400844(“Nice”)处,避免运行到0x400844(“Incorrect password!”)处。使用explore方法寻找到达到目标的路径。

return simgr.found[0].posix.dumps(0).strip(b’\0\n’)
found成员是一个表,存储着所有找到的路径,成员数据类型是SimState,代表程序的状态,posix代表程序通过POSIX规范中的接口获取的数据。posix.dumps(0)获取的是标准输入中的数据。

PE结构

PE结构

PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,它们之间的区别只是语义上的,它们使用完全相同的PE格式,唯一的区别就是用一个字段标识出是EXE还是DLL。

PE格式定义在头文件winnt.h中,64位PE格式只是对32位做了简单的扩展,结构几乎一样。
在这里插入图片描述

MS-DOS头部

每个PE文件都是以一个DOS程序开始的。
PE文件的第一个字节起始于一个传统的MS-DOS头部,被称为IMAGE_DOS_HEADER。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

e_magic:一个WORD类型0x4D5A,因此可执行文件必须都是’MZ’开头。

e_lfanew:为32位可执行文件扩展的域,用来表示DOS头之后的NT头相对文件起始地址的偏移。

PE文件头

PE相关结构NT映像头,PE文件执行时,装载器在IMAGE_DOS_HEADER中的e_lfanew中找到PE头的起始偏移量,加上基地址就是PE文件头指针。

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature

类似于DOS头中的e_magic,0x00004550,用字符表示是’PE’

FileHeader

struct _IMAGE_FILE_HEADER {
1
2
3
4
5
6
7
8
    WORD    Machine; //运行平台
WORD NumberOfSections; //文件区块数
DWORD TimeDateStamp; //创建的日期时间
DWORD PointerToSymbolTable; //指向符号表
DWORD NumberOfSymbols; //符号表中符号个数
WORD SizeOfOptionalHeader; //紧随其后的可选头的大小
WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

OptionalHeader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; //文件被执行时入口RVA地址
DWORD BaseOfCode;
DWORD BaseOfData;

DWORD ImageBase; //文件优先装入地址
DWORD SectionAlignment; //节在内存中对齐单位
DWORD FileAlignment; //节存储在磁盘文件中对齐单位
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem; //子系统
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

数据目录表:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 //导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 //导入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 //资源
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 //异常
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 //安全
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //重定位表
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 //调试信息
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 //版权信息
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 //RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 //TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_IMPORT 10 //Lood Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 //Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 //导入函数地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 //Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 //COM Runtime descriptor

节表

PE中所有节的属性都定义在节表中,节表由一系列IMAGE_SECTION_HEADER结构构成,每个结构描述一个节,最后以一个空的结构结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名称
union {
DWORD PhysicalAddress; //物理地址
DWORD VirtualSize; //区块的大小(没对齐的实际大小)
} Misc;
DWORD VirtualAddress; //装载到内存中的RVA,是SectionAlignment的整数倍
DWORD SizeOfRawData; //在磁盘中占的大小
DWORD PointerToRawData; //在磁盘中的偏移地址(从文件头开始)
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //区块的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

upx手动脱壳

upx手动脱壳

虽然UPX本身提供脱壳器,但是修改后可能导致官方版本的UPX脱壳失败,因此需要学习upx手动脱壳。

使用OD在win10系统上脱壳会遇到部分API更改导致OllyDump基址无法正确填入的问题,x64DBG解决了这一问题。

一般情况下,已有栈的内容不会更改,upx会把这样的信息压栈,x86汇编指令pushad可以一次性压栈,载入后发现程序最开始为pushad指令。
在这里插入图片描述
如果在pushad执行后在栈顶下硬件读取断点,那么当程序执行完后续代码,使用popad恢复寄存器时就会中断。

于是,先单步执行pushad,再设置硬件读取断点。
在这里插入图片描述
按F9运行程序,程序中断在popad之后,看到后面是一个栈空间减少0x80的循环,后面接着一个较远的跳转,壳程序一般与原程序在不同区段,因此相隔较远,可以猜测较远的跳转指向原代码。
在这里插入图片描述
删除硬件断点,然后使用F4运行到较远的跳转,再单步运行一次。
可以看到程序执行到了很像原程序的代码。
在这里插入图片描述
打开Scylla插件如下。
在这里插入图片描述

使用IATAutosearch自动填入导入地址,再使用GetImports去掉imports中有红叉的部分。

在这里插入图片描述
点击Dump将内存转为可执行文件,然后点击Fix Dump修复导入表。
将脱壳后的文件使用IDA打开,可以发现程序已经被还原。
在这里插入图片描述

ps:这种方法生成的程序只能在IDA中分析,无法正常运行,因为程序的重定位信息仍未被修复,可以通过修改Nt Header的方式阻止系统对这个程序进行重定位进而正常运行。

安卓逆向入门

安卓逆向入门

apk文件结构

安卓应用是使用java编写的,它利用Android SDK编译代码,并且把所有资源文件和数据统一打包成APK,将apk文件扩展名改为zip即可解压。
在这里插入图片描述
(1)META-INF目录
在这里插入图片描述
MANIFEST.MF:清单文件
CERT.RSA:应用签名文件
CERT.SF:资源列表及对应SHA-1签名
(2)lib目录
包括平台相关库文件
(3)res
没有编译到resources.arsc中的其它资源
(4)assests
能通过AssetManager访问的资源文件
(5)AndroidManifest.xml
安卓组件清单文件,可使用apktool转换成XML明文格式文件,包含应用名字、版本、权限等信息
(6)classes.dex
安卓可执行文件,包含该可执行文件中所有Java层代码。
(7)resources.arsc
编译好的资源文件


后续做的Android题目wp可能写到这篇文章里

pyc学习

pyc学习

pyc文件的格式

最开始4个字节是一个Maigc int, 标识此pyc的版本信息。
接下来四个字节还是个int,是pyc产生的时间。
接下来是个序列化了的 PyCodeObject。

magic int

像大多数的文件格式一样,pyc 文件开头也有一个magic number,不过不一样的是 pyc文件的magic number并不固定,而是不同版本的Python生成的pyc文件的magic number都不相同。

文件信息

在Python2的时候,这部分只有4个字节,为源代码文件的修改时间。
Python3.5和3.6相对于Python2,源代码文件信息这部分,在时间后面增加了4个字节的源代码文件的大小。
从Python3.7开始支持hash-based pyc文件,Python为了支持hash校验又使源代码文件信息这部分增加了4个字节,变为一共12个字节。

PycObject

一串二进制流,代表着指令序列,详见https://kdr2.com/tech/python/pyc-format.html

Re中常见的加密算法

常见的加密算法

Base64

简介

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。

规则

把3个字节(3*8bit)变成4个字节(4*6bit),然后把6Bit再添两位高位0,组成四个8bit的字节

字母表如下
alphabet
需要注意的是,在CTF题目中,有些题目索引表会做出改变

特点

当反编译发现字母表时,可能存在base64编码,需要注意的是,CTF中base64的索引表可能被更换

脚本

1
2
3
4
5
6
import base64
my_base64table = ""#新的索引表
std_base64table ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
s = ""#密文
s = s.translate(str.maketrans(my_base64table,std_base64table))
print(base64.b64decode(s))

Tea

简介

在密码学中,微型加密算法(Tiny Encryption Algorithm,TEA)是一种易于描述和执行的块密码,通常只需要很少的代码就可实现。其设计者是剑桥大学计算机实验室的大卫 · 惠勒与罗杰 · 尼达姆。

规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdint.h>

void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}

void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}

特点

Tea算法比较简单,且可以根据需求设置不同加密轮数来增加加密强度。
最主要的识别特征为delta = 0x9e3779b9

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class MyTea:
def __init__(self, key):
assert(type(key) == bytes)
assert(len(key) == 16)
self.k = [0]*4
for i in range(4):
self.k[i] = self.btoi(key[4*i:4*i+4])

@staticmethod
def btoi(b):
return int.from_bytes(b, byteorder="little", signed=False)

@staticmethod
def itob(i):
return int.to_bytes(i, 4, "little")

def decrypt(self, ciphertext: bytes):
plaintext = b''
for i in range(len(ciphertext)//8):
plaintext += self.decrypt_8_char(ciphertext[i*8:i*8+8])
return plaintext

def decrypt_8_char(self, sub_str):
assert(type(sub_str) == bytes)
assert(len(sub_str) == 8)
v0 = self.btoi(sub_str[:4])
v1 = self.btoi(sub_str[4:])
delta = 0x9e3779b9
sum = delta * 32
sum &= 0xffffffff
for _ in range(32):
v1 -= ((v0 << 4) + self.k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + self.k[3])
v1 &= 0xFFFFFFFF
v0 -= ((v1 << 4) + self.k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + self.k[1])
v0 &= 0xFFFFFFFF
sum -= delta
sum &= 0xffffffff

return self.itob(v0)+self.itob(v1)


if __name__ == "__main__":
cipher = [int.to_bytes(n, 1, "little")for n in [ ]]#密文
cipher=b''.join(cipher)
key = b''#密钥
t = MyTea(key)

plain = t.decrypt(cipher)
print(plain)

RC4

简介

在密码学中,RC4(来自 Rivest Cipher 4 的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法。

规则

RC4生成一种称为密钥流的伪随机流,它同明文通过异或操作以达到加密的目的,伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void rc4_init(unsigned char *s, unsigned char *key, unsigned long Len) //初始化函数
{
int i =0, j = 0;
char k[256] = {0};
unsigned char tmp = 0;
for (i=0;i<256;i++) {
s[i] = i;
k[i] = key[i%Len];
}
for (i=0; i<256; i++) {
j=(j+s[i]+k[i])%256;
tmp = s[i];
s[i] = s[j]; //交换s[i]和s[j]
s[j] = tmp;
}
} //密钥调度算法,用0-255初始化数组S,然后用密钥进行替换

void rc4_crypt(unsigned char *s, unsigned char *Data, unsigned long Len) //加解密
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for(k=0;k<Len;k++) {
i=(i+1)%256;
j=(j+s[i])%256;
tmp = s[i];
s[i] = s[j]; //交换s[x]和s[y]
s[j] = tmp;
t=(s[i]+s[j])%256;
Data[k] ^= s[t];
} //对每个S[i]根据当前的值与S中的另一元素替换,得到的密钥流与明文进行异或操作
}

特点

2个长度为256的For循环
S盒乱序时的数据交换
异或加解密

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Cipher import ARC4
from binascii import b2a_hex, a2b_hex
def myRC4(data,key):
rc41 = ARC4.new(key)
encrypted = rc41.encrypt(data)
return encrypted.encode('hex')
def rc4_decrpt_hex(data,key):
rc41=ARC4.new(key)S
return rc41.decrypt(a2b_hex(data))
key=''
data=r''
print myRC4(data,key)
print rc4_decrpt_hex('e79aaf7a42d9a1',key)

MD5

简介

一种被广泛使用的密码散列函数,可以产生16字节的散列值

规则

(1)数据填充
填充消息使其长度与448模512同余,方法是附一个1在后面,然后用0填充。
(2)添加长度
附上64位消息长度,使之成为512的整数倍
(3)初始化变量(MD5Init())
初始化A=0x01234567,B=0x89ABCDEF,C=0xFEDCBA98,D=0x76543210用来计算。
(4)数据处理(MD5Update()、MD5Final())
主循环有四轮,每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向左环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之一。
以下是每次操作中用到的四个辅助函数(每轮一个)。
F( X ,Y ,Z ) = ( X & Y ) | ( (X) & Z )
G( X ,Y ,Z ) = ( X & Z ) | ( Y & (
Z) )
H( X ,Y ,Z ) =X ^ Y ^ Z
I( X ,Y ,Z ) =Y ^ ( X | (~Z) )

特点

四个常数
h0 = 0x67452301;
h1 = 0xefcdab89;
h2 = 0x98badcfe;
h3 = 0x10325476;
MD5变形:改变4个常数、改变填充的方法、改变hash变换的处理过程

AESKeyGenMe

AES的实例分析

首先用PEiD的插件Krypto ANALyzer查看文件
在这里插入图片描述
显示文件中由MD5算法的常量元素及AES的S盒与逆S盒,可能使用到MD5,AES算法。

使用IDA,定位关键代码并分析
在这里插入图片描述
可以看到输入的序列号长度应为32
进入sub_4012F0
在这里插入图片描述
可以看到4个MD5状态变量
由此推出对用户名作了MD5
分析sub_401EC0函数
在这里插入图片描述
这部分可能是用于判断密钥长度
进一步找到S盒
在这里插入图片描述
推测是对输入的序列号进行了AES加密与MD5后的用户名进行比较
在这里插入图片描述
v7应该是AES的密钥
写出解密脚本

1
2
3
4
5
6
7
8
9
from hashlib import md5
from Crypto.Cipher import AES
from binascii import b2a_hex

name=b'tipsy'
md5_digest=md5(name).digest()
aes=AES.new(key=b'\x2B\x7E\x15\x16\x28\xAE\xD2\xA6\xAB\xF7\x15\x88\x09\xCF\x4F\x3C',mode=AES.MODE_ECB)
decrypt=aes.decrypt(md5_digest)
print(b2a_hex(decrypt).decode().upper())

在这里插入图片描述