XXTEA

上班的时候被安排了个代打比赛的活

内容主要是日志跟流量分析

其中一题题目文件如下

ha.pcapng

其中一个小问题是失陷主机上有多少个用户

常理来说应该是要看/etc/passwd

但是这个流量包并没有相关信息

流量包最后把ELF文件en下载, 也给了en的运行结果

感觉应该该是要对这个文件进行逆向了

下载, 拖IDA

看着像UPX

1
2
3
4
5
6
7
8
9
10
11
LOAD:0000000000005F3C                 dd 1E68h
LOAD:0000000000005F40 db 0
LOAD:0000000000005F41 db 5Ah, 0E8h, 1Eh, 3 dup(0), 50h
LOAD:0000000000005F48 aRotExecProtWri db 'ROT_EXEC|PROT_WRITE failed.',0Ah,0
LOAD:0000000000005F65 db 5Eh, 6Ah, 2
LOAD:0000000000005F68 dq 7F6A050F58016A5Fh, 0A050F583C6A5Fh
LOAD:0000000000005F78 aInfoThisFileIs db '$Info: This file is packed with the UPX executable packer http://'
LOAD:0000000000005F78 db 'upx.sf.net $',0Ah,0
LOAD:0000000000005FC7 aIdUpx424Copyri db '$Id: UPX 4.24 Copyright (C) 1996-2024 the UPX Team. All Rights Re'
LOAD:0000000000005FC7 db 'served. $',0Ah,0
LOAD:0000000000006013 align 4

要在github上下一个新版本的upx

https://github.com/upx/upx/releases/tag/v4.2.4

脱壳之后看主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[6]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
v4[0] = 0x12345678;
v4[1] = 0x9ABCDEF0;
v4[2] = 0xFEDCBA98;
v4[3] = 0x76543210;
encrypt_file("/etc/passwd", "message.txt.enc", v4);
return 0;
}

跟进函数encrypt_file

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
void __fastcall encrypt_file(const char *a1, const char *a2, __int64 a3)
{
FILE *stream; // [rsp+28h] [rbp-28h]
__int64 n; // [rsp+30h] [rbp-20h]
unsigned __int64 nmemb; // [rsp+38h] [rbp-18h]
void *ptr; // [rsp+40h] [rbp-10h]
FILE *s; // [rsp+48h] [rbp-8h]

stream = fopen(a1, "rb");
if ( !stream )
{
perror("Failed to open input file");
exit(1);
}
fseek(stream, 0LL, 2);
n = ftell(stream);
fseek(stream, 0LL, 0);
nmemb = (unsigned __int64)(n + 3) >> 2;
ptr = calloc(nmemb, 4uLL);
fread(ptr, 1uLL, n, stream);
fclose(stream);
xxtea_encrypt(ptr, (unsigned int)nmemb, a3);
s = fopen(a2, "wb");
if ( !s )
{
perror("Failed to open output file");
free(ptr);
exit(1);
}
fwrite(ptr, 4uLL, nmemb, s);
fclose(s);
free(ptr);
}

稍微理一下流程

主函数里的v4是密钥
读取/etc/passwd作为明文进行加密
加密轮数参数为输入长度除以4

加密结果为输出的message.txt.enc
在流量包里面有这个密文

所以是要进行XXTEA解密


Solution 0x00

这里给出一个取巧的方法

由于这个程序给出了函数xxtea_decrypt

而且函数参数与函数xxtea_encrypt一致

都是输入, 加密/解密轮数参数, 密钥

虽然说程序中没有调用这个函数, 但是可以在动态调试中把call xxtea_encrypt改为call xxtea_decrypt

输入需要读取/etc/passwd, 那就把密文放在/etc/passwd, 这样就解决了输入密文的问题

Call指令的字节码为E8 75 FC FF FF

Call参数 = 目标地址 - 当前地址 - 指令长度

0xFFFFFCF5 = (0x12F5 - 0x167B - 0x5)& 0xFFFFFFFF

这里的Call的函数是xxtea_encrypt

需要改为xxtea_decrypt, 地址为0x1456

那么Call的参数则为

(0x1456 - 0x167B - 0x5)& 0xFFFFFFFF = 0xFFFFFDD6

则修改过后的Call指令应当为E8 D6 FD FF FF

运行即可, 得到的message.txt.enc即为解密之后的/etc/passwd文件

内容如下

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
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:103:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:104:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:105:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:106:systemd Bus Proxy,,,:/run/systemd:/bin/false
hack:x:1000:1000::/home/hack:/bin/sh
admin:x:1001:1001::/home/admin:/bin/sh
admin1:x:1002:1002::/home/admin1:/bin/sh
admin2:x:1003:1003::/home/admin2:/bin/sh
admin3:x:1004:1004::/home/admin3:/bin/sh

应该是六个用户吧


Solution 0x01

当然,也有不取巧的方法

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
50
51
52
53
54
55
56
57
58
import binascii

def read_int(a, i):
tmp_list = a[4*i:4*i+4]
num = 0
for I in range(4):
num = num + (tmp_list[I] << I * 8)
return num

def write_int(a, i, num):
tmp_list = []
hex_num = hex(num)[2:]
if len(hex_num) < 8:
hex_num = (8 - len(hex_num)) * "0" + hex_num
for I in range(4):
a[4 * i + I] = int(hex_num[6 - 2*I:8 - 2*I], 16)


cipher_bytes = binascii.unhexlify(b"868F9499451EC1600F994E17A3656B5E688ADBFA251AEA32331935FBD377C95492D8A870D9C29281BA32032400A7D04E7E7988AA11973B8AD996BB081F2F560B29F1CE80F35E86C74E14D1D1EEE1D2AEFD2513DF6E47162F1CC259FC23A73EE5A5F82C453F7933B997A220F1214EF8B241FC8586C95B87B37A17A12B35EFD45FB0BAE2B3850BA3AD25213BB2C75E82925C4DBA50E60E95118C8F61BA5E34043BA54688423E60857F159AF93C7E5E5D059577A5F758DC791533FB3714B8186DC9E27F78A082F3551BBDE57A72EDBA9051AB91C1C84F9BF95EF3AED2A17DD5DCE0D194A5F6D8F90D2B55C743D6889346AA38F08833943B8BA3F63853D45CA4D78C8F53F31EEFE9C2E24F633897EFCDD8CF0A9A7233FF8D824D5B0B52CD5D821BF420578EC651613AEC0B461A9079803D02910A43F898919C9288C8D941E9D0B9990789A4CE7A2A7FD4DC0F64B819616979BC56BEE51E92D0891792B155F4132265D475E02A372529211CB4DDADB39E22D4E5123458143405C32011A191915A5004E2142C9225E1DB8AAEEF040ECA3BB8E677DC058EE48B77A3D97E73F7494A8CEE9E41D98E690669AF396CD47AF26FFC0CEB704E9182AB4D62A8F474B233EFB317CC8B0EC1132E4394CEBF4EEE357493CEADE4F43CBEB0C2C664B62278E7B28CCEFA633BA5C0457830D3654BB3A9CA25BA33021E0611F9076807328DD3C8AC65F251D9B3AFCF4D47D43D1B0DCF1B5F93BC1DF69402E152EA2F12AC3F056F3EB85BF59D6647E0EDE6653550B3DD559974B1696641795BC0A40DA170EAE78054BC9833A9EA8A48CD27ABBC420F30A80CFBD181275D8434658F650B4D55AF817C0E94FAFE5329F5BF6CF66001689661EEA4318A4390F6D6BFFE52E347C4147D30C67B97FF6C5FE4755A8BC8AE4487DF86E9A282C8A90DBBB91923B4BB1112BBDC1DDE6D8A501B29390EFE5D9C01A130AD046891BBC21679B0EF7DFB83A89F4A944E8C4E87971FBF8902F6E0B4B6CDA714B472EE6C39E19F60A511EAC4AFA1670A936B20DF32A9F83754ADA308565FEC7C2E4F14CD361BE29B5E0C9C3EF81B32F347269AD7270BBFE1866E3DD5987BF5FF6193BF6F6693764AE8A2BA031715207F4F47384101BC8EE51CAAC05DF536C046D268905FF1367185091DCBD94CB8469DA91AF6354471289FE2148F8E50CFA37AE8AEA8517D2832D293DBF74332B187EC74087A8B77E167099065784B412A02ED05BD7C668AFE708DFB83CC48910E343D287D748721E59F7DFBE3F32FF69D624A2B5635544AF9C751B7C3024582F0BB7ADACEAEE07A44F7348F1C5989C75825FFF3F5EA220789D7EAC9F28526557EE333F412A9931E9FF4C073D2ED2C9EC302CF3FBF4A75456AB1C5DBDE2A0BBE61C05CB5F55BC940850151A9D530514B788CFA07D2CB6DBF6BF4FB913B380EA599DA9A18203AA8050AB7AC0149126A323D17D53716DF344DECB2CEDC3FEB9446459134D25B817727AAAC34498B448BDD44C2C8EADAC5B79200AA586C1186D51DCF8020A8D73D7D090857F2D97175D8262A39B4D78571972746858721B99AB833E60387EBEE3C9936ED8BC82C19F71D042F04A97CE4A7052936FC1287D98928DC1CB4BD6CAF1D605E96B3150D2AC14D5C073E2B2E56F08B27FDC4D257B137D8D220DDF10422DA261705828A2DE1FEB230F104030BB92DAE65C00CDD47176CC4A41FC47F0F383447EC3FA40680AE36698CF85D0D9D55059EEE4E769AD7FAA59D70DA828872E754F953C452911501EA69B70961DD22F54E49ED6445E04CB1EF8AAF10D085F242AB4507455866E4B56C20C7977662AAEE092A338BEB16D53FD9FA20658FE8588DAE80EB5F3FE7445536BF2D338264DBE2DC458D5C7565670147224CAC4E9CC81D681C301491615FC96729FE70CFB039F7B2EF28766A5859F05D97A5CF2CA49B041C251C68B3A96FA73D46498C2673737E13446FD8")

a2 = len(cipher_bytes) // 4

cipher = []
for I in cipher_bytes:
cipher.append(I)
key_bytes = binascii.unhexlify(b"78563412F0DEBC9A98BADCFE10325476")

key = []
for I in key_bytes:
key.append(I)
plain = ""


y = read_int(cipher, 0)
sum_num = (0x9E3779B9 * ((52 // a2) + 6)) & 0xffffffff
v15 = False

while not v15:
e = (sum_num >> 2) & 3
if (a2 != 1):
i = a2 - 1
while i != 0:
z_i = read_int(cipher, i)
z = read_int(cipher, i - 1)
y = (z_i - (((sum_num ^ y) + (read_int(key, ((i & 3) ^ e)) ^ z)) ^ (((z << 4) ^ (y >> 3)) + ((y << 2) ^ (z >> 5))))) & 0xffffffff
write_int(cipher, i, y)
i = i - 1
z = read_int(cipher, a2 - 1)
cipher_0 = read_int(cipher, 0)
y = (cipher_0 - (((sum_num ^ y) + (read_int(key, (0 & 3) ^ e) ^ z)) ^ (((z << 4) ^ (y >> 3)) + ((y << 2) ^ (z >> 5))))) & 0xffffffff
write_int(cipher, 0, y)
v15 = (sum_num == 0x9E3779B9)
sum_num = (sum_num + 0x61C88647) & 0xffffffff

plain = ""
for _ in cipher:
plain = plain + chr(_)
print(plain)

Update

看到XXTEA在github上有repo

https://github.com/xxtea/xxtea-python

下载下来后解密却解不出来

大概看了一下xxtea.c

函数static uint32_t * xxtea_to_uint_array(const uint8_t * data, size_t len, int inc_len, size_t * out_len)会进行pad

pad为输入内容的长度, 四字节小端存储添加在明文末尾

而函数static uint8_t * xxtea_to_ubyte_array(const uint32_t * data, size_t len, int inc_len, size_t * out_len)会unpad

unpad失败则会返回空字符串

程序en加密/etc/passwd的时候没有进行pad, 所以用这个repo解密自然在unpad时会失败

而这个repo里pad或unpad与否由参数inc_len决定

所以解密时不进行unpad即可

删除python环境路径下的pyc文件缓存/Lib/site-packages/xxtea/__pycache__ (如果有的话)

python环境下的/Lib/site-packages/xxtea.c

函数xxtea_ubyte_decrypt的定义中对函数xxtea_to_ubyte_array的调用, 第三个参数改为0即可跳过unpad

EOF