浮点数的定义 - IEEE 754 标准
数字的存储是关于“精度”与“范围”的权衡。传统的定点数(Fixed-point)虽然简单高效,但在面对极大的天文数字或极小的物理常量时,往往显得捉襟见肘。为了打破这种局限,IEEE 754 标准引入了让小数点“浮动”起来的设计方案。 但指数偏差、隐式前导位、正规数等概念不够直观,本文将通过构建一个简单的 8-bit 浮点系统,重新梳理定点到浮点的演进路径,理解藏在比特位底层的设计逻辑与权衡。 定点数 1. “定点"表示的整数 4-bit无符号整数可以用下面这种方式来表示: 整数不存在小数点,也可以说我们默认小数点固定在整数的末尾,所以整数的表示也可以看作是“定点”。 2. 定点表示的小数 使用7位二进制,前4位表示整数部分,后3位表示小数部分,可以这样表示: $$ \begin{array}{c:c:c:c|c:c:c} 2^3 & 2^2 & 2^1 & 2^0 & 2^{-1} & 2^{-2} & 2^{-3} \end{array} $$ 小数点固定在第4、第5个比特之间,分别计算每个位置的值并求和即可得到这段二进制表示的小数值: 可以表示的范围是 $[0.0, 15.875]$,表示的数之间的最小间隔是 $0.125$,并且间隔是均匀的。 定点数的特点: 定点数和整数的表示很接近,计算也像整数一样简单高效; 几乎可以重用为整数运算设计的硬件; 表示的数值间隔均匀,但是整体范围较小。 浮点数 为了方便理解,我们还是从最简单的无符号整数开始,逐步定义出浮点数。 4-bit可以表示出集合 $A=\{i|0\leq i \leq 15, i\in \Z\}$,如果把它们看作 $2$ 的指数,就可以表示出数值更大的集合 $B=\{2^i|i\in A\}$,但是数据变得非常稀疏,并且是距离0越远越稀疏。 前 $4$ 位二进制可以称为指数(Exponent),简写为 $E$;为了表示小数,我们再加入 $3$ 位尾数(Mantissa),简写为 $M$,尾数部分和之前的计算方式相同: $$ \begin{array}{c:c:c} 2^{-1} & 2^{-2} & 2^{-3} \end{array} $$ ...
Install OpenSSL and GmSSL on Win32
由于参与了一个在 PLC 和上位机之间建立加密信道的项目,需要在PLC的Win7-32bit系统中安装OpenSSL和GmSSL,因为PLC硬盘太小,所以我实际上是在虚拟机里面进行的编译。 OpenSSL 一开始是打算编译一下的,下载了OpenSSL的源码,OpenSSL的Configure是依赖Perl的。然后下载安装了Strawberry Perl for win32,然后发现会出现各种报错,一番搜索了解到需要安装ActivePerl: Install ActivePerl and remove Stawberry Perl as it is not compatible with openssl. 但是ActivePerl的官网只提供64bit的版本,安装32bit版本的ActivePerl需要支付$912/yr: The site states: “if you need 32-bit or other older/legacy versions, they are available through our new ActiveState Platform by subscribing to at least Team Tier. See pricing here.” 这就很难搞,只能去下载安装现成的二进制文件:http://slproweb.com/products/Win32OpenSSL.html GmSSL GmSSL官方的编译与安装教程虽然字数不多但是很有帮助,编译过程比较顺利。 虽然也提及要用ActivePerl,但是我在对Configure进行了一点简单修改之后,用Strawberry Perl也可以成功运行perl Configure VC-WIN32。 直接运行perl Configure VC-WIN32会遇到报错:"glob" is not exported by the File::Glob module。 分别在Configure和test/build.info两个文件中,把: use if $^O ne "VMS", 'File::Glob' => qw/glob/; 修改为: ...
使用树莓派4B作为一台服务器
树莓派是个一直听说,但是一直没玩过的东西,所以在今年年初搞了一块折腾了一段时间。在上面起了一些服务,确实很好玩。在上面搭建了私有网盘、Minecraft、SageMath 还有几个密码学题目。这篇博客记录了我折腾树莓派的经历~ 解决公网IP问题 我想在公网连接到家里的树莓派,访问到上面的服务,所以需要有公网IP或者使用 frp 内网穿透。当然如果不需要让树莓派暴露在公网下,就不需要搞公网 IP 了。 静态公网IP 如果有角度可以搞到企业专线,那还是很香的,不仅拥有运营商分配的静态公网 IP,还有上下行相等的大带宽,当然费用也很高,也需要注册的企业资质。所以我是搞不到静态的公网 IP。 动态公网IP 没有静态的,可以退而求其次搞动态的,跟运营商交涉一下是可以得到动态的公网 IP 的,虽然是上下行不对等的家庭带宽,但是作为服务器来说绰绰有余,已经远超很多便宜的云服务器了。 第一次我直接跟人工客服打电话申请要一个静态公网IP被拒绝了;然后第二天我又一次联系了人工客服,询问能不能申请动态的公网IP(可以说装摄像头要用到,不能说在家里搭建服务器),结果很快就回电通知我已经给了动态的公网IP,还算顺利~ Dynamic DNS 后面使用发现,我们这边的动态的公网IP大约2~3天变动一次,这就可以通过 Dynamic DNS(DDNS) 将动态的IP解析到固定的域名,就能通过固定的域名访问到服务器了。 DDNS需要服务端和本地各运行一个服务:本地的树莓派或路由器上运行一个服务,每隔一段时间(几分钟)就获取一下自己的公网IP,判断一下有没有发生改变,如果发生改变就将新的 IP 发送给服务端,服务端得到新的 IP,就重新将域名解析到新的IP,所以是动态DNS。 DDNS的服务端只提供域名的动态解析服务,所以服务端的带宽对访问树莓派的速度没有影响(但是可能会影响到延时?实际使用没有明显感觉到) DDNS服务: 直接使用一些网站(oray.com/3322.org/Dyndns.com/No-ip.com)免费提供的DDNS服务,但是他们给的域名都是比较杂乱的三级域名,需要再把自己租的阿里云/腾讯云域名CNAME解析到这个三级域名。花生壳(oray.com)可以免费使用;而No-ip.com免费版需要每30天登录上去手动续约。 使用自己的vps运行脚本为树莓派提供DDNS服务,由于需要通过脚本进行域名解析,所以需要在vps上安装对应的SDK,然后开发对应的脚本,可以直接将自己的阿里云/腾讯云域名解析到家里的公网IP。 我现在是选择使用花生壳(oray.com)的DDNS服务,只需要注册即可得到一个三级域名,通过控制台->域名->壳域名来查看。我的路由器是小米 4A,可以在后台设置DDNS,选择“花生壳”并输入账号密码域名和检查 IP 的时间间隔即可。路由器不支持的话就需要在树莓派上运行一个脚本来发送新的IP。 也有很多树莓派玩家通过每次变动IP时,让树莓派将新IP发送给自己的邮箱,使自己总是可以知道最新的IP地址。 frp内网穿透 如果也申请不到动态 IP,那就只能用 frp 做内网穿透了,需要借助一台有公网 IP 的 vps,流量也都需要经过这台 vps,所以享受不到家庭宽带的低价大带宽了。 测试上下行带宽 可以在 speedtest.cn 测试下行带宽和上行带宽(最好直接连网线测)。 从公网访问树莓派下载文件需要的是家里的上行带宽,而家庭宽带的上行都是很低的,只能去升级下行带宽,上行才能对应的提升一点。。。。所以如果感觉带宽不太够还是要去升级一下宽带的。 我家的联通宽带就不太行,一开始是100M下行,20M上行,实测大约下行90~100M,上行20~40M。于是换了500M下行,50M上行的宽带,实测大约下行450~550M,上行60~70M。 组装树莓派和安装Ubuntu 我的是树莓派 4B。启动树莓派还需要有 5V 3A 的电源和一张 TF 卡。 各版本树莓派的对照表:https://shumeipai.nxez.com/wp-content/uploads/2017/03/raspberrypi-version-compare-4b.png 然后就是愉快的组装环节,树莓派 PCB 边缘是有毛糙的,最好用砂纸磨光滑再装到壳子里。 写入Ubuntu镜像 树莓派支持安装很多种系统,我选择的是 Ubuntu Server 20.04.2 LTS 64-bit,可以在这里下载:https://ubuntu.com/download/raspberry-pi,先把镜像下载到本地,把 TF 卡插到读卡器里,再把读卡器插在这台电脑上。 ...
湖湘杯 2021 Crypto
hxb 2021 crypto signin $n1/n2$ 的连分数展开是对 $q1/q2$ 的一个逼近,所以枚举连分数中的每一项,就可以得到 $q1, q2$ 了,分解之后正常进行 RSA 解密得到 flag。 from Crypto.Util.number import GCD, inverse, long_to_bytes, isPrime pk = (1150398070565459492080597718626032792435556703413923483458704675295997646493249759818468321328556510074044954676615760446708253531839417036997811506222349194302791943489195718713797322878586379546657275419261647635859989280700191441312691274285176619391539387875252135478424580680264554294179123254566796890998243909286508189826458854346825493157697201495100628216832191035903848391447704849808577310612723700318670466035077202673373956324725108350230357879374234418393233, 1242678737076048096780023147702514112272319497423818488193557934695583793070332178723043194823444815153743889740338870676093799728875725651036060313223096288606947708155579060628807516053981975820338028456770109640111153719903207363617099371353910243497871090334898522942934052035102902892149792570965804205461900841595290667647854346905445201396273291648968142608158533514391348407631818144116768794595226974831093526512117505486679153727123796834305088741279455621586989) c1, c2 = (361624030197288323178211941746074961985876772079713896964822566468795093475887773853629454653096485450671233584616088768705417987527877166166213574572987732852155320225332020636386698169212072312758052524652761304795529199864805108000796457423822443871436659548626629448170698048984709740274043050729249408577243328282313593461300703078854044587993248807613713896590402657788194264718603549894361488507629356532718775278399264279359256975688280723740017979438505001819438, 33322989148902718763644384246610630825314206644879155585369541624158380990667828419255828083639294898100922608833810585530801931417726134558845725168047585271855248605561256531342703212030641555260907310067120102069499927711242804407691706542428236208695153618955781372741765233319988193384708525251620506966304554054884590718068210659709406626033891748214407992041364462525367373648910810036622684929049996166651416565651803952838857960054689875755131784246099270581394) n1, n2 = pk e = 0x10001 def getRoot(x, n): high = 1 while high ** n <= x: high *= 2 low = high // 2 while low < high: mid = (low + high) // 2 if low < mid and mid**n < x: low = mid elif high > mid and mid**n > x: high = mid else: return mid return mid + 1 # https://github.com/pablocelayes/rsa-wiener-attack def rational_to_contfrac(x, y): a = x // y pquotients = [a] while a * y != x: x, y = y, x - a*y a = x // y pquotients.append(a) return pquotients def convergents_from_contfrac(frac): convs = [] for i in range(len(frac)): convs.append(contfrac_to_rational(frac[0:i])) return convs def contfrac_to_rational(frac): if len(frac) == 0: return (0, 1) num = frac[-1] denom = 1 for _ in range(-2, -len(frac) - 1, -1): num, denom = frac[_] * num + denom, num return (num, denom) frac = rational_to_contfrac(n1, n2) convergents = convergents_from_contfrac(frac) p1, p2 = None, None for (P1, P2) in convergents: gcd1, gcd2 = GCD(P1, n1), GCD(P2, n2) p1 = gcd1 if (gcd1.bit_length() <= 128 and isPrime(gcd1)) else p1 p2 = gcd2 if (gcd2.bit_length() <= 128 and isPrime(gcd2)) else p2 print(f"[+]p1: {p1}") print(f"[+]p2: {p2}") q1, q2 = getRoot(n1 // p1, 4), getRoot(n2 // p2, 4) phi1 = (p1 - 1) * (q1 - 1) * q1**3 phi2 = (p2 - 1) * (q2 - 1) * q2**3 m1 = pow(c1, inverse(e, phi1), n1) m2 = pow(c2, inverse(e, phi2), n2) flag = (long_to_bytes(m1) + long_to_bytes(m2)).decode() print(f"[~]flag: {flag}") ''' [+]p1: 181856133933383097933223133658050179553 [+]p2: 196443958511498599913330690975430421229 [~]flag: flag{8ef333ac-21a7-11ec-80f1-00155d83f114} ''' fastOT Python 的random的内部是MT19937 伪随机数生成器,虽然生成的随机数周期很大,但是内部状态(state)很少,仅仅有 624 个,每个值都是 32bit 的。 ...
ByteCTF 2021 Crypto
easyxor shift函数是个常见的移位异或操作,convert是对一个数字使用不同的 key 和 mask 进行 4 次移位异或,这个函数在已知 key 的情况下是可逆的。 encrypt函数是对明文块进行两种模式(CBC和OFB)的块加密,块长度为 8,对于每一块的加密使用的就是上面的convert函数。 首先通过密文的长度可以得知一共被分成了 6 块;前 3 块明文使用 OFB 模式,后三块明文使用 CBC 模式;keys 是一个长度为 4 的列表,列表中每个值的范围是(-32, 32),$64^4$ 爆破也是可以接受的。 读完题目代码之后可以想到其实我们已经知道第一块明文了,就是 flag 的格式ByteCTF{,而OFB模式实际上是加密的key,最终结果和明文块异或,所以第一个明文块异或第一个密文块就可以知道第一个 key 加密的结果,也就是cur_c = convert(last, k)的cur_c,这样就可以得到第二块的 last。 现在对于第二块,已知 IV(last),未知 keys,已知明文是可显示字符,所以可以爆破 keys 了,把能解出可显示字符明文的 keys 都保留出来,发现有 4836 个 keys 是满足的,那么我们还要借助第三块再筛一次,最终只得到一组 keys。 from itertools import product from tqdm import tqdm from Crypto.Util.number import bytes_to_long, long_to_bytes def check(s): return min([((i < 129) and (i > 31)) for i in s]) c = "89b8aca257ee2748f030e7f6599cbe0cbb5db25db6d3990d3b752eda9689e30fa2b03ee748e0da3c989da2bba657b912" c_list = [int(c[i*16:i*16+16], 16) for i in range(len(c) // 16)] known_m = bytes_to_long(b'ByteCTF{') range64 = list(range(-32, 33)) cur_c = known_m ^ c_list[0] print(cur_c) k_cnt = 0 for a,b,c,d in tqdm(product(range64, range64, range64, range64)): last = cur_c k = [a, b, c, d] try_cur_c = convert(last, k) m1 = long_to_bytes(try_cur_c ^ c_list[1]) if check(m1): # 只筛选这第一轮的话,4836个k是满足条件的,所以得筛第二轮 last = try_cur_c try_cur_c = convert(last, k) m2 = long_to_bytes(try_cur_c ^ c_list[2]) if check(m2): k_cnt += 1 try: print(m1.decode() + m2.decode(), k) except: print("error") print(k_cnt) # keys = [-12, 26, -3, -31] # ByteCTF{5831a241s-f30980 现在已经得到了keys和前三块的明文,可以接着解后三块明文了。 ...
Install OpenWrt for Redmi Router AC2100
Get shell 小米路由器后台管理页面是不给我们上传并且刷入新固件的,所以要先拿到 shell,ssh 连上路由器就可以往里面刷入新固件了。 先正常启动路由器,并进入路由器后台管理页面,这时可以在浏览器的地址栏看到自己路由器的内网 IP,和自己的 stok,例如我的开头是: http://10.161.145.162/cgi-bin/luci/;stok=e9974e290dd74c4683328c5a5876308b/... 我的路由器 IP 是10.161.145.162,stok的值为e9974e290dd74c4683328c5a5876308b,现在把下面的链接替换成自己的,然后 Enter,浏览器返回0说明这一步可以了。 http://「这里替换IP」/cgi-bin/luci/;stok=「stok的值」/api/misystem/set_config_iotdev?bssid=Xiaomi&user_id=longdike&ssid=-h%3B%20nvram%20set%20ssh_en%3D1%3B%20nvram%20commit%3B%20sed%20-i%20's%2Fchannel%3D.*%2Fchannel%3D%5C%22debug%5C%22%2Fg'%20%2Fetc%2Finit.d%2Fdropbear%3B%20%2Fetc%2Finit.d%2Fdropbear%20start%3B 然后下面这个链接也是一样的操作,同样应该返回0: http://「这里替换IP」/cgi-bin/luci/;stok=「stok的值」/api/misystem/set_config_iotdev?bssid=Xiaomi&user_id=longdike&ssid=-h%3B%20echo%20-e%20'12345678%5Cn12345678'%20%7C%20passwd%20root%3B 如果这两个链接都成功返回了0,那么就可以使用习惯的工具去 ssh 连接路由器了: ssh root@「这里替换IP」 # passwd 12345678 不出意外,这边是可以连接到路由器的 shell 的,然后可以看到字符画显示的are you ok。 上传并刷入固件 前面拿到了 shell,现在可以上传固件了,首先下载openwrt-RM2100-kernel1.bin和openwrt-RM2100-rootfs0.bin,然后 cd 进入这两个文件所在的目录,用 SCP 传到路由器上。 # 把openwrt的固件通过SCP上传到路由器的/tmp目录下 scp openwrt-RM2100-kernel1.bin root@「这里替换IP」:/tmp scp openwrt-RM2100-rootfs0.bin root@「这里替换IP」:/tmp 然后 ssh 连接上路由器,开始刷入固件: # 进入/tmp目录下 cd /tmp mtd write openwrt-RM2100-kernel1.bin kernel1 nvram set uart_en=1 nvram set bootdelay=5 nvram set flag_try_sys1_failed=1 nvram commit mtd -r write openwrt-RM2100-rootfs0.bin rootfs0 等待写入完成,就可以重启路由器了,这个时候路由器的 Wi-Fi 名称变成了 OpenWrt,并且没有密码,可以直接连接,然后用 ssh 连接新的 IP: ...
Install SageMath for Apple Silicon M1
Install conda 官网上没有直接给出 M1 Mac 版本的 SageMath 二进制安装文件,不过给出了使用 conda 安装 Sage 的方法,参考Install from conda-forge。 所以如果自己的 Mac 上还没有安装 conda 的话,可以先安装一下,然后使用 conda 安装 SageMath。 首先下载Miniforge3-MacOSX-arm64.sh,cd 进入Miniforge3-MacOSX-arm64.sh所在的目录。 bash Miniforge3-MacOSX-arm64.sh 接着一路回车,直到确认条款: Do you accept the license terms? [yes|no] [no] >>> yes 然后编辑配置文件vim ~/.zshrc,在最下面加入如下内容: path=('/Users/「这里替换成Mac用户名」/miniforge3/bin' $path) export PATH :wq保存并退出,然后source ~/.zshrc,conda info应该就可以看到了,到这里 conda 安装完成。 在终端输入下面这些,给 conda 换到清华源,这样在使用国内网络不走代理的情况下安装一些东西就更快了: conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/ conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/ conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/msys2/ conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/bioconda/ conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/menpo/ conda config --set show_channel_urls yes 然后输入conda config --show | grep https可以看到已经更新成功的上面的链接。 ...
Official Solution to the "lowExponent" Problem in NepCTF2021
这题使用的加密算法是 Demytko,属于一种类似 RSA 的在椭圆曲线上的加密算法,这题的攻击思路也是可以完全类比 RSA Hastad 广播攻击。 加密后的结果是椭圆曲线上的点, Division Polynomials 使我们可以用仅含一个未知数的多项式来表示这个点的 $x$ 坐标: $$ \begin{aligned} \psi_{-1} &=-1 \\ \psi_{0} &=0 \\ \psi_{1} &=1 \\ \psi_{2} &=2 y \\ \psi_{3} &=3 x^{4}+6 a x^{2}+12 b x-a^{2} \\ \psi_{4} &=4 y\left(x^{6}+5 a x^{4}+20 b x^{3}-5 a^{2} x^{2}-4 a b x-8 b^{2}-a^{3}\right) \\ \psi_{2 i+1} &=\psi_{i}\left(\psi_{i+2} \psi_{i-1}^{2}-\psi_{i-2} \psi_{i+1}^{2}\right) / 2 y, i \geq 2 \\ \psi_{2 i} &=\psi_{i+2} \psi_{i}^{3}-\psi_{i+1}^{3} \psi_{i-1}, i \geq 3 \end{aligned} $$ ...
0xGame 2020 Crypto Problems
0xGame2020是第一届0xGame比赛,时间持续一个月,面向零基础的新生。题目和exp可以在我的GitHub上找到:https://github.com/Am473ur/My-CTF-Challenge/tree/main/0xGame2020 ,这里记录一下出题人角度的 wp。 Week 1 Calendar 题目给了一张图片和一串逗号隔开的坐标信息,没看出来的话不难想到去百度一下“日历加密”,这题只是做了简单的修改。 SAT1,THU1,MON3,MON2,WED3,SUN2,THU1,SUN4,FRI3,THU1,MON4,MON4,FRI4,THU3,SUN4,SUN2,TUE4,THU1,FRI1,MON3,MON2 懒得百度的话,也不难看出前三个字母代表周一到周日,紧跟的数字范围是 1~4,所以他们代表两个坐标,列举出来并用a~z替换1~26,即可得到 flag。 easyXor 做出这题只需要知道异或的逆运算还是异或,反过来跑一遍就拿到了flag。 exp: cipher = [72, 63, 38, 12, 8, 30, 30, 6, 82, 4, 84, 88, 92, 7, 79, 29, 8, 90, 85, 26, 25, 87, 80, 10, 20, 20, 9, 4, 80, 73, 31, 5, 82, 0, 1, 92, 0, 0, 94, 81, 4, 85, 27, 35] flag = "" cipher += [ord("^")] for i in range(len(cipher)-1): flag = chr(cipher[len(cipher) - i - 2] ^ cipher[len(cipher) - i - 1]) + flag cipher[len(cipher) - i - 2] = ord(flag[0]) print(flag) # 0xGame{ec15a9eb-08b7-4c39-904d-27eed888f73f} 发现有的学弟跑完脚本手动补0,exp正确的话,是可以得到完整flag的。 ...
Writeup for 强网杯 2020
强网先锋 baby_crt 考点是 CRT-RSA,找到一篇paper:Wagner’s Attack on a Secure CRT-RSA Algorithm Reconsidered 然后看到里面提到可以这样获取 $p$: $$ \large gcd(m^{c_1}-Sig^e,N)=p $$ 这个题目只有 $c_1$ 没有给出,但是很小,可以直接爆破。 from Crypto.Util.number import * from hashlib import sha1 e = 65537 n = 26318358382258215770827770763384603359524444566146134039272065206657135513496897321983920652242182112479484135343436206815722605756557098241887233837248519031879444740922789351356138322947108346833956405647578838873425658405513192437479359531790697924285889505666769580176431360506227506064132034621123828090480606055877425480739950809109048177976884825589023444901953529913585288143291544181183810227553891973915960951526154469344587083295640034876874318610991153058462811369615555470571469517472865469502025030548451296909857667669963720366290084062470583318590585472209798523021029182199921435625983186101089395997 m = 26275493320706026144196966398886196833815170413807705805287763413013100962831703774640332765503838087434904835657988276064660304427802961609185997964665440867416900711128517859267504657627160598700248689738045243142111489179673375819308779535247214660694211698799461044354352200950309392321861021920968200334344131893259850468214901266208090469265809729514249143938043521579678234754670097056281556861805568096657415974805578299196440362791907408888958917063668867208257370099324084840742435785960681801625180611324948953657666742195051492610613830629731633827861546693629268844700581558851830936504144170791124745540 sig = 20152941369122888414130075002845764046912727471716839854671280255845798928738103824595339885345405419943354215456598381228519131902698373225795339649300359363119754605698321052334731477127433796964107633109608706030111197156701607379086766944096066649323367976786383015106681896479446835419143225832320978530554399851074180762308322092339721839566642144908864530466017614731679525392259796511789624080228587080621454084957169193343724515867468178242402356741884890739873250658960438450287159439457730127074563991513030091456771906853781028159857466498315359846665211412644316716082898396009119848634426989676119219246 for c1 in range(1, 65536): p = GCD(pow(m, c1, n) - pow(sig, e, n), n) if p == 1: continue print(p) break q = n // p flag = "flag{" + sha1(long_to_bytes(p if p < q else q)).hexdigest() + "}" print(flag) # flag{601cb6f6d990ed5b89cf0de60508a95c07543793} bank proof_of_work: ...