青

一言

中少快乐阅读期刊 Flash 阅览器分析解密

中少快乐阅读期刊 Flash 阅览器分析解密

声明

本文仅用于学习和研究,本文及本人不提供任何工具、文件,不以任何形式用于商业用途,阅读本文也应遵守相关法律法规,维护版权,不得侵犯他人合法权益。

工具

  1. JEPXS-decompiler releases
    JEPXS 是一款开源的 SWF 文件反编译工具,支持 Windows、Linux、MacOS 等多个平台,支持多种语言
  2. CEF Flash Browser
    Flash 浏览器,不需要安装 Flash 插件

序言

最开始是在贴吧上看到了可以在在线阅读中国少年儿童出版社历年期刊的网站,包括《儿童文学》,《少年文摘》,《知心姐姐》等童年超喜欢的期刊们,为了方便阅读,
最好能下载下来离线看,这样就可以在没有网络的情况下看到自己喜欢的期刊了,所以有了后续的一系列内容。

中少快乐阅读(国图)中少快乐阅读(杭州)
是其中两个网站,也是最具代表性的两个,本文全文以《儿童文学》为例。

国图版中少快乐阅读 中少期刊版中少快乐阅读

分析从国图版开始,国图版能更方便说明情况。

国图版默认是只有 2014 年~ 2021 年的期刊,中少版除了少部分无法打开外,可以查看几乎所有的期刊,并且可以直接浏览,通常思路就是既然可以通过 HTML 直接浏览
那么,找到对应的路径就可以下载了,一开始我也这么做了,但后来我还是将 HTML5 版本的大部分内容删除,改用了 Flash 版本,也算走了不少弯路吧,后面会具体阐述。

国图版可以通过修改 URL 或使用插件插入输入框来查看更多年份的期刊,不难发现,2013 年及以前的内容并不是没有,
而是只能通过 Flash 进行访问,众所周知,Flash 已经被淘汰,不再被浏览器支持,这些内容如今几乎无法访问,对于大部分读者来说,虽然有国产的 Flash 程序,
但所谓的“Flash浏览器”其实只是旧版本浏览器套了个壳而已,广告,弹窗等内容堪比流氓软件。感谢 CEF Flash Browser,让我能够在不安装 Flash 插件的情况下,
访问和阅读,具体请查看文章开头的相关部分。

HTML 分析和下载

以国图版本总 971 期(2021年12月经典)为例,点击阅读,
这里直接给出链接
大致链接格式是这样的:

1
http://{IP}:{端口}/Reading/Show/{ID}

这个 ID 是期刊的 ID,是 UUID 的格式。查看网页源代码,包括了一些系统版本跳转到HTML5版本的js,调整狂口大小的js和主要内容:一个 iframe 标签。这里的判断系统版本并跳转 H5 看似是旧版本遗留的内容,实质上目前
直接打开就是 HTML5 的内容。iframe 标签的 src 属性指向了和 js 跳转链接一样的地址 链接

JavaScript 链接被 AdGuard 处理过,不过不影响分析。

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
59
60
61
62
63

<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>儿童文学总第971期</title>
<script type="text/javascript" nonce="789b5b3975c14de9a64ee243f87" src="//local.adguard.org?ts=1708947648105&amp;type=content-script&amp;dmn=202.96.31.36:8888&amp;url=http%3A%2F%2F202.96.31.36%3A8888%2FReading%2FShow%2F8add12c7-34dc-2dc0-00d6-9ee2f5772d26&amp;app=chrome.exe&amp;css=3&amp;js=1&amp;rel=1&amp;rji=1&amp;sbe=1&amp;stealth=1&amp;st-wrtc&amp;st-loc&amp;st-flash&amp;st-dnt"></script>
<script type="text/javascript" nonce="789b5b3975c14de9a64ee243f87" src="//local.adguard.org?ts=1708947648105&amp;name=AdGuard%20Extra&amp;name=AdGuard%20Popup%20Blocker&amp;type=user-script"></script><link rel="stylesheet" href="/Content/bootstrap-3.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="/Content/bootstrap-3.0.0/css/bootstrap-theme.min.css">
<link rel="stylesheet" type="text/css" href="/Content/css/layout.css">
<link rel="stylesheet" media="screen" href="/Content/css/user-defined-responsive.css">
<script type="text/javascript" src="/Scripts/jquery.min.js"></script>
<!--[if lt IE 9]>
<link rel="stylesheet" type="text/css" href="css/ie.css">
<![endif]-->
</head>
<body>
<script type="text/javascript">
function browserRedirect() {
var sUserAgent = navigator.userAgent.toLowerCase();
var bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
var bIsMidp = sUserAgent.match(/midp/i) == "midp";
var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
var bIsAndroid = sUserAgent.match(/android/i) == "android";
var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
var bIsIEMobile = sUserAgent.match(/iemobile/i) == "iemobile";
if (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM || bIsIEMobile) {
window.location.href = '/fliphtml5/password/main/qikan/etwx/2021/12/971/web/flipviewerxpress.html?pswd=6273';
} else {
//window.location= 'http://url/pc.html';
}
}
</script>
<script type="text/javascript">
$(document).ready(function () {
browserRedirect();
function fullscreen() {
var width = $(window).width();
var height = $(document).height();
$("iframe").attr("height", height);
$("iframe").attr("width", width);
}
window.onload = fullscreen();
$(window).resize(function () {
fullscreen();
}
);
)
</script>
<iframe id="bookview" src="/fliphtml5/password/main/qikan/etwx/2021/12/971/web/flipviewerxpress.html?pswd=6273" width="" height="" frameborder="0"></iframe>
<!--FenXi Code-->
<script type="text/javascript" charset="utf-8" src="http://fenxi.61read.com/res/zsstat.js?
uid=">
</script>
<!--End FenXi Code-->
</body>
</html>

1
/fliphtml5/password/main/qikan/etwx/{年}/{月}/{总期数}/web/flipviewerxpress.html?pswd={开书密码}';

继续看新打开的页面的源码,

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

<!doctype html>
<html lang="zh">
<head>
<title>儿童文学总第971期</title>
<meta name="viewport" content="width = 1050, user-scalable = no"/>
<meta charset="UTF-8">
<script type="text/javascript" nonce="789b5b3975c14de9a64ee243f87" src="//local.adguard.org?ts=1708947648105&amp;type=content-script&amp;dmn=202.96.31.36:8888&amp;url=http%3A%2F%2F202.96.31.36%3A8888%2Ffliphtml5%2Fpassword%2Fmain%2Fqikan%2Fetwx%2F2021%2F12%2F971%2Fweb%2Fflipviewerxpress.html%3Fpswd%3D6273&amp;app=chrome.exe&amp;css=3&amp;js=1&amp;rel=1&amp;rji=1&amp;sbe=1&amp;stealth=1&amp;st-wrtc&amp;st-loc&amp;st-flash&amp;st-dnt"></script>
<script type="text/javascript" nonce="789b5b3975c14de9a64ee243f87" src="//local.adguard.org?ts=1708947648105&amp;name=AdGuard%20Extra&amp;name=AdGuard%20Popup%20Blocker&amp;type=user-script"></script><link rel="stylesheet" type="text/css" href="/turn/css/magazine.css">
<link rel="stylesheet" type="text/css" href="/turn/css/pagination.css">
<script type="text/javascript" src="/turn/extras/jquery.min.1.7.js"></script>
<script type="text/javascript" src="/turn/extras/modernizr.2.5.3.min.js"></script>
<script type="text/javascript" src="/turn/lib/hash.js"></script>
<script type="text/javascript" src="/turn/lib/vconsole.min.js"></script>
<script type="text/javascript" src="/turn/lib/zoom.min.js"></script>
<script type="text/javascript" src="/turn/lib/turn.js"></script>
<script type="text/javascript" src="/turn/js/magazine.js"></script>
</head>
<body>
<div id="canvas">
<div class="zoom-icon zoom-icon-in"></div>
<div class="magazine-viewport">
<div class="container">
<div class="magazine">
<div ignore="1" class="next-button"></div>
<div ignore="1" class="previous-button"></div>
</div>
</div>
</div>
<div class="pagination-container">
<ul class="pagination">
<li><span id="p-first">封面</span></li>
<li><span id="p-previous">
上一页</span>
</li>
<li><input id="p-now" type="text"/></li>
<li><span id="p-next">下一页</span></li>
<li id="li8"><span id="p-last">封底</span></li>
</ul>
</div>
<script type="text/javascript">
var pageFile = "html5/tablet/";
var xmlFile="etwx971DL.xml";
if (screen.width > 1024) {
pageFile = "html5/tablet/";
} else {
pageFile = "html5/mobile/";
}
loadXML(pageFile+xmlFile);
function loadApp() {
$('#canvas').fadeIn(1000);
var flipbook = $('.magazine');
if (flipbook.width() == 0 || flipbook.height() == 0) {
setTimeout(loadApp, 10);
return;
}
// Create the flipbook
flipbook.turn({
width: 1456,
height: 1024,
duration: 1000,
acceleration: !isChrome(),
gradients: true,
autoCenter: true,
elevation: 50,
pages: 126,
when: {
turning: function (event, page, view) {
var book = $(this),
currentPage = book.turn('page'),
pages = book.turn('pages');
Hash.go('page/' + page).update();
disableControls(page);
$('.pagination #p-' + currentPage).parent().removeClass('current');
$('.pagination #p-' + page).parent().addClass('current');
$('.pagination #p-now').val(page);
},
turned: function (event, page, view) {
disableControls(page);
$(this).turn('center');
if (page == 1) {
$(this).turn('peel', 'br');
}
},
missing: function (event, pages) {
for (var i = 0; i < pages.length; i++) {
addPage(pages[i], $(this));
}
}
}
});
// Zoom.js
$('.magazine-viewport').zoom({
flipbook: $('.magazine'),
max: function () {
return largeMagazineWidth() / $('.magazine').width();
},
when: {
swipeLeft: function () {
flipbook.turn('next');
},
swipeRight: function () {
flipbook.turn('previous');
},
resize: function (event, scale, page, pageElement) {
if (scale == 1)
loadSmallPage(page, pageElement);
else
loadLargePage(page, pageElement);
},
zoomIn: function () {
//$('.thumbnails').hide();
$('.made').hide();
$('.magazine').removeClass('animated').addClass('zoom-in');
$('.zoom-icon').removeClass('zoom-icon-in').addClass('zoom-icon-out');
if (!window.escTip && !$.isTouch) {
escTip = true;
$('<div />', {'class': 'exit-message'}).html('<div>Press ESC to exit</div>').appendTo($('body')).delay(2000).animate({opacity: 0}, 500, function () {
$(this).remove();
});
}
},
zoomOut: function () {
$('.exit-message').hide();
//$('.thumbnails').fadeIn();
$('.made').fadeIn();
$('.zoom-icon').removeClass('zoom-icon-out').addClass('zoom-icon-in');
setTimeout(function () {
$('.magazine').addClass('animated').removeClass('zoom-in');
resizeViewport();
}, 0);
}
}
});
// Zoom event
if ($.isTouch)
$('.magazine-viewport').bind('zoom.doubleTap', zoomTo);
else
$('.magazine-viewport').bind('zoom.tap', zoomTo);
// Using arrow keys to turn the page
$(document).keydown(function (e) {
var previous = 37, next = 39, esc = 27;
switch (e.keyCode) {
case previous:
// left arrow
$('.magazine').turn('previous');
e.preventDefault();
break;
case next:
//right arrow
$('.magazine').turn('next');
e.preventDefault();
break;
case esc:
$('.magazine-viewport').zoom('zoomOut');
e.preventDefault();
break;
}
});
// URIs - Format #/page/1
Hash.on('^page\/([0-9]*)$', {
yep: function (path, parts) {
var page = parts[1];
if (page !== undefined) {
if ($('.magazine').turn('is'))
$('.magazine').turn('page', page);
}
},
nop: function (path) {
if ($('.magazine').turn('is'))
$('.magazine').turn('page', 1);
}
});
$(window).resize(function () {
resizeViewport();
}).bind('orientationchange', function () {
resizeViewport();
});
//Events for pagination
$('.pagination span').click(function (event) {
var page;
if (event.target) {
if ((page = /p-([0-9]+)/.exec($(event.target).attr('id')))) {
$('.magazine').turn('page', page[1]);
} else {
switch ($(event.target).attr('id')) {
case "p-first":
$('.magazine').turn('page', 1);
break;
case "p-previous":
$('.magazine').turn('previous');
break;
case "p-next":
$('.magazine').turn('next');
break;
case "p-last":
$('.magazine').turn('page', $(".magazine").turn("pages"));
break;
}
}
}

});
// Regions
if ($.isTouch) {
$('.magazine').bind('touchstart', regionClick);
} else {
$('.magazine').click(regionClick);
}
// Events for the next button
$('.next-button').bind($.mouseEvents.over, function () {
$(this).addClass('next-button-hover');
}).bind($.mouseEvents.out, function () {
$(this).removeClass('next-button-hover');
}).bind($.mouseEvents.down, function () {
$(this).addClass('next-button-down');
}).bind($.mouseEvents.up, function () {
$(this).removeClass('next-button-down');
}).click(function () {
$('.magazine').turn('next');
});
// Events for the next button
$('.previous-button').bind($.mouseEvents.over, function () {
$(this).addClass('previous-button-hover');
}).bind($.mouseEvents.out, function () {
$(this).removeClass('previous-button-hover');
}).bind($.mouseEvents.down, function () {
$(this).addClass('previous-button-down');
}).bind($.mouseEvents.up, function () {
$(this).removeClass('previous-button-down');
}).click(function () {
$('.magazine').turn('previous');
});
resizeViewport();
$('.magazine').addClass('animated');
}

// Zoom icon
$('.zoom-icon').bind('mouseover', function () {
if ($(this).hasClass('zoom-icon-in'))
$(this).addClass('zoom-icon-in-hover');
if ($(this).hasClass('zoom-icon-out'))
$(this).addClass('zoom-icon-out-hover');
}).bind('mouseout', function () {
if ($(this).hasClass('zoom-icon-in'))
$(this).removeClass('zoom-icon-in-hover');
if ($(this).hasClass('zoom-icon-out'))
$(this).removeClass('zoom-icon-out-hover');
}).bind('click', function () {
if ($(this).hasClass('zoom-icon-in'))
$('.magazine-viewport').zoom('zoomIn');
else if ($(this).hasClass('zoom-icon-out'))
$('.magazine-viewport').zoom('zoomOut');
});
$('#canvas').hide();
loadApp();
</script>
</body>
</html>

让我们感谢 ChatGPT 为我们进行的分析:

GPT 分析的源码

那么直接看这个 XML 文件吧:

完整 XML 内容:

完整的 XML 内容
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
<package unique-identifier="{7A2A5428-44E4-4734-973E-DF2A4215287B}">
<script/>
<metadata>
<x-metadata>
<meta name="EbooksysVersion" content="1.50"/>
<meta name="page_content_size" content="465 649"/>
<meta name="window_size" content="768 1024"/>
<meta name="book_background" content="color:0x878787"/>
<meta name="flipping_sound" content="yes"/>
<meta name="background_music" content="yes"/>
<meta name="standard_direction" content="yes"/>
<meta name="autoflip" content="no direction:forward atend:repeat interval:3000ms"/>
<meta name="bookmark" content="type:text autosize:no shape:roundrect 3deffect:yes gradienteffect:yes slope:-8 gap:3"/>
</x-metadata>
</metadata>
<manifest>
<item id="item0" href="normal/etwx971DL_1.jpg" href2="zoomone/etwx971DL_1.jpg" media-type="image/x-flp"/>
<item id="item1" href="normal/etwx971DL_2.jpg" href2="zoomone/etwx971DL_2.jpg" media-type="image/x-flp"/>
<item id="item2" href="normal/etwx971DL_3.jpg" href2="zoomone/etwx971DL_3.jpg" media-type="image/x-flp"/>
<item id="item3" href="normal/etwx971DL_4.jpg" href2="zoomone/etwx971DL_4.jpg" media-type="image/x-flp"/>
<item id="item4" href="normal/etwx971DL_5.jpg" href2="zoomone/etwx971DL_5.jpg" media-type="image/x-flp"/>
<item id="item5" href="normal/etwx971DL_6.jpg" href2="zoomone/etwx971DL_6.jpg" media-type="image/x-flp"/>
<item id="item6" href="normal/etwx971DL_7.jpg" href2="zoomone/etwx971DL_7.jpg" media-type="image/x-flp"/>
<item id="item7" href="normal/etwx971DL_8.jpg" href2="zoomone/etwx971DL_8.jpg" media-type="image/x-flp"/>
<item id="item8" href="normal/etwx971DL_9.jpg" href2="zoomone/etwx971DL_9.jpg" media-type="image/x-flp"/>
<item id="item9" href="normal/etwx971DL_10.jpg" href2="zoomone/etwx971DL_10.jpg" media-type="image/x-flp"/>
<item id="item10" href="normal/etwx971DL_11.jpg" href2="zoomone/etwx971DL_11.jpg" media-type="image/x-flp"/>
<item id="item11" href="normal/etwx971DL_12.jpg" href2="zoomone/etwx971DL_12.jpg" media-type="image/x-flp"/>
<item id="item12" href="normal/etwx971DL_13.jpg" href2="zoomone/etwx971DL_13.jpg" media-type="image/x-flp"/>
<item id="item13" href="normal/etwx971DL_14.jpg" href2="zoomone/etwx971DL_14.jpg" media-type="image/x-flp"/>
<item id="item14" href="normal/etwx971DL_15.jpg" href2="zoomone/etwx971DL_15.jpg" media-type="image/x-flp"/>
<item id="item15" href="normal/etwx971DL_16.jpg" href2="zoomone/etwx971DL_16.jpg" media-type="image/x-flp"/>
<item id="item16" href="normal/etwx971DL_17.jpg" href2="zoomone/etwx971DL_17.jpg" media-type="image/x-flp"/>
<item id="item17" href="normal/etwx971DL_18.jpg" href2="zoomone/etwx971DL_18.jpg" media-type="image/x-flp"/>
<item id="item18" href="normal/etwx971DL_19.jpg" href2="zoomone/etwx971DL_19.jpg" media-type="image/x-flp"/>
<item id="item19" href="normal/etwx971DL_20.jpg" href2="zoomone/etwx971DL_20.jpg" media-type="image/x-flp"/>
<item id="item20" href="normal/etwx971DL_21.jpg" href2="zoomone/etwx971DL_21.jpg" media-type="image/x-flp"/>
<item id="item21" href="normal/etwx971DL_22.jpg" href2="zoomone/etwx971DL_22.jpg" media-type="image/x-flp"/>
<item id="item22" href="normal/etwx971DL_23.jpg" href2="zoomone/etwx971DL_23.jpg" media-type="image/x-flp"/>
<item id="item23" href="normal/etwx971DL_24.jpg" href2="zoomone/etwx971DL_24.jpg" media-type="image/x-flp"/>
<item id="item24" href="normal/etwx971DL_25.jpg" href2="zoomone/etwx971DL_25.jpg" media-type="image/x-flp"/>
<item id="item25" href="normal/etwx971DL_26.jpg" href2="zoomone/etwx971DL_26.jpg" media-type="image/x-flp"/>
<item id="item26" href="normal/etwx971DL_27.jpg" href2="zoomone/etwx971DL_27.jpg" media-type="image/x-flp"/>
<item id="item27" href="normal/etwx971DL_28.jpg" href2="zoomone/etwx971DL_28.jpg" media-type="image/x-flp"/>
<item id="item28" href="normal/etwx971DL_29.jpg" href2="zoomone/etwx971DL_29.jpg" media-type="image/x-flp"/>
<item id="item29" href="normal/etwx971DL_30.jpg" href2="zoomone/etwx971DL_30.jpg" media-type="image/x-flp"/>
<item id="item30" href="normal/etwx971DL_31.jpg" href2="zoomone/etwx971DL_31.jpg" media-type="image/x-flp"/>
<item id="item31" href="normal/etwx971DL_32.jpg" href2="zoomone/etwx971DL_32.jpg" media-type="image/x-flp"/>
<item id="item32" href="normal/etwx971DL_33.jpg" href2="zoomone/etwx971DL_33.jpg" media-type="image/x-flp"/>
<item id="item33" href="normal/etwx971DL_34.jpg" href2="zoomone/etwx971DL_34.jpg" media-type="image/x-flp"/>
<item id="item34" href="normal/etwx971DL_35.jpg" href2="zoomone/etwx971DL_35.jpg" media-type="image/x-flp"/>
<item id="item35" href="normal/etwx971DL_36.jpg" href2="zoomone/etwx971DL_36.jpg" media-type="image/x-flp"/>
<item id="item36" href="normal/etwx971DL_37.jpg" href2="zoomone/etwx971DL_37.jpg" media-type="image/x-flp"/>
<item id="item37" href="normal/etwx971DL_38.jpg" href2="zoomone/etwx971DL_38.jpg" media-type="image/x-flp"/>
<item id="item38" href="normal/etwx971DL_39.jpg" href2="zoomone/etwx971DL_39.jpg" media-type="image/x-flp"/>
<item id="item39" href="normal/etwx971DL_40.jpg" href2="zoomone/etwx971DL_40.jpg" media-type="image/x-flp"/>
<item id="item40" href="normal/etwx971DL_41.jpg" href2="zoomone/etwx971DL_41.jpg" media-type="image/x-flp"/>
<item id="item41" href="normal/etwx971DL_42.jpg" href2="zoomone/etwx971DL_42.jpg" media-type="image/x-flp"/>
<item id="item42" href="normal/etwx971DL_43.jpg" href2="zoomone/etwx971DL_43.jpg" media-type="image/x-flp"/>
<item id="item43" href="normal/etwx971DL_44.jpg" href2="zoomone/etwx971DL_44.jpg" media-type="image/x-flp"/>
<item id="item44" href="normal/etwx971DL_45.jpg" href2="zoomone/etwx971DL_45.jpg" media-type="image/x-flp"/>
<item id="item45" href="normal/etwx971DL_46.jpg" href2="zoomone/etwx971DL_46.jpg" media-type="image/x-flp"/>
<item id="item46" href="normal/etwx971DL_47.jpg" href2="zoomone/etwx971DL_47.jpg" media-type="image/x-flp"/>
<item id="item47" href="normal/etwx971DL_48.jpg" href2="zoomone/etwx971DL_48.jpg" media-type="image/x-flp"/>
<item id="item48" href="normal/etwx971DL_49.jpg" href2="zoomone/etwx971DL_49.jpg" media-type="image/x-flp"/>
<item id="item49" href="normal/etwx971DL_50.jpg" href2="zoomone/etwx971DL_50.jpg" media-type="image/x-flp"/>
<item id="item50" href="normal/etwx971DL_51.jpg" href2="zoomone/etwx971DL_51.jpg" media-type="image/x-flp"/>
<item id="item51" href="normal/etwx971DL_52.jpg" href2="zoomone/etwx971DL_52.jpg" media-type="image/x-flp"/>
<item id="item52" href="normal/etwx971DL_53.jpg" href2="zoomone/etwx971DL_53.jpg" media-type="image/x-flp"/>
<item id="item53" href="normal/etwx971DL_54.jpg" href2="zoomone/etwx971DL_54.jpg" media-type="image/x-flp"/>
<item id="item54" href="normal/etwx971DL_55.jpg" href2="zoomone/etwx971DL_55.jpg" media-type="image/x-flp"/>
<item id="item55" href="normal/etwx971DL_56.jpg" href2="zoomone/etwx971DL_56.jpg" media-type="image/x-flp"/>
<item id="item56" href="normal/etwx971DL_57.jpg" href2="zoomone/etwx971DL_57.jpg" media-type="image/x-flp"/>
<item id="item57" href="normal/etwx971DL_58.jpg" href2="zoomone/etwx971DL_58.jpg" media-type="image/x-flp"/>
<item id="item58" href="normal/etwx971DL_59.jpg" href2="zoomone/etwx971DL_59.jpg" media-type="image/x-flp"/>
<item id="item59" href="normal/etwx971DL_60.jpg" href2="zoomone/etwx971DL_60.jpg" media-type="image/x-flp"/>
<item id="item60" href="normal/etwx971DL_61.jpg" href2="zoomone/etwx971DL_61.jpg" media-type="image/x-flp"/>
<item id="item61" href="normal/etwx971DL_62.jpg" href2="zoomone/etwx971DL_62.jpg" media-type="image/x-flp"/>
<item id="item62" href="normal/etwx971DL_63.jpg" href2="zoomone/etwx971DL_63.jpg" media-type="image/x-flp"/>
<item id="item63" href="normal/etwx971DL_64.jpg" href2="zoomone/etwx971DL_64.jpg" media-type="image/x-flp"/>
<item id="item64" href="normal/etwx971DL_65.jpg" href2="zoomone/etwx971DL_65.jpg" media-type="image/x-flp"/>
<item id="item65" href="normal/etwx971DL_66.jpg" href2="zoomone/etwx971DL_66.jpg" media-type="image/x-flp"/>
<item id="item66" href="normal/etwx971DL_67.jpg" href2="zoomone/etwx971DL_67.jpg" media-type="image/x-flp"/>
<item id="item67" href="normal/etwx971DL_68.jpg" href2="zoomone/etwx971DL_68.jpg" media-type="image/x-flp"/>
<item id="item68" href="normal/etwx971DL_69.jpg" href2="zoomone/etwx971DL_69.jpg" media-type="image/x-flp"/>
<item id="item69" href="normal/etwx971DL_70.jpg" href2="zoomone/etwx971DL_70.jpg" media-type="image/x-flp"/>
<item id="item70" href="normal/etwx971DL_71.jpg" href2="zoomone/etwx971DL_71.jpg" media-type="image/x-flp"/>
<item id="item71" href="normal/etwx971DL_72.jpg" href2="zoomone/etwx971DL_72.jpg" media-type="image/x-flp"/>
<item id="item72" href="normal/etwx971DL_73.jpg" href2="zoomone/etwx971DL_73.jpg" media-type="image/x-flp"/>
<item id="item73" href="normal/etwx971DL_74.jpg" href2="zoomone/etwx971DL_74.jpg" media-type="image/x-flp"/>
<item id="item74" href="normal/etwx971DL_75.jpg" href2="zoomone/etwx971DL_75.jpg" media-type="image/x-flp"/>
<item id="item75" href="normal/etwx971DL_76.jpg" href2="zoomone/etwx971DL_76.jpg" media-type="image/x-flp"/>
<item id="item76" href="normal/etwx971DL_77.jpg" href2="zoomone/etwx971DL_77.jpg" media-type="image/x-flp"/>
<item id="item77" href="normal/etwx971DL_78.jpg" href2="zoomone/etwx971DL_78.jpg" media-type="image/x-flp"/>
<item id="item78" href="normal/etwx971DL_79.jpg" href2="zoomone/etwx971DL_79.jpg" media-type="image/x-flp"/>
<item id="item79" href="normal/etwx971DL_80.jpg" href2="zoomone/etwx971DL_80.jpg" media-type="image/x-flp"/>
<item id="item80" href="normal/etwx971DL_81.jpg" href2="zoomone/etwx971DL_81.jpg" media-type="image/x-flp"/>
<item id="item81" href="normal/etwx971DL_82.jpg" href2="zoomone/etwx971DL_82.jpg" media-type="image/x-flp"/>
<item id="item82" href="normal/etwx971DL_83.jpg" href2="zoomone/etwx971DL_83.jpg" media-type="image/x-flp"/>
<item id="item83" href="normal/etwx971DL_84.jpg" href2="zoomone/etwx971DL_84.jpg" media-type="image/x-flp"/>
<item id="item84" href="normal/etwx971DL_85.jpg" href2="zoomone/etwx971DL_85.jpg" media-type="image/x-flp"/>
<item id="item85" href="normal/etwx971DL_86.jpg" href2="zoomone/etwx971DL_86.jpg" media-type="image/x-flp"/>
<item id="item86" href="normal/etwx971DL_87.jpg" href2="zoomone/etwx971DL_87.jpg" media-type="image/x-flp"/>
<item id="item87" href="normal/etwx971DL_88.jpg" href2="zoomone/etwx971DL_88.jpg" media-type="image/x-flp"/>
<item id="item88" href="normal/etwx971DL_89.jpg" href2="zoomone/etwx971DL_89.jpg" media-type="image/x-flp"/>
<item id="item89" href="normal/etwx971DL_90.jpg" href2="zoomone/etwx971DL_90.jpg" media-type="image/x-flp"/>
<item id="item90" href="normal/etwx971DL_91.jpg" href2="zoomone/etwx971DL_91.jpg" media-type="image/x-flp"/>
<item id="item91" href="normal/etwx971DL_92.jpg" href2="zoomone/etwx971DL_92.jpg" media-type="image/x-flp"/>
<item id="item92" href="normal/etwx971DL_93.jpg" href2="zoomone/etwx971DL_93.jpg" media-type="image/x-flp"/>
<item id="item93" href="normal/etwx971DL_94.jpg" href2="zoomone/etwx971DL_94.jpg" media-type="image/x-flp"/>
<item id="item94" href="normal/etwx971DL_95.jpg" href2="zoomone/etwx971DL_95.jpg" media-type="image/x-flp"/>
<item id="item95" href="normal/etwx971DL_96.jpg" href2="zoomone/etwx971DL_96.jpg" media-type="image/x-flp"/>
<item id="item96" href="normal/etwx971DL_97.jpg" href2="zoomone/etwx971DL_97.jpg" media-type="image/x-flp"/>
<item id="item97" href="normal/etwx971DL_98.jpg" href2="zoomone/etwx971DL_98.jpg" media-type="image/x-flp"/>
<item id="item98" href="normal/etwx971DL_99.jpg" href2="zoomone/etwx971DL_99.jpg" media-type="image/x-flp"/>
<item id="item99" href="normal/etwx971DL_100.jpg" href2="zoomone/etwx971DL_100.jpg" media-type="image/x-flp"/>
<item id="item100" href="normal/etwx971DL_101.jpg" href2="zoomone/etwx971DL_101.jpg" media-type="image/x-flp"/>
<item id="item101" href="normal/etwx971DL_102.jpg" href2="zoomone/etwx971DL_102.jpg" media-type="image/x-flp"/>
<item id="item102" href="normal/etwx971DL_103.jpg" href2="zoomone/etwx971DL_103.jpg" media-type="image/x-flp"/>
<item id="item103" href="normal/etwx971DL_104.jpg" href2="zoomone/etwx971DL_104.jpg" media-type="image/x-flp"/>
<item id="item104" href="normal/etwx971DL_105.jpg" href2="zoomone/etwx971DL_105.jpg" media-type="image/x-flp"/>
<item id="item105" href="normal/etwx971DL_106.jpg" href2="zoomone/etwx971DL_106.jpg" media-type="image/x-flp"/>
<item id="item106" href="normal/etwx971DL_107.jpg" href2="zoomone/etwx971DL_107.jpg" media-type="image/x-flp"/>
<item id="item107" href="normal/etwx971DL_108.jpg" href2="zoomone/etwx971DL_108.jpg" media-type="image/x-flp"/>
<item id="item108" href="normal/etwx971DL_109.jpg" href2="zoomone/etwx971DL_109.jpg" media-type="image/x-flp"/>
<item id="item109" href="normal/etwx971DL_110.jpg" href2="zoomone/etwx971DL_110.jpg" media-type="image/x-flp"/>
<item id="item110" href="normal/etwx971DL_111.jpg" href2="zoomone/etwx971DL_111.jpg" media-type="image/x-flp"/>
<item id="item111" href="normal/etwx971DL_112.jpg" href2="zoomone/etwx971DL_112.jpg" media-type="image/x-flp"/>
<item id="item112" href="normal/etwx971DL_113.jpg" href2="zoomone/etwx971DL_113.jpg" media-type="image/x-flp"/>
<item id="item113" href="normal/etwx971DL_114.jpg" href2="zoomone/etwx971DL_114.jpg" media-type="image/x-flp"/>
<item id="item114" href="normal/etwx971DL_115.jpg" href2="zoomone/etwx971DL_115.jpg" media-type="image/x-flp"/>
<item id="item115" href="normal/etwx971DL_116.jpg" href2="zoomone/etwx971DL_116.jpg" media-type="image/x-flp"/>
<item id="item116" href="normal/etwx971DL_117.jpg" href2="zoomone/etwx971DL_117.jpg" media-type="image/x-flp"/>
<item id="item117" href="normal/etwx971DL_118.jpg" href2="zoomone/etwx971DL_118.jpg" media-type="image/x-flp"/>
<item id="item118" href="normal/etwx971DL_119.jpg" href2="zoomone/etwx971DL_119.jpg" media-type="image/x-flp"/>
<item id="item119" href="normal/etwx971DL_120.jpg" href2="zoomone/etwx971DL_120.jpg" media-type="image/x-flp"/>
<item id="item120" href="normal/etwx971DL_121.jpg" href2="zoomone/etwx971DL_121.jpg" media-type="image/x-flp"/>
<item id="item121" href="normal/etwx971DL_122.jpg" href2="zoomone/etwx971DL_122.jpg" media-type="image/x-flp"/>
<item id="item122" href="normal/etwx971DL_123.jpg" href2="zoomone/etwx971DL_123.jpg" media-type="image/x-flp"/>
<item id="item123" href="normal/etwx971DL_124.jpg" href2="zoomone/etwx971DL_124.jpg" media-type="image/x-flp"/>
<item id="item124" href="normal/etwx971DL_125.jpg" href2="zoomone/etwx971DL_125.jpg" media-type="image/x-flp"/>
<item id="item125" href="normal/etwx971DL_126.jpg" href2="zoomone/etwx971DL_126.jpg" media-type="image/x-flp"/>
</manifest>
<spine>
<itemref idref="item0" thumbnail="/resources/_thumbnail_/tn_etwx971DL_1.jpg" shadow="no" numberingdescription="1"/>
<itemref idref="item1" thumbnail="/resources/_thumbnail_/tn_etwx971DL_2.jpg" numberingdescription="2"/>
<itemref idref="item2" thumbnail="/resources/_thumbnail_/tn_etwx971DL_3.jpg" numberingdescription="3"/>
<itemref idref="item3" thumbnail="/resources/_thumbnail_/tn_etwx971DL_4.jpg" numberingdescription="4"/>
<itemref idref="item4" thumbnail="/resources/_thumbnail_/tn_etwx971DL_5.jpg" numberingdescription="5"/>
<itemref idref="item5" thumbnail="/resources/_thumbnail_/tn_etwx971DL_6.jpg" numberingdescription="6"/>
<itemref idref="item6" thumbnail="/resources/_thumbnail_/tn_etwx971DL_7.jpg" numberingdescription="7"/>
<itemref idref="item7" thumbnail="/resources/_thumbnail_/tn_etwx971DL_8.jpg" numberingdescription="8"/>
<itemref idref="item8" thumbnail="/resources/_thumbnail_/tn_etwx971DL_9.jpg" numberingdescription="9"/>
<itemref idref="item9" thumbnail="/resources/_thumbnail_/tn_etwx971DL_10.jpg" numberingdescription="10"/>
<itemref idref="item10" thumbnail="/resources/_thumbnail_/tn_etwx971DL_11.jpg" numberingdescription="11"/>
<itemref idref="item11" thumbnail="/resources/_thumbnail_/tn_etwx971DL_12.jpg" numberingdescription="12"/>
<itemref idref="item12" thumbnail="/resources/_thumbnail_/tn_etwx971DL_13.jpg" numberingdescription="13"/>
<itemref idref="item13" thumbnail="/resources/_thumbnail_/tn_etwx971DL_14.jpg" numberingdescription="14"/>
<itemref idref="item14" thumbnail="/resources/_thumbnail_/tn_etwx971DL_15.jpg" numberingdescription="15"/>
<itemref idref="item15" thumbnail="/resources/_thumbnail_/tn_etwx971DL_16.jpg" numberingdescription="16"/>
<itemref idref="item16" thumbnail="/resources/_thumbnail_/tn_etwx971DL_17.jpg" numberingdescription="17"/>
<itemref idref="item17" thumbnail="/resources/_thumbnail_/tn_etwx971DL_18.jpg" numberingdescription="18"/>
<itemref idref="item18" thumbnail="/resources/_thumbnail_/tn_etwx971DL_19.jpg" numberingdescription="19"/>
<itemref idref="item19" thumbnail="/resources/_thumbnail_/tn_etwx971DL_20.jpg" numberingdescription="20"/>
<itemref idref="item20" thumbnail="/resources/_thumbnail_/tn_etwx971DL_21.jpg" numberingdescription="21"/>
<itemref idref="item21" thumbnail="/resources/_thumbnail_/tn_etwx971DL_22.jpg" numberingdescription="22"/>
<itemref idref="item22" thumbnail="/resources/_thumbnail_/tn_etwx971DL_23.jpg" numberingdescription="23"/>
<itemref idref="item23" thumbnail="/resources/_thumbnail_/tn_etwx971DL_24.jpg" numberingdescription="24"/>
<itemref idref="item24" thumbnail="/resources/_thumbnail_/tn_etwx971DL_25.jpg" numberingdescription="25"/>
<itemref idref="item25" thumbnail="/resources/_thumbnail_/tn_etwx971DL_26.jpg" numberingdescription="26"/>
<itemref idref="item26" thumbnail="/resources/_thumbnail_/tn_etwx971DL_27.jpg" numberingdescription="27"/>
<itemref idref="item27" thumbnail="/resources/_thumbnail_/tn_etwx971DL_28.jpg" numberingdescription="28"/>
<itemref idref="item28" thumbnail="/resources/_thumbnail_/tn_etwx971DL_29.jpg" numberingdescription="29"/>
<itemref idref="item29" thumbnail="/resources/_thumbnail_/tn_etwx971DL_30.jpg" numberingdescription="30"/>
<itemref idref="item30" thumbnail="/resources/_thumbnail_/tn_etwx971DL_31.jpg" numberingdescription="31"/>
<itemref idref="item31" thumbnail="/resources/_thumbnail_/tn_etwx971DL_32.jpg" numberingdescription="32"/>
<itemref idref="item32" thumbnail="/resources/_thumbnail_/tn_etwx971DL_33.jpg" numberingdescription="33"/>
<itemref idref="item33" thumbnail="/resources/_thumbnail_/tn_etwx971DL_34.jpg" numberingdescription="34"/>
<itemref idref="item34" thumbnail="/resources/_thumbnail_/tn_etwx971DL_35.jpg" numberingdescription="35"/>
<itemref idref="item35" thumbnail="/resources/_thumbnail_/tn_etwx971DL_36.jpg" numberingdescription="36"/>
<itemref idref="item36" thumbnail="/resources/_thumbnail_/tn_etwx971DL_37.jpg" numberingdescription="37"/>
<itemref idref="item37" thumbnail="/resources/_thumbnail_/tn_etwx971DL_38.jpg" numberingdescription="38"/>
<itemref idref="item38" thumbnail="/resources/_thumbnail_/tn_etwx971DL_39.jpg" numberingdescription="39"/>
<itemref idref="item39" thumbnail="/resources/_thumbnail_/tn_etwx971DL_40.jpg" numberingdescription="40"/>
<itemref idref="item40" thumbnail="/resources/_thumbnail_/tn_etwx971DL_41.jpg" numberingdescription="41"/>
<itemref idref="item41" thumbnail="/resources/_thumbnail_/tn_etwx971DL_42.jpg" numberingdescription="42"/>
<itemref idref="item42" thumbnail="/resources/_thumbnail_/tn_etwx971DL_43.jpg" numberingdescription="43"/>
<itemref idref="item43" thumbnail="/resources/_thumbnail_/tn_etwx971DL_44.jpg" numberingdescription="44"/>
<itemref idref="item44" thumbnail="/resources/_thumbnail_/tn_etwx971DL_45.jpg" numberingdescription="45"/>
<itemref idref="item45" thumbnail="/resources/_thumbnail_/tn_etwx971DL_46.jpg" numberingdescription="46"/>
<itemref idref="item46" thumbnail="/resources/_thumbnail_/tn_etwx971DL_47.jpg" numberingdescription="47"/>
<itemref idref="item47" thumbnail="/resources/_thumbnail_/tn_etwx971DL_48.jpg" numberingdescription="48"/>
<itemref idref="item48" thumbnail="/resources/_thumbnail_/tn_etwx971DL_49.jpg" numberingdescription="49"/>
<itemref idref="item49" thumbnail="/resources/_thumbnail_/tn_etwx971DL_50.jpg" numberingdescription="50"/>
<itemref idref="item50" thumbnail="/resources/_thumbnail_/tn_etwx971DL_51.jpg" numberingdescription="51"/>
<itemref idref="item51" thumbnail="/resources/_thumbnail_/tn_etwx971DL_52.jpg" numberingdescription="52"/>
<itemref idref="item52" thumbnail="/resources/_thumbnail_/tn_etwx971DL_53.jpg" numberingdescription="53"/>
<itemref idref="item53" thumbnail="/resources/_thumbnail_/tn_etwx971DL_54.jpg" numberingdescription="54"/>
<itemref idref="item54" thumbnail="/resources/_thumbnail_/tn_etwx971DL_55.jpg" numberingdescription="55"/>
<itemref idref="item55" thumbnail="/resources/_thumbnail_/tn_etwx971DL_56.jpg" numberingdescription="56"/>
<itemref idref="item56" thumbnail="/resources/_thumbnail_/tn_etwx971DL_57.jpg" numberingdescription="57"/>
<itemref idref="item57" thumbnail="/resources/_thumbnail_/tn_etwx971DL_58.jpg" numberingdescription="58"/>
<itemref idref="item58" thumbnail="/resources/_thumbnail_/tn_etwx971DL_59.jpg" numberingdescription="59"/>
<itemref idref="item59" thumbnail="/resources/_thumbnail_/tn_etwx971DL_60.jpg" numberingdescription="60"/>
<itemref idref="item60" thumbnail="/resources/_thumbnail_/tn_etwx971DL_61.jpg" numberingdescription="61"/>
<itemref idref="item61" thumbnail="/resources/_thumbnail_/tn_etwx971DL_62.jpg" numberingdescription="62"/>
<itemref idref="item62" thumbnail="/resources/_thumbnail_/tn_etwx971DL_63.jpg" numberingdescription="63"/>
<itemref idref="item63" thumbnail="/resources/_thumbnail_/tn_etwx971DL_64.jpg" numberingdescription="64"/>
<itemref idref="item64" thumbnail="/resources/_thumbnail_/tn_etwx971DL_65.jpg" numberingdescription="65"/>
<itemref idref="item65" thumbnail="/resources/_thumbnail_/tn_etwx971DL_66.jpg" numberingdescription="66"/>
<itemref idref="item66" thumbnail="/resources/_thumbnail_/tn_etwx971DL_67.jpg" numberingdescription="67"/>
<itemref idref="item67" thumbnail="/resources/_thumbnail_/tn_etwx971DL_68.jpg" numberingdescription="68"/>
<itemref idref="item68" thumbnail="/resources/_thumbnail_/tn_etwx971DL_69.jpg" numberingdescription="69"/>
<itemref idref="item69" thumbnail="/resources/_thumbnail_/tn_etwx971DL_70.jpg" numberingdescription="70"/>
<itemref idref="item70" thumbnail="/resources/_thumbnail_/tn_etwx971DL_71.jpg" numberingdescription="71"/>
<itemref idref="item71" thumbnail="/resources/_thumbnail_/tn_etwx971DL_72.jpg" numberingdescription="72"/>
<itemref idref="item72" thumbnail="/resources/_thumbnail_/tn_etwx971DL_73.jpg" numberingdescription="73"/>
<itemref idref="item73" thumbnail="/resources/_thumbnail_/tn_etwx971DL_74.jpg" numberingdescription="74"/>
<itemref idref="item74" thumbnail="/resources/_thumbnail_/tn_etwx971DL_75.jpg" numberingdescription="75"/>
<itemref idref="item75" thumbnail="/resources/_thumbnail_/tn_etwx971DL_76.jpg" numberingdescription="76"/>
<itemref idref="item76" thumbnail="/resources/_thumbnail_/tn_etwx971DL_77.jpg" numberingdescription="77"/>
<itemref idref="item77" thumbnail="/resources/_thumbnail_/tn_etwx971DL_78.jpg" numberingdescription="78"/>
<itemref idref="item78" thumbnail="/resources/_thumbnail_/tn_etwx971DL_79.jpg" numberingdescription="79"/>
<itemref idref="item79" thumbnail="/resources/_thumbnail_/tn_etwx971DL_80.jpg" numberingdescription="80"/>
<itemref idref="item80" thumbnail="/resources/_thumbnail_/tn_etwx971DL_81.jpg" numberingdescription="81"/>
<itemref idref="item81" thumbnail="/resources/_thumbnail_/tn_etwx971DL_82.jpg" numberingdescription="82"/>
<itemref idref="item82" thumbnail="/resources/_thumbnail_/tn_etwx971DL_83.jpg" numberingdescription="83"/>
<itemref idref="item83" thumbnail="/resources/_thumbnail_/tn_etwx971DL_84.jpg" numberingdescription="84"/>
<itemref idref="item84" thumbnail="/resources/_thumbnail_/tn_etwx971DL_85.jpg" numberingdescription="85"/>
<itemref idref="item85" thumbnail="/resources/_thumbnail_/tn_etwx971DL_86.jpg" numberingdescription="86"/>
<itemref idref="item86" thumbnail="/resources/_thumbnail_/tn_etwx971DL_87.jpg" numberingdescription="87"/>
<itemref idref="item87" thumbnail="/resources/_thumbnail_/tn_etwx971DL_88.jpg" numberingdescription="88"/>
<itemref idref="item88" thumbnail="/resources/_thumbnail_/tn_etwx971DL_89.jpg" numberingdescription="89"/>
<itemref idref="item89" thumbnail="/resources/_thumbnail_/tn_etwx971DL_90.jpg" numberingdescription="90"/>
<itemref idref="item90" thumbnail="/resources/_thumbnail_/tn_etwx971DL_91.jpg" numberingdescription="91"/>
<itemref idref="item91" thumbnail="/resources/_thumbnail_/tn_etwx971DL_92.jpg" numberingdescription="92"/>
<itemref idref="item92" thumbnail="/resources/_thumbnail_/tn_etwx971DL_93.jpg" numberingdescription="93"/>
<itemref idref="item93" thumbnail="/resources/_thumbnail_/tn_etwx971DL_94.jpg" numberingdescription="94"/>
<itemref idref="item94" thumbnail="/resources/_thumbnail_/tn_etwx971DL_95.jpg" numberingdescription="95"/>
<itemref idref="item95" thumbnail="/resources/_thumbnail_/tn_etwx971DL_96.jpg" numberingdescription="96"/>
<itemref idref="item96" thumbnail="/resources/_thumbnail_/tn_etwx971DL_97.jpg" numberingdescription="97"/>
<itemref idref="item97" thumbnail="/resources/_thumbnail_/tn_etwx971DL_98.jpg" numberingdescription="98"/>
<itemref idref="item98" thumbnail="/resources/_thumbnail_/tn_etwx971DL_99.jpg" numberingdescription="99"/>
<itemref idref="item99" thumbnail="/resources/_thumbnail_/tn_etwx971DL_100.jpg" numberingdescription="100"/>
<itemref idref="item100" thumbnail="/resources/_thumbnail_/tn_etwx971DL_101.jpg" numberingdescription="101"/>
<itemref idref="item101" thumbnail="/resources/_thumbnail_/tn_etwx971DL_102.jpg" numberingdescription="102"/>
<itemref idref="item102" thumbnail="/resources/_thumbnail_/tn_etwx971DL_103.jpg" numberingdescription="103"/>
<itemref idref="item103" thumbnail="/resources/_thumbnail_/tn_etwx971DL_104.jpg" numberingdescription="104"/>
<itemref idref="item104" thumbnail="/resources/_thumbnail_/tn_etwx971DL_105.jpg" numberingdescription="105"/>
<itemref idref="item105" thumbnail="/resources/_thumbnail_/tn_etwx971DL_106.jpg" numberingdescription="106"/>
<itemref idref="item106" thumbnail="/resources/_thumbnail_/tn_etwx971DL_107.jpg" numberingdescription="107"/>
<itemref idref="item107" thumbnail="/resources/_thumbnail_/tn_etwx971DL_108.jpg" numberingdescription="108"/>
<itemref idref="item108" thumbnail="/resources/_thumbnail_/tn_etwx971DL_109.jpg" numberingdescription="109"/>
<itemref idref="item109" thumbnail="/resources/_thumbnail_/tn_etwx971DL_110.jpg" numberingdescription="110"/>
<itemref idref="item110" thumbnail="/resources/_thumbnail_/tn_etwx971DL_111.jpg" numberingdescription="111"/>
<itemref idref="item111" thumbnail="/resources/_thumbnail_/tn_etwx971DL_112.jpg" numberingdescription="112"/>
<itemref idref="item112" thumbnail="/resources/_thumbnail_/tn_etwx971DL_113.jpg" numberingdescription="113"/>
<itemref idref="item113" thumbnail="/resources/_thumbnail_/tn_etwx971DL_114.jpg" numberingdescription="114"/>
<itemref idref="item114" thumbnail="/resources/_thumbnail_/tn_etwx971DL_115.jpg" numberingdescription="115"/>
<itemref idref="item115" thumbnail="/resources/_thumbnail_/tn_etwx971DL_116.jpg" numberingdescription="116"/>
<itemref idref="item116" thumbnail="/resources/_thumbnail_/tn_etwx971DL_117.jpg" numberingdescription="117"/>
<itemref idref="item117" thumbnail="/resources/_thumbnail_/tn_etwx971DL_118.jpg" numberingdescription="118"/>
<itemref idref="item118" thumbnail="/resources/_thumbnail_/tn_etwx971DL_119.jpg" numberingdescription="119"/>
<itemref idref="item119" thumbnail="/resources/_thumbnail_/tn_etwx971DL_120.jpg" numberingdescription="120"/>
<itemref idref="item120" thumbnail="/resources/_thumbnail_/tn_etwx971DL_121.jpg" numberingdescription="121"/>
<itemref idref="item121" thumbnail="/resources/_thumbnail_/tn_etwx971DL_122.jpg" numberingdescription="122"/>
<itemref idref="item122" thumbnail="/resources/_thumbnail_/tn_etwx971DL_123.jpg" numberingdescription="123"/>
<itemref idref="item123" thumbnail="/resources/_thumbnail_/tn_etwx971DL_124.jpg" numberingdescription="124"/>
<itemref idref="item124" thumbnail="/resources/_thumbnail_/tn_etwx971DL_125.jpg" numberingdescription="125"/>
<itemref idref="item125" thumbnail="/resources/_thumbnail_/tn_etwx971DL_126.jpg" shadow="no" numberingdescription="126"/>
</spine>
<authoring name="FlipViewer Xpress Creator" version="3.6.0.305" time="2021-12-30-17-03-GMT8" nogui="yes"/>
<drm_enabled version="4.0.0" bookid="8add12c734dc2dc000d69ee2f5772d26" authorid="ccppg">
<publishing title="etwx971DL" id="">
<issue title="etwx971DL" id="8add12c734dc2dc000d69ee2f5772d26"/>
</publishing>
<publisher name="CCPPG-Et9t8SWt"/>
<additionalcontrol signature="4947e3b6d8febbed5aa71ed1644f3e97"/>
<modifiedby name="FlipViewer Xpress Creator" version="3.6.0.305" time="2021-12-30-17-03-GMT8" nogui="yes"/>
<certificate type="2" url="etwx971DL_license__license_.xml" alternative="http://asp16.digitalflip.com/drm2/getCert" trial="no"/>
<searchabletext url="etwx971DL_text_.xml"/>
<linkdef url="etwx971DL_linkdef_.xml"/>
<coverimage url="etwx971DL_cover_.jpg"/>
<archive url="etwx971DL_archive_.xml"/>
<customized allow="yes">
<ibutton show="no" click="http://www.flipviewer.com"/>
<aboutbox allow="yes" title="etwx971DL" url="http://www.ccppg.com.cn/" company="中国少年儿童新闻出版总社." logo="" author="" tel="010-57526117" address="北京市建国门外大街丙12号(100022) ." email="61read@ccppg.com.cn"/>
<fliptopage show="yes"/>
<fliptocover show="yes"/>
<bookmark show="yes"/>
<search show="yes"/>
<help show="yes" detailhelphidden="yes"/>
<branding show="no" url="http://www.flipviewer.com" logo=""/>
<shadowmargin show="yes"/>
<flipindicator show="yes"/>
<sendemail show="no"/>
<socialnetwork show="yes"/>
<printsinglepageonly show="no"/>
<thumbnail show="yes"/>
<pagetips show="yes"/>
<toolbartips show="yes"/>
<pagedescription show="no" external=""/>
<note show="yes"/>
<winframe show="yes"/>
<close show="no"/>
<toolbar show="yes" position="7" color="8882055" noshadow="no"/>
<sidebar show="yes" position="9"/>
<saveas show="no"/>
<highlight show="yes" style="translucent"/>
<copytext show="no"/>
<settings show="yes"/>
<autoflipping show="yes"/>
<watermark show="no"/>
<zoompage show="yes" factor="150" rubberband="no" dblclick="yes"/>
<archive show="no"/>
<view_mode show="yes" content="landscape" fit2height="yes"/>
</customized>
<digital_signature content="50f39a0f434ac85fddb45f3b2dc5a593" checksum="593553436E6D78575A446D70796F59532F56734E48644139794D504A652F48474270436F6B2B6B656E64633D"/>
</drm_enabled>
</package>

在XML中包括了几个其他的XML文件:

  1. etwx971DL_license__license_.xml
  2. etwx971DL_text_.xml
  3. etwx971DL_linkdef_.xml
  4. etwx971DL_archive_.xml

其中,etwx971DL_license__license_.xml包括了现在看起来意味不明的内容,etwx971DL_text_.xml是一个包含了书籍的部分文本,看起来应该是方便检索使用功能的,etwx971DL_linkdef_.xml是一个没有实际内容的文件,
etwx971DL_archive_.xml指明了封面图和文本xml地址,其他并没有提供额外信息。

etwx971DL_license__license_.xml 的完整内容:

etwx971DL_license__license_.xml
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
<package unique-identifier="{2FFC6E0F-48D2-45B3-A5B3-4706235FE0B1}">
<script/>
<certificate version="4.0.0" id="8add12c734dc2dc000d69ee2f5772d26" authorid="ccppg">
<security encryption="yes" password="yes">
<flipbook id="8add12c734dc2dc000d69ee2f5772d26">
<signature content="5acbea65386e418ec804050cd5a55211"/>
<integrity content="2c29c55815b10f67a4ef999297952490"/>
</flipbook>
<encryption content="63534D4B6D5462467654694247654D3451564C3652624D667777355736374D79657655715A6B37633464343D"/>
<password content="2B644E7238513D3D"/>
<constraints>
<print printmaxpagenumber="10"/>
</constraints>
</security>
<rights>
<permissions>
<pdf allow="yes"/>
<embed allow="yes"/>
<display allow="yes"/>
<enlarge allow="yes"/>
<print allow="no"/>
<copytext allow="no"/>
<download allow="yes"/>
</permissions>
</rights>
</certificate>
<certificate_digital_signature>
<signature content="a80a70215d901311939fe4765a66259d" checksum="9732dd58833d52787b77b6073b046a39"/>
</certificate_digital_signature>
</package>

同时还包括了每一页的下载地址:

1
<item id="item0" href="normal/etwx971DL_1.jpg" href2="zoomone/etwx971DL_1.jpg" media-type="image/x-flp"/>

实质上每一页包括了两个地址,一个是普通的图片地址,另一个是放大后的图片地址,阅读时放大图片其实是加载了另一个张更清晰的图片进行放大,直接访问对应的地址
就可以获取到相应的图片。

获取到的图片

那么事情的脉络已经较为清晰,我们需要做的是:

  1. 获取到每一本书对应的 XML 文件
  2. 通过 XML 文件获取到每一页的图片地址
  3. 下载图片,合成 PDF

看起来到这里都合情合理,但不难发现,有一些额外的内容在这里特别显眼,首先是 password,其次是 license 文件,在已知有 Flash 版本的前提下,
这一系列都预示着如果要通过 Flash 版本的方式进行下载,有着复杂的过程。

直接下载 HTML 5 图片并合成 PDF

虽然每一本书对应一个 UUID,可以通过爬取索引页面获得,但其实根据链接的格式,我们可以直接通过链接猜到对应的 XML 地址,这样会方便一些,在实际过程中,
这些地址有所改动的地方基本只有 SLDL 的替换,例如 2021 年 12 月的分别为:

1
2
3
4
http://202.96.31.36:8888/fliphtml5/password/main/qikan/etwx/2021/12/971/web/html5/tablet/etwx971DL.xml
http://202.96.31.36:8888/fliphtml5/password/main/qikan/etwx/2021/12/972/web/html5/tablet/etwx972DL.xml
http://202.96.31.36:8888/fliphtml5/password/main/qikan/etwx/2021/12/973/web/html5/tablet/etwx973SL.xml
http://202.96.31.36:8888/fliphtml5/password/main/qikan/etwx/2021/12/974/web/html5/tablet/etwx974SL.xml

通过这样的方式可以直接获取到几乎所有的 XML 文件,对于特殊的月份,需要单独进行处理,手动打开页面获取地址即可。

随后就是下载图片和合成,这里通过 Python 快速实现,虽然程序是中少版的,但国图版其实是一致的,只需要修改一下地址即可。

Python 程序
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
import requests
from bs4 import BeautifulSoup
import os

nowurl = "http://hangzhoushaoer.61read.com/open/main/qikan/etwx/2023/12/1070/web/html5/tablet/etwx1070SL.xml"

dirname = nowurl.split('/')[-1].replace('.xml', '')
# 创建文件夹,并下载 XML 文件
os.makedirs(dirname, exist_ok=True)
res = requests.get(nowurl)
if res.status_code == 404:
res = requests.get(nowurl.replace('SL.xml', 'DL.xml'))
res.encoding = 'utf-8'
# 解析 XML 文件
soup = BeautifulSoup(res.text, 'lxml')
baseurls = nowurl.split('/')[:-1]
imgurls = [('/'.join(baseurls) + '/' + item['href2']) for item in soup.find_all('item', {'media-type': 'image/x-flp'})]
# 下载图片
for imgurl in imgurls:
imgname = imgurl.split('/')[-1].split('_')[-1]
if imgname[0] == 'r' or imgname[0] == 'l':
imgname = imgurl.split('/')[-1].split('_')[-2] + imgname
# 保存图片
imgname = imgname.zfill(8)
imgres = requests.get(imgurl)
with open(dirname + '/' + imgname, 'wb') as f:
f.write(imgres.content)
# 转换为 PDF
from PIL import Image
imgnames = os.listdir(dirname)
imgnames.sort()
imgnames = [dirname + '/' + imgname for imgname in imgnames]
img = Image.open(imgnames[0])
img.save(dirname + '.pdf', "PDF", resolution=100.0, save_all=True, append_images=[Image.open(imgname) for imgname in imgnames[1:]])

这样就可以直接下载并合成 PDF 了,这里的代码是直接下载了放大后的图片,给定 XML 地址后,即可完成下载和合成。

转折

就这样,完成了第一批的下载和合成,在和大佬交流的过程中,发现了 Flash 版本,Flash版本下载的内容大小和 HTML5 下载大小有一定偏差。

这是大佬:

这是大佬

然后,寻思,对比一下两个版本的内容,发现了一些细节上的问题,直接上对比图(左侧为 H5 版本,右侧为 Flash 版本)

以 2014 年 1 月 经典为例:

封面:
对比图1

文章页:
对比图2

很显然,Flash 版本的内容更加清晰,H5 的图片在 JPEG 小波处理的影响下,边缘变得模糊,有明显锯齿,而 Flash 版本则没有这么严重。

那么既然想要收藏,那折腾肯定是要的,那么就开始对 Flash 版本下手吧。

Flash 版本阅读器

首先依然观察链接,Flash 链接和 H5 链接的区别只在 fliphtml5 和 flipbooks 的替换上,其他一致,XML文件,图片地址都是一致的。

1
http://202.96.31.36:8888/flipbooks/password/main/qikan/etwx/2014/01/591/web/flipviewerxpress.html?pswd=7391

直接下载一个 Flash 页面,
以封面
对应的 链接 为例,
下载无法打开,通过十六进制编辑器查看文件头非常混乱,明显经过了加密处理。

Flash 文件头

既然浏览器可以打开阅读,说明播放的工具有着解密功能,观察可以发现 license 文件中似乎已经提供了一部分内容,那么就需要来分析一下 Flash 阅读器了。

通过任意一本期刊都可以获取到同一个播放器的 swf 文件,
链接在这里,实际获取链接中带了参数,这些参数传递了 XML 文件的地址,暂时忽略。

获取到 swf 文件后,进行快速检查,并没有对这个 swf 文件进行额外的加密,那么就可以直接使用 JEPXS 进行反编译了。

通过 JEPXS 反编译后,得到几乎可读的源码。

其中有这么一些很令人在意:

在 FVDRMCertificate.as 中,找到了加载 license 验证 license 的部分,确定了如果手动修改拦截了 license 文件,但不对播放器进行处理是无效的,
可以利用 JEPXS 对 PCODE 进行修改来实现忽略不正确的 license 以实现相应功能,同时找到了密码所在,因为web访问自带密码,所以几乎无感,但如果是下载
后打开,就需要这一密码,这部分内容与解密下载的 swf 无关,此处不过多站开。

跟随打开书的逻辑在fv.tool.FVLoadTool.as 中找到了加载页面的逻辑:

onPageDataLoaded
1
2
3
4
5
6
7
8
private function onPageDataLoaded(objectByte:ByteArray, url:String, success:Boolean) : void
{
if(FVDRMCertificate.getInstance().encrypted)
{
objectByte = FVUtil.encryptStream(objectByte,FVDRMCertificate.getInstance().encryptionKey,false);
}
this._loader.loadByteObject(objectByte,url,this.onPageObjectLoaded,this);
}

跟随找到 fv.common.FVUtil.as 中的 encryptStream:

encryptStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static function encryptStream(inArray:ByteArray, key:String, isEncryption:Boolean) : ByteArray
{
var outArray:ByteArray = null;
var i:int = 0;
var len:int = 1024 * 8;
if(inArray.length <= len)
{
return FVCipher.getInstance().encryptStream(inArray,key,inArray.length,isEncryption);
}
outArray = FVCipher.getInstance().encryptStream(inArray,key,len,isEncryption);
for(i = len; i <= inArray.length; i++)
{
outArray[i] = inArray[i];
}
return outArray;
}

这里发现定义了一个 10248 的长度,如果小于这个长度,就直接加密,否则只加密前 10248 长度的内容,也就是说,只需要对前 8k 长度的内容进行解密即可。

跟随加密方式找到了 fv.common.FVCipher.as 中的加密方式:

encryptStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function encryptStream(strm:ByteArray, encryptKey:String, bufSize:int, isEncryption:Boolean) : ByteArray
{
if(strm.length == 0)
{
return strm;
}
var szKey:ByteArray = new ByteArray();
szKey.writeUTFBytes(encryptKey);
var cipherMgr:FVCipherMgr = FVCipherMgr.getInstance();
cipherMgr.initKey(szKey,encryptKey.length);
var outStrm:ByteArray = new ByteArray();
if(isEncryption)
{
cipherMgr.encodeBuffer(strm,outStrm,bufSize);
}
else
{
cipherMgr.decodeBuffer(strm,outStrm,bufSize);
}
return outStrm;
}

这里的加密方式是通过 FVCipherMgr 进行的,这里的加密方式是对称加密,通过 szKey 进行初始化,然后对 strm 进行加密,继续分析 encodeBuffer:

encodeBuffer
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
public function encodeBuffer(src:ByteArray, dest:ByteArray, dataSize:int) : void
{
var i:int = 0;
var tempBlock:ByteArray = null;
var point:int = 0;
var plainTextBlock:ByteArray = new ByteArray();
plainTextBlock.length = this.SAFER_BLOCK_LEN;
var offset:int = 0;
var arrayPoint:int = 0;
var safer:FVSafer = FVSafer.getInstance();
while(dataSize >= this.SAFER_BLOCK_LEN)
{
for(i = 0; i < this.SAFER_BLOCK_LEN; i++)
{
plainTextBlock[i] = src[arrayPoint + i] ^ this._lastBlock[i];
}
safer.saferEncryptBlock(plainTextBlock,this._encryptKey,dest,arrayPoint);
for(i = 0; i < this.SAFER_BLOCK_LEN; i++)
{
this._lastBlock[i] ^= dest[arrayPoint + i];
}
arrayPoint += this.SAFER_BLOCK_LEN;
dataSize -= this.SAFER_BLOCK_LEN;
}
if(dataSize > 0)
{
tempBlock = new ByteArray();
tempBlock.length = this.SAFER_BLOCK_LEN;
point = 0;
safer.saferEncryptBlock(this._lastBlock,this._encryptKey,tempBlock,point);
for(i = 0; i < dataSize; i++)
{
dest[arrayPoint + i] = src[arrayPoint + i] ^ tempBlock[i];
}
for(i = 0; i < this.SAFER_BLOCK_LEN; i++)
{
this._lastBlock[i] ^= tempBlock[i];
}
}
}

这里的加密方式是 SAFER 算法,通过搜索引擎并没有找到相关的解密方式,需要进一步分析,通过 FVSafer.as 找到了 SAFER 算法的实现,

saferEncryptBlock
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public function saferEncryptBlock(block_in:ByteArray, key:ByteArray, block_out:ByteArray, arrayPoint:int) : void
{
var a:uint = 0;
var b:uint = 0;
var c:uint = 0;
var d:uint = 0;
var e:uint = 0;
var f:uint = 0;
var g:uint = 0;
var h:uint = 0;
var t:uint = 0;
var round:uint = 0;
var keyPoint:int = 0;
a = uint(block_in[0]);
b = uint(block_in[1]);
c = uint(block_in[2]);
d = uint(block_in[3]);
e = uint(block_in[4]);
f = uint(block_in[5]);
g = uint(block_in[6]);
h = uint(block_in[7]);
if(this.SAFER_MAX_NOF_ROUNDS < (round = uint(key[0])))
{
round = uint(this.SAFER_MAX_NOF_ROUNDS);
}
while(round--)
{
a ^= key[++keyPoint];
b += key[++keyPoint];
c += key[++keyPoint];
d ^= key[++keyPoint];
e ^= key[++keyPoint];
f += key[++keyPoint];
g += key[++keyPoint];
h ^= key[++keyPoint];
a = this._exp_tab[a & 255] + key[++keyPoint];
b = uint(this._log_tab[b & 255] ^ key[++keyPoint]);
c = uint(this._log_tab[c & 255] ^ key[++keyPoint]);
d = this._exp_tab[d & 255] + key[++keyPoint];
e = this._exp_tab[e & 255] + key[++keyPoint];
f = uint(this._log_tab[f & 255] ^ key[++keyPoint]);
g = uint(this._log_tab[g & 255] ^ key[++keyPoint]);
h = this._exp_tab[h & 255] + key[++keyPoint];
b += a;
a += b;
d += c;
c += d;
f += e;
e += f;
h += g;
g += h;
c += a;
a += c;
g += e;
e += g;
d += b;
b += d;
h += f;
f += h;
e += a;
a += e;
f += b;
b += f;
g += c;
c += g;
h += d;
d += h;
t = b;
b = e;
e = c;
c = t;
t = d;
d = f;
f = g;
g = t;
}
a ^= key[++keyPoint];
b += key[++keyPoint];
c += key[++keyPoint];
d ^= key[++keyPoint];
e ^= key[++keyPoint];
f += key[++keyPoint];
g += key[++keyPoint];
h ^= key[++keyPoint];
block_out[arrayPoint + 0] = a & 255;
block_out[arrayPoint + 1] = b & 255;
block_out[arrayPoint + 2] = c & 255;
block_out[arrayPoint + 3] = d & 255;
block_out[arrayPoint + 4] = e & 255;
block_out[arrayPoint + 5] = f & 255;
block_out[arrayPoint + 6] = g & 255;
block_out[arrayPoint + 7] = h & 255;
}

同时找到了解密函数:

saferDecryptBlock
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public function saferDecryptBlock(block_in:ByteArray, key:ByteArray, block_out:ByteArray, arrayPoint:int) : void
{
var a:uint = 0;
var b:uint = 0;
var c:uint = 0;
var d:uint = 0;
var e:uint = 0;
var f:uint = 0;
var g:uint = 0;
var h:uint = 0;
var t:uint = 0;
var round:uint = 0;
var keyPoint:int = 0;
a = uint(block_in[0]);
b = uint(block_in[1]);
c = uint(block_in[2]);
d = uint(block_in[3]);
e = uint(block_in[4]);
f = uint(block_in[5]);
g = uint(block_in[6]);
h = uint(block_in[7]);
if(this.SAFER_MAX_NOF_ROUNDS < (round = uint(key[0])))
{
round = uint(this.SAFER_MAX_NOF_ROUNDS);
}
keyPoint += this.SAFER_BLOCK_LEN * (1 + 2 * round);
h ^= key[keyPoint];
g -= key[--keyPoint];
f -= key[--keyPoint];
e ^= key[--keyPoint];
d ^= key[--keyPoint];
c -= key[--keyPoint];
b -= key[--keyPoint];
a ^= key[--keyPoint];
while(round--)
{
t = e;
e = b;
b = c;
c = t;
t = f;
f = d;
d = g;
g = t;
a -= e;
e -= a;
b -= f;
f -= b;
c -= g;
g -= c;
d -= h;
h -= d;
a -= c;
c -= a;
e -= g;
g -= e;
b -= d;
d -= b;
f -= h;
h -= f;
a -= b;
b -= a;
c -= d;
d -= c;
e -= f;
f -= e;
g -= h;
h -= g;
h -= key[--keyPoint];
g ^= key[--keyPoint];
f ^= key[--keyPoint];
e -= key[--keyPoint];
d -= key[--keyPoint];
c ^= key[--keyPoint];
b ^= key[--keyPoint];
a -= key[--keyPoint];
h = uint(this._log_tab[h & 255] ^ key[--keyPoint]);
g = this._exp_tab[g & 255] - key[--keyPoint];
f = this._exp_tab[f & 255] - key[--keyPoint];
e = uint(this._log_tab[e & 255] ^ key[--keyPoint]);
d = uint(this._log_tab[d & 255] ^ key[--keyPoint]);
c = this._exp_tab[c & 255] - key[--keyPoint];
b = this._exp_tab[b & 255] - key[--keyPoint];
a = uint(this._log_tab[a & 255] ^ key[--keyPoint]);
}
block_out[arrayPoint + 0] = a & 255;
block_out[arrayPoint + 1] = b & 255;
block_out[arrayPoint + 2] = c & 255;
block_out[arrayPoint + 3] = d & 255;
block_out[arrayPoint + 4] = e & 255;
block_out[arrayPoint + 5] = f & 255;
block_out[arrayPoint + 6] = g & 255;
block_out[arrayPoint + 7] = h & 255;
}

只需要实现这个解密函数,就可以对前 8k 的内容进行解密,就可以对文件进行解密,同时解密密钥是在 FVDRMCertificate 中的 encryptionKey 中,
但和 license 文件中的 encryption 略微不同:

在加载 license 函数中有如下内容:

parseLicense
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
public function parseLicense(licenseContents:String) : void
{
this._licenseContents = licenseContents;
var licenseParser:XMLDocument = new XMLDocument();
licenseParser.ignoreWhite = true;
licenseParser.parseXML(this._licenseContents);
var packageNode:XMLNode = FVUtil.childNodeForName("package",licenseParser);
var certificateNode:XMLNode = FVUtil.childNodeForName("certificate",packageNode);
this.parseCertificateSession(certificateNode);
var signatureNode:XMLNode = FVUtil.childNodeForName("certificate_digital_signature.signature",packageNode);
this._certificateSignature = signatureNode.attributes.content;
if(this._embeddedLicense)
{
if(this._encryptionKey != "")
{
this._encryptionKey = FVUtil.encryptString(this._encryptionKey,FVConstants.constEncryptKeyCDViewer,false,true);
}
}
else if(this._encryptionKey != "" && this._nodeId != "")
{
if(FVBookOpenTool.getInstance().license != "")
{
this._encryptionKey = FVUtil.encryptString(this._encryptionKey,this._nodeId,false,true);
}
else
{
this._encryptionKey = FVUtil.encryptString(this._encryptionKey,FVUtil.encryptString(this._nodeId,FVConstants.constKeyForHttp,false,true),false,true);
}
}
var bookinterity:Boolean = this._bookSignature == this._tempBookSignature || FVConstants.DEBUG_MODE;
if(this._bookId != this._tempId || !bookinterity || !this.checkIntegrityOfLicense(this._licenseContents) && !FVConstants.DEBUG_MODE)
{
this._errorCode = FVLocal.LICENSE_TAMPERED_ERROR;
this._callback.apply(this._caller);
}
else
{
this._errorCode = 0;
this._currentTime = new Date();
if(this._embeddedLicense && FVBook.getInstance().certInfo.trial || !this._embeddedLicense && (this.getNeedCheckServerTime() || this._checkLicenseCondition != ""))
{
this.getServerTime();
}
else
{
this.getLicenseValidity();
}
}
}

这里的 encryptionKey 是通过 encryptString 进行加密的,同时还包括了一个 constEncryptKeyCDViewer,
这个值是在 FVConstants 中定义的,这个值是一个固定的字符串,可以直接通过反编译找到:

1
public static const constEncryptKeyCDViewer:* = "0b8b6a4650b148a1975331bc2da63f93";

再次跟随找到字符串的加密函数:

encryptString
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
59
60
public function encryptString(str:String, encryptKey:String, isEncryption:Boolean, isHex:Boolean) : String
{
var i:int = 0;
var outStr2:ByteArray = null;
var outStr3:ByteArray = null;
if(str == "" || str == null)
{
return str;
}
var outStr:ByteArray = new ByteArray();
var keyLen:int = encryptKey.length;
var szKey:ByteArray = new ByteArray();
szKey.writeUTFBytes(encryptKey);
var cipherMgr:FVCipherMgr = FVCipherMgr.getInstance();
cipherMgr.initKey(szKey,keyLen);
var outPut:String = "";
var strBuffer:ByteArray = new ByteArray();
if(isEncryption)
{
strBuffer.writeUTFBytes(str);
cipherMgr.encodeString(strBuffer,outStr,strBuffer.length);
outStr2 = new ByteArray();
for(i = 0; i < outStr.length; i++)
{
if(outStr[i] == "0")
{
break;
}
outStr2[i] = outStr[i];
}
if(isHex)
{
outPut = this.normalToHexString(outStr2);
}
else
{
outPut = outStr2.toString();
}
}
else
{
if(isHex)
{
str = this.hexToNormalString(str);
}
strBuffer.writeUTFBytes(str);
cipherMgr.decodeString(strBuffer,outStr,strBuffer.length);
outStr3 = new ByteArray();
for(i = 0; i < outStr.length; i++)
{
if(outStr[i] == "0")
{
break;
}
outStr3[i] = outStr[i];
}
outPut = outStr3.toString();
}
return outPut;
}

此外还有一些函数:

encodeString,decodeString
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function encodeString(src:ByteArray, dest:ByteArray, dataSize:int) : void
{
var outPutBuffer:ByteArray = new ByteArray();
outPutBuffer.length = dataSize;
this.encodeBuffer(src,outPutBuffer,dataSize);
FVBase64.getInstance().base64Encode(outPutBuffer,dest,dataSize);
}

public function decodeString(src:ByteArray, dest:ByteArray, dataSize:int) : void
{
var nOutPutBytes:int = nOutPutBytes = dataSize / 4 * 3;
if(src[dataSize - 1] == 61)
{
nOutPutBytes -= src[dataSize - 2] == 61 ? 2 : 1;
}
var outPutBuffer:ByteArray = new ByteArray();
outPutBuffer.length = nOutPutBytes + 5;
FVBase64.getInstance().base64Decode(src,outPutBuffer,dataSize);
this.decodeBuffer(outPutBuffer,dest,nOutPutBytes);
dest[nOutPutBytes] = 0;
}

初始化密钥的函数:

initKey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function initKey(inputStr:ByteArray, nKeyBytes:int) : void
{
for(var i:int = 0; i < this.SAFER_BLOCK_LEN; i++)
{
this._lastBlock[i] = 255;
}
var MDBuf:ByteArray = new ByteArray();
MDBuf.length = this.MAX_HASH_RESULT_LEN;
FVRipeMD.getInstance().ripeMD256Hash(inputStr,nKeyBytes,MDBuf);
var point:int = 0;
var safer:FVSafer = FVSafer.getInstance();
safer.saferExpandUserkey(MDBuf,MDBuf,this.SAFER_BLOCK_LEN,10,1,this._encryptKey);
safer.saferEncryptBlock(this._lastBlock,this._encryptKey,this._lastBlock,point);
}

saferEncryptBlock 函数前面已经实现了,继续找到的 saferExpandUserkey 函数:

saferExpandUserkey
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
59
60
61
62
public function saferExpandUserkey(userkey_1:ByteArray, userkey_2:ByteArray, key2Start:int, nof_rounds:uint, strengthened:int, key:ByteArray) : void
{
var i:uint = 0;
var j:uint = 0;
var keyPoint:int = 0;
var ka:ByteArray = new ByteArray();
var kb:ByteArray = new ByteArray();
ka.length = this.SAFER_BLOCK_LEN + 1;
kb.length = this.SAFER_BLOCK_LEN + 1;
if(this.SAFER_MAX_NOF_ROUNDS < nof_rounds)
{
nof_rounds = uint(this.SAFER_MAX_NOF_ROUNDS);
}
key[0] = nof_rounds;
keyPoint = 1;
ka[this.SAFER_BLOCK_LEN] = 0;
kb[this.SAFER_BLOCK_LEN] = 0;
for(j = 0; j < this.SAFER_BLOCK_LEN; j++)
{
ka[this.SAFER_BLOCK_LEN] ^= ka[j] = this.ROL(userkey_1[j],5);
kb[this.SAFER_BLOCK_LEN] ^= kb[j] = key[keyPoint + j] = userkey_2[j + key2Start];
}
keyPoint = j + 1;
for(i = 1; i <= nof_rounds; i++)
{
for(j = 0; j < this.SAFER_BLOCK_LEN + 1; j++)
{
ka[j] = this.ROL(ka[j],6);
kb[j] = this.ROL(kb[j],6);
}
for(j = 0; j < this.SAFER_BLOCK_LEN; j++)
{
if(strengthened)
{
key[keyPoint] = ka[(j + 2 * i - 1) % (this.SAFER_BLOCK_LEN + 1)] + this._exp_tab[this._exp_tab[18 * i + j + 1]] & 255;
keyPoint++;
}
else
{
key[keyPoint] = ka[j] + this._exp_tab[this._exp_tab[18 * i + j + 1]] & 255;
keyPoint++;
}
}
for(j = 0; j < this.SAFER_BLOCK_LEN; j++)
{
if(strengthened)
{
key[keyPoint] = kb[(j + 2 * i) % (this.SAFER_BLOCK_LEN + 1)] + this._exp_tab[this._exp_tab[18 * i + j + 10]] & 255;
keyPoint++;
}
else
{
key[keyPoint] = kb[j] + this._exp_tab[this._exp_tab[18 * i + j + 10]] & 255;
keyPoint++;
}
}
}
for(j = 0; j < this.SAFER_BLOCK_LEN + 1; j++)
{
ka[j] = kb[j] = 0;
}
}

至此,所有需要的内容就绪,现在需要做的就是实现这个解密函数,由于 Flash 使用的是 ActionScript,如今已经几乎没有使用,FLEX 也快要被淘汰,很难
找到一个合适的环境进行编译,在现代科技(AI)的帮助下,可以较为轻松的使用与其类似的语言进行改写,出于个人技术栈等原因,我选择了 Go 语言。

Go 解密实现

本着简单高效考虑,忽略了很多语法规范,RipeMD256Hash 直接使用了第三方库。

程序如下:

Go 语言程序实现
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
package main

import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/binary"
"fmt"
"github.com/C0MM4ND/go-ripemd"
"io"
"os"
"strconv"
"strings"
)

var lastBlock []byte
var encryptKey []byte
var expTab []byte = make([]byte, 256)
var logTab []byte = make([]byte, 256)

const constEncryptKeyCDViewer = "0b8b6a4650b148a1975331bc2da63f93"
const TAB_LEN = 256
const SAFER_BLOCK_LEN = 8
const SAFER_KEY_LEN = 217
const SAFER_MAX_NOF_ROUNDS = 13

var inputfile, outputfile, encryptionKey string

func main() {
args := os.Args[1:]
if len(args) == 2 {
inputfile = args[0]
//outputfile = args[1]
encryptionKey = args[1]
} else {
fmt.Println("错误输入")
os.Exit(-1)
}
f, _ := os.Open(inputfile) // etwx196312DL_1.swf
defer func(f *os.File) {
_ = f.Close()
}(f)
buf := make([]byte, 8*1024*1024) // max cache
ri, _ := f.Read(buf)
r := PageLoad(buf[:ri])
//fmt.Println(r[:21])
//fmt.Println(string(r[:3]))

outputfile = strings.Replace(inputfile, ".swf", ".jpg", 1)
_ = os.Remove(outputfile)
f2, _ := os.OpenFile(outputfile, os.O_CREATE, 0x644)
defer f2.Close()
errf, _ := os.Open("error.txt")
defer errf.Close()
if bytes.Equal(r[:3], []byte("CWS")) {
// zlib 压缩 swf
rr, err := zlib.NewReader(bytes.NewReader(r[8:]))
if err != nil {
fmt.Println(err)
return
}
defer rr.Close()
br := bytes.NewBuffer([]byte{})
br.WriteByte(70)
br.Write(r[1:8])
io.Copy(br, rr)
a := TryToExportJpeg2(br.Bytes())
if a == nil {
f2.Write(br.Bytes())
errf.WriteString(outputfile + "\n")
} else {
f2.Write(a)
}
} else if bytes.Equal(r[:3], []byte("FWS")) {
// 无压缩 swf
a := TryToExportJpeg2(r)
if a == nil {
f2.Write(r)
errf.WriteString(outputfile + "\n")
} else {
f2.Write(a)
}
} else {
// 其他格式
errf.WriteString(outputfile + "\n")
f2.Write(r)
}
}

func TryToExportJpeg2(r []byte) []byte {
rd := bytes.NewReader(r[21:])
for {
var tagType uint16 = 0
var tglen uint32 = 0
err := binary.Read(rd, binary.LittleEndian, &tagType)
if err == io.EOF {
break
}
if err != nil {
fmt.Println(err)
return nil
}
tgtype := tagType >> 6
tglen = uint32(tagType & 0x3f)
//fmt.Println(tgtype)
//fmt.Println(tglen)
if tglen == 0x3f {
binary.Read(rd, binary.LittleEndian, &tglen)
}
if tgtype == 21 {
// jpeg2
rbuf := make([]byte, tglen-2-4+2)
rd.Read(rbuf)
cutindex := bytes.Index(rbuf, []byte{0xff, 0xd9, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10})
if cutindex > 0 {
rbuf = append(rbuf[:cutindex], rbuf[cutindex+4:]...)
}
return rbuf[2:]
}
rd.Seek(int64(tglen), io.SeekCurrent)
}
return nil
}

// PageLoad 加载个页面
func PageLoad(src []byte) []byte {
//encryptionKey := "3353665A766A512F36574F4D4E59334B31456F484E4267437A5A4678326446674E31774F4D376C4270444D3D"
encryptionKey = encryptString(encryptionKey, constEncryptKeyCDViewer)
encryptKey = []byte(encryptionKey)
//fmt.Println("encryptKey,", string(encryptKey))
objectByte := EncryptStream(src, encryptionKey)
return objectByte
}

// EncryptStream 流解密(放弃 8192 后的内容)
func EncryptStream(src []byte, key string) []byte {
var outArray []byte = nil
if len(src) <= 8192 {
return encryptStream(src, key, len(src))
}
if strings.HasSuffix(inputfile, ".xml") {
outArray = encryptStream(src, key, len(src))
out := bytes.NewBuffer(outArray)
return out.Bytes()
}
outArray = encryptStream(src, key, 8192)
out := bytes.NewBuffer(outArray)
if 8192 < len(src) {
out.Write(src[8192:])
}
return out.Bytes()
}

// encryptStream 流(bytes)解密,限制了大小
func encryptStream(src []byte, encKey string, bufSize int) []byte {
if len(src) == 0 {
return src
}
szKey := bytes.NewBuffer([]byte{})
szKey.WriteString(encKey)

initKey(szKey.Bytes(), len(encKey))
// >safer init
exp := 1
for i := 0; i < TAB_LEN; i++ {
expTab[i] = byte(exp & 255)
logTab[expTab[i]] = byte(i)
exp = exp * 45 % 257
}
// <safer init
var res = make([]byte, bufSize)
res = decodeBuffer(src, bufSize)
return res
}

// RipeMD256Hash RipeMD256,抄个作业
func RipeMD256Hash(input []byte) []byte {
hash := ripemd.New256()
hash.Write(input)
sum := hash.Sum(nil)
return sum
}

// ROL 循环移位
func ROL(x uint, n uint) byte {
return byte((x << n) | (x >> (8 - n)))
}

// saferExpandUserkey 我是谁?我在哪?这有什么意义?
func saferExpandUserkey(userkey1 []byte, userkey2 []byte, key2Start int, nofRounds int, strengthened int) []byte {
i := 0
j := 0
keyPoint := 0
ka := make([]byte, SAFER_BLOCK_LEN+1)
kb := make([]byte, SAFER_BLOCK_LEN+1)
key := make([]byte, SAFER_KEY_LEN)

key[0] = byte(nofRounds)
keyPoint = 1
ka[SAFER_BLOCK_LEN] = 0
kb[SAFER_BLOCK_LEN] = 0
for j = 0; j < SAFER_BLOCK_LEN; j++ {
ka[j] = ROL(uint(userkey1[j]), 5)
ka[SAFER_BLOCK_LEN] ^= ka[j]
key[keyPoint+j] = userkey2[j+key2Start]
kb[j] = key[keyPoint+j]
kb[SAFER_BLOCK_LEN] ^= kb[j]
}
keyPoint = j + 1
for i = 1; i <= nofRounds; i++ {
for j := 0; j < SAFER_BLOCK_LEN+1; j++ {
ka[j] = ROL(uint(ka[j]), 6)
kb[j] = ROL(uint(kb[j]), 6)
}
for j = 0; j < SAFER_BLOCK_LEN; j++ {
if strengthened != 0 {
key[keyPoint] = ka[(j+2*i-1)%(SAFER_BLOCK_LEN+1)] + expTab[expTab[18*i+j+1]]&255
keyPoint++
} else {
key[keyPoint] = ka[j] + expTab[expTab[18*i+j+1]]&255
keyPoint++
}
}
for j = 0; j < SAFER_BLOCK_LEN; j++ {
if strengthened != 0 {
key[keyPoint] = kb[(j+2*i)%(SAFER_BLOCK_LEN+1)] + expTab[expTab[18*i+j+10]]&255
keyPoint++
} else {
key[keyPoint] = kb[j] + expTab[expTab[18*i+j+10]]&255
keyPoint++
}
}
}
for j = 0; j < SAFER_BLOCK_LEN+1; j++ {
ka[j] = 0
kb[j] = 0
}
return key
}

// saferEncryptBlock 块加密,为啥我做解密需要重现加密部分啊???
func saferEncryptBlock(blockIn []byte, key []byte) []byte {
blockOut := make([]byte, len(blockIn))
keyPoint := 0
a := blockIn[0]
b := blockIn[1]
c := blockIn[2]
d := blockIn[3]
e := blockIn[4]
f := blockIn[5]
g := blockIn[6]
h := blockIn[7]
round := uint(key[0])
if uint(SAFER_MAX_NOF_ROUNDS) < round {
round = uint(SAFER_MAX_NOF_ROUNDS)
}
for i := round; i > 0; i-- {
keyPoint++
a ^= key[keyPoint]
keyPoint++
b += key[keyPoint]
keyPoint++
c += key[keyPoint]
keyPoint++
d ^= key[keyPoint]
keyPoint++
e ^= key[keyPoint]
keyPoint++
f += key[keyPoint]
keyPoint++
g += key[keyPoint]
keyPoint++
h ^= key[keyPoint]
keyPoint++
a = expTab[a&255] + key[keyPoint]
keyPoint++
b = logTab[b&255] ^ key[keyPoint]
keyPoint++
c = logTab[c&255] ^ key[keyPoint]
keyPoint++
d = expTab[d&255] + key[keyPoint]
keyPoint++
e = expTab[e&255] + key[keyPoint]
keyPoint++
f = logTab[f&255] ^ key[keyPoint]
keyPoint++
g = logTab[g&255] ^ key[keyPoint]
keyPoint++
h = expTab[h&255] + key[keyPoint]
b += a
a += b
d += c
c += d
f += e
e += f
h += g
g += h
c += a
a += c
g += e
e += g
d += b
b += d
h += f
f += h
e += a
a += e
f += b
b += f
g += c
c += g
h += d
d += h
t := b
b = e
e = c
c = t
t = d
d = f
f = g
g = t
}
keyPoint++
a ^= key[keyPoint]
keyPoint++
b += key[keyPoint]
keyPoint++
c += key[keyPoint]
keyPoint++
d ^= key[keyPoint]
keyPoint++
e ^= key[keyPoint]
keyPoint++
f += key[keyPoint]
keyPoint++
g += key[keyPoint]
keyPoint++
h ^= key[keyPoint]
blockOut[0] = a & 255
blockOut[1] = b & 255
blockOut[2] = c & 255
blockOut[3] = d & 255
blockOut[4] = e & 255
blockOut[5] = f & 255
blockOut[6] = g & 255
blockOut[7] = h & 255
return blockOut
}

// decodeBuffer Buffer 分块解码(叫 bytes 解密更贴切)
func decodeBuffer(src []byte, dataSize int) []byte {
i := 0
arrayPoint := 0
cipherTextBlock := make([]byte, SAFER_BLOCK_LEN)
dest := make([]byte, dataSize)
for dataSize >= SAFER_BLOCK_LEN {
for i = 0; i < SAFER_BLOCK_LEN; i++ {
cipherTextBlock[i] = src[arrayPoint+i]
}
decrypted := saferDecryptBlock(cipherTextBlock, encryptKey)
copy(dest[arrayPoint:], decrypted)
for i = 0; i < SAFER_BLOCK_LEN; i++ {
dest[arrayPoint+i] ^= lastBlock[i]
}
for i = 0; i < SAFER_BLOCK_LEN; i++ {
lastBlock[i] ^= cipherTextBlock[i]
}
arrayPoint += SAFER_BLOCK_LEN
dataSize -= SAFER_BLOCK_LEN
}
if dataSize > 0 {
TempBlock := make([]byte, SAFER_BLOCK_LEN)
TempBlock = saferEncryptBlock([]byte{255, 255, 255, 255, 255, 255, 255, 255}, encryptKey)
for i = 0; i < dataSize; i++ {
dest[arrayPoint+i] = src[arrayPoint+i] ^ TempBlock[i]
}
for i = 0; i < SAFER_BLOCK_LEN; i++ {
lastBlock[i] ^= TempBlock[i]
}
}
return dest
}

// saferDecryptBlock 块解密
func saferDecryptBlock(blockIn []byte, key []byte) []byte {
blockOut := make([]byte, len(blockIn))
var keyPoint uint
keyPoint = 0
a := blockIn[0]
b := blockIn[1]
c := blockIn[2]
d := blockIn[3]
e := blockIn[4]
f := blockIn[5]
g := blockIn[6]
h := blockIn[7]
round := uint(key[0])
if uint(SAFER_MAX_NOF_ROUNDS) < round {
round = uint(SAFER_MAX_NOF_ROUNDS)
}

keyPoint += uint(SAFER_BLOCK_LEN) * (1 + 2*round)
h ^= key[keyPoint]
keyPoint--
g -= key[keyPoint]
keyPoint--
f -= key[keyPoint]
keyPoint--
e ^= key[keyPoint]
keyPoint--
d ^= key[keyPoint]
keyPoint--
c -= key[keyPoint]
keyPoint--
b -= key[keyPoint]
keyPoint--
a ^= key[keyPoint]

for i := round; i > 0; i-- {
t := e
e = b
b = c
c = t
t = f
f = d
d = g
g = t
a -= e
e -= a
b -= f
f -= b
c -= g
g -= c
d -= h
h -= d
a -= c
c -= a
e -= g
g -= e
b -= d
d -= b
f -= h
h -= f
a -= b
b -= a
c -= d
d -= c
e -= f
f -= e
g -= h
h -= g
keyPoint--
h -= key[keyPoint]
keyPoint--
g ^= key[keyPoint]
keyPoint--
f ^= key[keyPoint]
keyPoint--
e -= key[keyPoint]
keyPoint--
d -= key[keyPoint]
keyPoint--
c ^= key[keyPoint]
keyPoint--
b ^= key[keyPoint]
keyPoint--
a -= key[keyPoint]
keyPoint--
h = logTab[h&255] ^ key[keyPoint]
keyPoint--
g = expTab[g&255] - key[keyPoint]
keyPoint--
f = expTab[f&255] - key[keyPoint]
keyPoint--
e = logTab[e&255] ^ key[keyPoint]
keyPoint--
d = logTab[d&255] ^ key[keyPoint]
keyPoint--
c = expTab[c&255] - key[keyPoint]
keyPoint--
b = expTab[b&255] - key[keyPoint]
keyPoint--
a = logTab[a&255] ^ key[keyPoint]
}
blockOut[0] = a & 255
blockOut[1] = b & 255
blockOut[2] = c & 255
blockOut[3] = d & 255
blockOut[4] = e & 255
blockOut[5] = f & 255
blockOut[6] = g & 255
blockOut[7] = h & 255
return blockOut
}

// encryptString 文本解密,专门用来处理 key
func encryptString(str string, encryptKey string) string {
outStr := make([]byte, 8192)
zsKey := bytes.NewBuffer([]byte{})
zsKey.WriteString(encryptKey)
initKey(zsKey.Bytes(), len(encryptKey))
buf2 := []byte{}
strBuffer := bytes.NewBuffer(buf2)

str = string(hexStringToNormal(str))
strBuffer.WriteString(str)
outStr = decodeString(strBuffer.Bytes())
//fmt.Println("strBuff", strBuffer.Bytes())
//fmt.Println("outStr", outStr)
for i := 0; i < len(outStr); i++ {
if outStr[i] == 0 {
outStr = outStr[:i]
}
}
outPut := string(outStr)
return outPut
}

// hexStringToNormal bytes.fromhex() 的 Go 实现
func hexStringToNormal(hexStr string) []byte {
var result []byte
for i := 0; i < len(hexStr); i += 2 {
value, err := strconv.ParseUint(hexStr[i:i+2], 16, 8)
if err != nil {
return nil
}
result = append(result, byte(value))
}
return result
}

// decodeString 字符串解码(解密)
func decodeString(src []byte) []byte {
dataSize := len(src)
nOutPutBytes := dataSize / 4 * 3
if src[dataSize-1] == 61 {
if src[dataSize-2] == 61 {
nOutPutBytes -= 2
} else {
nOutPutBytes -= 1
}
}
OutPutBuffer := make([]byte, nOutPutBytes+5)
base64.StdEncoding.Decode(OutPutBuffer, src)
dest := decodeBuffer(OutPutBuffer, nOutPutBytes)
return dest
}

// initKey swf FVCipherMgr 的初始化函数
func initKey(inputStr []byte, nKeyBytes int) {
lastBlock = make([]byte, SAFER_BLOCK_LEN)
for i := 0; i < SAFER_BLOCK_LEN; i++ {
lastBlock[i] = 255
}
MDBuf := RipeMD256Hash(inputStr)

exp := 1
for i := 0; i < TAB_LEN; i++ {
expTab[i] = byte(exp & 255)
logTab[expTab[i]] = byte(i)
exp = exp * 45 % 257
}
encryptKey = saferExpandUserkey(MDBuf, MDBuf, SAFER_BLOCK_LEN, 10, 1)

lastBlock = saferEncryptBlock(lastBlock, encryptKey)
}

程序根据 ActionScript 脚本,更换了变量类型和部分方法,对解密过程进行了复现,使用时候需要提供 swf 文件路径和 lisence 文件中的 encryption 密码。

encryptionKey

程序还根据 swf 文件的格式,对 swf 文件进行了解压缩,提取了其中的图片,如果是 xml 文件,直接解密输出。

虽然程序已经实现了,但分析还是补上,对 swf 文件进行解密后还是 swf 文件,通过 JPEXS 打开可以找到里面的内容。

解密后的 swf

如果文件内只有一张图片,那可以按上面程序给的方法,直接根据 swf 文件格式,提取首个附件,但如果是像例子这样由图片和文字拼凑而成,就变得非常麻烦。

无法直接导出图片的情况

无法直接导出图片的情况2

目前暂时没有再进行下一步处理,如果你有兴趣或者有更好的方法,请务必与我联系(tdh@tdh6.top,避免爬虫,不直接放通讯软件ID了),
毕竟都看到这里了,交个朋友也不赖吧?

最后的吐槽:

  1. Flash 时代已经过去了,但那时候的记忆永远留在心中,包括众多小游戏和一些不太小的游戏(比如 EBF),借由 Flash 被大家所知晓,喜爱。
  2. TMD 你加密一次只送 8 字节是什么意思???还好只加密开头的 8K,不然解密麻烦死。
  3. 初见不识画中意,再见已是画中人。
  4. 我不是大佬,谢谢。

本文封面图来自番剧《星灵感应》,星屑テレパス(Hoshikuzu Telepath)S01E01 00:06:04

本文作者:
本文链接:https://tdh6.top/%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90/etwx/
版权声明:本站文章采用 CC BY-NC-SA 3.0 CN 协议进行许可,翻译文章遵循原文协议。
图片来源:本站部分图像来源于网络,前往查看 相关说明。