My Cloud PR4100 / PR2100 Firmware Analysis


#1

After some trial and error, I’ve successfully built the WD PR4100 firmware from the GPL source code. However, this is just a test. I’m a long way from being ready to risk uploading it to my NAS, which would also void the warranty.

Ideally, I’d like to create a test environment to run the firmware or possibly locate similar (and cheaper) hardware for testing. If there is any interest, I may create a guide so others can do the same.


[GUIDE] How to Make Persistent System Changes (crontab, etc)
NFS no_root_squash or UID/GID preset
#2

Eventually, I’d like to update the kernel and some of the packages contained in the firmware. With this in mind, I successfully built new firmware using an updated Linux 4.9.19 kernel. The newer kernel caused a few compile warnings/errors with netatop version 0.6, but that was expected. Updating netatop to version 1.0 should resolve most or all warnings/errors encountered.

Upgrading the kernel is no simple matter, and one must be extremely careful to select the proper options during the config process, especially when selecting hardware. Using “make oldconfig” to import the old config file helps, but a new kernel often has new options, and each option has potential consequences. For this test, all new config options were set to their default values.

Regardless, this was just a test. When the time comes, I plan to make many other changes long before attempting to update the kernel.


#3

To verify that the everything is correct, I used Binwalk to analyze and extract the contents of the .bin file generated by the firmware build (merge) process. Afterwards, I did the same to the factory firmware .bin file and the results are comparable. The byte offsets sometimes vary slightly, but that’s to be expected.

Kernel (uImage):
128 - 0x80 = uImage header (Kernel)
192 - 0xC0 = Microsoft PE executable (Kernel wrapper)
17524 - 0x4474 = Kernel (ELF Executable)

RAM file system (uRamdisk):
5800592 - 0x588290 = uImage header (uRamdisk Image)
5800656 - 0x5882D0 = uRamdisk (CPIO)

Module (image.cfs):
9663112 - 0x937288 = Squashfs file system

Default (default.tar.gz):
119050888 - 0x7189288 = Default configuration files

GRUB (grub.tgz):
119067204 - 0x718D244 = GRUB boot loader


#4

Part of my firmware analysis process is to learn what happens to the firmware .bin file after it’s uploaded to the NAS. The firmware .bin file is just a collection of separate files all combined into one, which means they must be separated and/or extracted on the receiving end, but what happens after that?

I first tried looking at the compiled binary files which are executed from the dashboard during the firmware upgrade process, but that was a dead end. This is when I began to consider an alternate approach. I decided to start by examining the NAS boot sequence, from the beginning to the end.

Long story short, I finally found the keys to the kingdom, a series of hidden system areas where certain critical system files are extracted from the firmware .bin file and stored. These system areas effectively allow one to take FULL CONTROL of the NAS, without the need to create new firmware, or at least not all of it. I also discovered the location of the NAS rescue firmware, as shown below.

# ls -l
-rwxr-xr-x    1 root     root      15728640 Feb  1 14:37 rescue_firmware
-rw-r--r--    1 root     root       5800592 Feb  1 14:37 uImage
-rw-r--r--    1 root     root            33 Feb  1 14:37 uImage_md5checksum
-rw-r--r--    1 root     root       8192228 Feb  1 14:37 uRamdisk
-rw-r--r--    1 root     root            33 Feb  1 14:37 uRamdisk_md5checksum

The hidden system areas include the GRUB bootloader, linux kernel, initramfs, and more. There is also a hidden system area where certain configuration and log files are stored, most notably the config.xml file. This is the actual source, and the reason why many people have been unable to make persistent changes to cron jobs and other NAS functions. Every time the NAS is rebooted, certain files are read from this location, and written to a file system which is refreshed after every reboot.


#5

You seem to be a talented developer and I wish I could be of more help to you. Indeed, testing the custom firmware is a risk that could brick your NAS beyond repair, and I am not aware of a method to virtualize the hardware or force-install the original firmware in case of an emergency.

Have you checked the My Cloud Developer site for guidance?

http://developer.mycloud.com/


#6

Thanks. I looked at the My Cloud developer website, but they only seem to have high-level information pertaining to the My Cloud OS. I’m working directly with the Linux kernel and underlying hardware, which requires very detailed system information, plus some familiarity with SSH and Linux commands.

Unfortunately, I haven’t had much luck getting the kernel to run in a VirtualBox, so I tried booting it from a specially prepared USB stick. GRUB loads fine, but the kernel crashes every time. It was a long shot because the PR4100 NAS has an Intel Pentium N3710 (64 bit) Braswell CPU architecture and my local PC has an Intel Core i7 (64 bit) Sandybridge CPU architecture. I could probably tweak the gcc compiler options to get the kernel to boot on my PC, but doing so could break compatibility with the NAS, so I’m not going to try.

VirtualBox is a pretty robust platform so I haven’t given up on it yet, although I’m not certain what options I haven’t tried. In the end, I may simply have to take the plunge and upload the newly built firmware directly to the NAS and hope everything is correct. If that fails, there is always the rescue firmware. And if that fails, there is always the direct serial connection method using an FTDI 3.3V breakout board, an option of last resort I hope to avoid.

For the time being, I’m just poking around and learning as I go. I only wish more detailed information were included with the My Cloud PR4100 GPL source code, which might require less reverse engineering to figure out how everything works.


#7

Can you describe the process you do when you build this firmware ( I have PR2100 Mycloud ), and need rebuild “php” to enable ZIP extension for opencart setup work ?
:slight_smile: @dswv42 Can you modify uimage ? Wd mycloud may have a bootloader or else, may be we can make it boot on the fly other kernel and if has problem we can reboot to restore base kernel, I think.


#8

I’ve been working on a guide, but it will take some time to complete. Building the firmware is fairly simple, once you know how, but there are a lot of things one must take into consideration.

Yes. The uImage file is just a wrapper for the Linux kernel, but modifying or replacing the kernel is a very complex process, with many variables to take into consideration.


#9

After reading about the Kernel in certain WD My Cloud firmware versions using non-standard 64kB pages, which often cause more problems than they solve, I decided to see what page size the WD PR4100 NAS uses.

The Kernel’s default page size can be viewed by querying its configuration variables via the getconf command. The result is displayed in bytes, so 4096 bytes equals 4 kilobytes (4kB) and 65536 bytes equals 64 kilobytes (64kB).

getconf PAGE_SIZE
getconf PAGESIZE

To display all configuration variables for the current system and their values.

getconf -a

The page size is a compromise between memory usage and speed. A larger page size means more waste when a page is partially used, so the system runs out of memory sooner. The gains of larger page sizes are minimal for most applications, but the cost is substantial. This is why most systems use 4kB pages.

Fortunately, the Kernel for the WD PR4100 NAS (firmware 2.21.126) is configured to use standard 4kB pages.


#10

While examining the various steps of the firmware build process and trying to determine if portions of the firmware can be built and uploaded separately to the appropriate NAND flash partitions, I noticed something interesting.

Part of the process involves running a shell script, then coping the squashfs generated image.cfs file to a “merge” folder.

./create_image.sh
cp -f image.cfs ../merge/image.cfs

The create_image.sh shell script contains the following commands.

rm -f image.cfs
./squashfs crfs image.cfs -comp xz
./image_checksum image.cfs

When the process is complete, an image.cfs file is generated by the squashfs program, and an image_tmp.cfs file is generated by the image_checksum program.

My theory is that the image_tmp.cfs file has an embedded CRC and/or MD5 checksum, thus allowing it to pass some kind of authentication process. If true, this likely means that the image_tmp.cfs file could be renamed to image.cfs and uploaded to the appropriate NAND flash partition, independent of a complete firmware payload. This theory is supported by the following commands and comments found in the rc.sh system initialization shell script.

MOUNT_CMD="busybox mount"
chk_image
#echo "initramfs: mounting squashfs'd image.cfs"
#${MOUNT_CMD} -t squashfs -o loop /usr/local/tmp/image.cfs /usr/local/modules

Since the image.cfs file is the source of the ramdisk filesystem used by the entire NAS, it only makes sense that a file authentication process would be used to ensure that the file has not been corrupted or tampered with.


#11

Looking for guide building “Open_Source_packages” folder. Try bypass every err(s) but it took too much time, and final linking it make segment fault? when run :frowning:. I Need rebuild php to enable mysqli, zip extension and python UC4 enable for tensorflow install by virtualenv .
I also found some file fault when copy sometime, it make add or replace by null(s) in content, do you catch it ?
@dswv42 I found it using grub for boot maybe we can modify it for checking some file if exit we can del it and boot modify kernel, if not … reboot for default kernel ?


#12

Have you verified that all Linux package dependencies are met? If not, it will cause errors. You also have to be certain that all required firmware build tools are installed.

Otherwise, I’m working on a guide, but it will be some time before it’s finished.


#13

The following source code is largely responsible for the dashboard “Capacity” display discrepancy between “cloud enabled” and “cloud disabled”. This is among a growing list of things I plan to change when I eventually build custom firmware.

Global Functions:

var remote_access = _REST_Get_Cloud_Info();	//true,false
if(remote_access=="false") {
	$("#create_user_tb input[name='users_mail_text']").addClass("gray_out").attr('readonly','true');
	$("#home_cloud_link").css({'left':'-10px'});
	$("#home_cloud_link").show();

	home_show_volume_info("home");
} else {
	$("#home_cloud_link").removeClass('gray_out');
	$("#home_cloud_link").show();

	//diag_volcapacity_info();
	home_volcapacity_pie();
}

Code from: dashboard.php

var REST_VERSION = "2.1";

function _REST_Get_Cloud_Info() {
	var Remote_Access="";
	wd_ajax({
		type: "GET",
		async: false,
		cache: false,
		//url: "/api/" + REST_VERSION + "/rest/device",
		url: "/web/restAPI/restAPI_action.php?action=device",
		dataType: "xml",
		success:function(xml)
		{
			Remote_Access= $(xml).find('remote_access').text();
		},
		error: function (request, status, error) {
			Remote_Access="false";
		}
	});
	return Remote_Access;
}

Code from: rest.js

.
Cloud Enabled:

function home_volcapacity_pie() {
    //		var home_storage_info = home_pie_cgi_storage_usage();
    var home_storage_info = home_pie_api_storage_usage();

    if (home_storage_info.length == 0) {
        home_show_volume_info("home");
        return;
    }

    $("#home_volcapity_pei").show();
    $("#VolCapity_pei").show();

    /*
    home_storage_info:
    home_storage_info[0], size;
    home_storage_info[1], usage;
    home_storage_info[2], video;
    home_storage_info[3], photos;
    home_storage_info[4], music;
    home_storage_info[5], other;
    */
    var total_free_blocks_size = (parseInt(home_storage_info[0], 10) - parseInt(home_storage_info[1], 10));
    var total_use_blocks_size = home_storage_info[1];
    var my_videos = Math.max(0, parseInt(home_storage_info[2], 10));
    var my_music = Math.max(0, parseInt(home_storage_info[4], 10));
    var my_photo = Math.max(0, parseInt(home_storage_info[3], 10));
    var my_others = Math.max(0, parseInt(home_storage_info[5], 10));

    var show_my_free_size = (parseInt(total_free_blocks_size, 10) == 0) ? "0 MB" : size2str(total_free_blocks_size, "", true, 3);
    var show_my_videos = (parseInt(my_videos, 10) == 0) ? "0 MB" : size2str(my_videos);
    var show_my_music = (parseInt(my_music, 10) == 0) ? "0 MB" : size2str(my_music);
    var show_my_photo = (parseInt(my_photo, 10) == 0) ? "0 MB" : size2str(my_photo);
    var show_my_other = (parseInt(my_others, 10) == 0) ? "0 MB" : size2str(my_others);

    var _tmp = show_my_free_size.replace(" ", "<br>");
    $("#home_b4_free").html(_tmp).contents().filter(function() {
        return this.nodeType === 3;
    }).wrap("<span></span>").end().filter("br").remove();

    var data = [{
            label: _T("_home", "desc12") + " <span class='legendLabel_size'>" + show_my_videos + "</span>",
            data: parseInt(my_videos, 10)
        },
        {
            label: _T("_home", "desc14") + " <span class='legendLabel_size'>" + show_my_photo + "</span>",
            data: parseInt(my_photo, 10)
        },
        {
            label: _T("_home", "desc13") + " <span class='legendLabel_size'>" + show_my_music + "</span>",
            data: parseInt(my_music, 10)
        },
        {
            label: _T("_home", "desc15") + " <span class='legendLabel_size'>" + show_my_other + "</span>",
            data: parseInt(my_others, 10)
        },
        {
            label: _T("_home", "desc11") + " <span class='legendLabel_size'>" + show_my_free_size + "</span>",
            data: parseInt(total_free_blocks_size, 10)
        }
    ];

    $.plot($("#VolCapity_pei"), data, {
        series: {
            pie: {
                innerRadius: 0.6,
                show: true,
                stroke: {
                    color: '#A7AFAE'
                },
                //						tilt: 0.5,
                label: {
                    formatter: function(label, series) {
                        return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;">' + label + '<br/>' + Math.round(series.percent) + '%</div>';
                    }
                } //end of label

            } //end of pie
        }, //end of series
        legend: {
            margin: [-200, 30], //[-150,70],/*[x margin, y margin]*/
            backgroundColor: "null"
        }, //end of legend
        colors: ["#CC505F", "#EDD058", "#3CA570", "#599DC6", "#A7AFAE"]
    }); //end of $.plot...	

    $('#VolCapity_pei .legend table').css({
        'right': '-219px',
        'width': '198px',
        'height': '242px',
        'top': '0'
    });
    //$('#VolCapity_pei .legend .legendLabel').css('width', '240px');
    $('#VolCapity_pei .legend .legendColorBox').each(function(n) {
        switch (n) {
            case 0:
                $(this).empty().append("<img src='/web/images/icon/dashboard/NAS_icn_capacity_videos.png'/>");
                break;
            case 1:
                $(this).empty().append("<img src='/web/images/icon/dashboard/NAS_icn_capacity_photos.png'/>");
                break;
            case 2:
                $(this).empty().append("<img src='/web/images/icon/dashboard/NAS_icn_capacity_music.png'/>");
                break;
            case 3:
                $(this).empty().append("<img src='/web/images/icon/dashboard/NAS_icn_capacity_others.png'/>");
                break;
            case 4:
                $(this).empty().append("<img src='/web/images/icon/dashboard/NAS_icn_capacity_free-space.png'/>");
                break;
        }
    });

    $('#VolCapacityDiag .legend .legendLabel').each(function(n) {
        $(this).attr("id", "home_legendLabel" + (n + 1) + "_value");
    });
}

Code from: homeDiag.js

function home_pie_api_storage_usage() {
    var my_info = new Array();
    /*
    	my_info:
    	my_info[0], size;
    	my_info[1], usage;
    	my_info[2], video;
    	my_info[3], photos;
    	my_info[4], music;
    	my_info[5], other;
    */
    wd_ajax({
        type: "GET",
        url: "/api/2.1/rest/storage_usage",
        //url: "/xml/storage_usage.xml",
        data: {},
        dataType: "xml",
        async: false,
        cache: false,
        success: function(xml) {
            var my_size = (parseInt($(xml).find('size').text(), 10) < 0) ? 0 : $(xml).find('size').text();
            var my_videos = (parseInt($(xml).find('video').text(), 10) < 0) ? 0 : $(xml).find('video').text();
            var my_music = (parseInt($(xml).find('music').text(), 10) < 0) ? 0 : $(xml).find('music').text();
            var my_photo = (parseInt($(xml).find('photos').text(), 10) < 0) ? 0 : $(xml).find('photos').text();

            if (parseInt(my_size, 10) == 0) {
                var tmp = new Array();
                tmp = home_pie_cgi_storage_usage();
                my_size = tmp[0].toString();
            }

            my_info.push(my_size);
            my_info.push($(xml).find('usage').text());
            my_info.push(my_videos);
            my_info.push(my_photo);
            my_info.push(my_music);
            my_info.push($(xml).find('other').text());

        }, // end of success 
        error: function(jqXHR, textStatus) {}
    }); //end of ajax

    return my_info;
}

Code from: homeDiag.js

<div style="display:none" id="home_volcapity_pei">
	<div id="VolCapity_pei" class="graph"></div>
	<div class="WDlabelInfoFreeBoxLarge" id="home_b4_free" style="position: absolute; left: 90px; top: 130px; width: 120px; text-align: center; font-size: 40px;"></div>
</div>

Code from: dashboard.php

.
Cloud Disabled:

function home_show_volume_info(str) {
	$("#home_volcapity_pei").hide();
	$("#home_volcapity_info").show();
	
	wd_ajax({
		url: "/xml/sysinfo.xml",
		type: "GET",
		async:false,
		cache:false,
		dataType:"xml",
		success: function(xml){

			var total_free_blocks_size = parseInt($(xml).find('vols').find('total_size').text(),10) - parseInt($(xml).find('vols').find("total_used_size").text() ,10);
			if (total_free_blocks_size < 1) total_free_blocks_size = 0;

			var vol_size = size2str(total_free_blocks_size);
			vol_size = (parseInt(vol_size,10) == 0)?"0.00 MB":vol_size;
			var tmp = vol_size.split(" ");
			if (tmp.length ==2)
			{
				var show_vol_size = show_volsize_str(tmp[0].substring(0,4));
				$("#"+str+"_b4_capacity_info").html(show_vol_size);
				$("#"+str+"_b4_capacity_size").html(tmp[1]);
			}
			else
			{
				$("#"+str+"_b4_capacity_info").html("0");
				$("#"+str+"_b4_capacity_size").html("KB");
			}	
		},
        error:function (xhr, ajaxOptions, thrownError){}  
	});	
}

Code from: home.js
.

<div id="home_volcapity_info" style="clear: both; margin-top: 0px ! important;">
	<div class="WDlabelInfoBoxLarge info_div" id="home_b4_capacity_info" style="margin-top:0;position: relative;top: 60px;">1.82</div>
	<div class="WDlabelInfoSizeBoxLarge" id="home_b4_capacity_size" style="display: inline-block;font-size: 78px;position: relative;top: 85px;padding-right: 34px;padding-left: 150px;color: #85939c;">TB</div>
	<div class="WDlabelInfoFreeBoxLarge" id="home_b4_capacity_free" style="display: inline-block;font-size: 78px;position: relative;top: 85px;color: #85939c; text-transform: lowercase;">
		<span class="_text" datafld="free" lang="_home">FREE</span>
	</div>
</div>

Code from: dashboard.php
.


#14

One or more of the following WDMC processes (wdmcserver, convert, wdphotodbmerger, and avconv) are linked to ImageMagick version 6.7.9 released on 3/1/2015. The latest stable release of ImageMagick is version 7.0.5 released on 3/25/2017. ImageMagick is likely responsible for certain “transcoding” operations which are responsible for generating thousands of hidden image files, whether they are wanted or not. The plugins ffmpeg, taglib, exif, and kthumb are also used, although I have not determined exact versions or release dates yet.

The WDMC database functionality depends on SQLite (sqlite3) version 3.7.17 released on 5/20/2013. The latest stable release of SQLite is version 3.18.0 released on 3/30/2017.

The following is the wdmc.xml WDMC default configuration file.

<?xml version = "1.0" encoding = "UTF-8"?>
<wdmc>
	<PurgeFrequency>259200</PurgeFrequency>
	<PurgeWindow>2592000</PurgeWindow>
	<MinCrawlCapacityMB>256000</MinCrawlCapacityMB>
	<ExternalVolumeEmptyDB>false</ExternalVolumeEmptyDB>
	<FolderChangeWaitMin>5</FolderChangeWaitMin>
	<FolderChangeWaitMax>25</FolderChangeWaitMax>
	<FolderChangeEventsThreshold>5000</FolderChangeEventsThreshold>
	<MaxThreads>2</MaxThreads>
	<AnalyticsLogIntervalSecs>604800</AnalyticsLogIntervalSecs>
	<PathFilters>
		<ExcludePatterns>
			<ExcludePattern>/\.</ExcludePattern>
			<ExcludePattern>/*/found\.[0-9]+</ExcludePattern>
			<ExcludePattern>/*/\~\$</ExcludePattern>
			<ExcludePattern>/*/AlbumArt_\{</ExcludePattern>
			<ExcludePattern>/*/thumbs.db</ExcludePattern>
			<ExcludePattern>/*/desktop.ini</ExcludePattern>
			<ExcludePattern>/*/SmartWare$</ExcludePattern>
			<ExcludePattern>\.sparsebundle</ExcludePattern>
			<ExcludePattern>/*/Recycled</ExcludePattern>
			<ExcludePattern>/*/RECYCLER</ExcludePattern>
			<ExcludePattern>/*/RECYCLED</ExcludePattern>
			<ExcludePattern>/*/\$RECYCLE\.BIN</ExcludePattern>
			<ExcludePattern>/*/System Volume Information</ExcludePattern>
			<ExcludePattern>/*/_WDPROT</ExcludePattern>
			<ExcludePattern>/Nas_Prog(/.*)?$</ExcludePattern>
			<ExcludePattern>/lost\+found</ExcludePattern>
			<ExcludePattern>/AppleDouble(/.*)?$</ExcludePattern>
			<ExcludePattern>/AppleDB(/.*)?$</ExcludePattern>
			<ExcludePattern>/AppleDesktop(/.*)?$</ExcludePattern>
			<ExcludePattern>/TemporaryItems(/.*)?$</ExcludePattern>
			<ExcludePattern>/\~\.</ExcludePattern>
			<ExcludePattern>/\~.*\.tmp$</ExcludePattern>
			<ExcludePattern>/Backups.backupdb(/.*)?$</ExcludePattern>
			<ExcludePattern>/*/*.mbox/.*/Attachments/</ExcludePattern>
		</ExcludePatterns>
		<CoverArtPatterns>
			<CoverArtPattern>/AlbumArt.*\.jpg</CoverArtPattern>
			<CoverArtPattern>/Folder\.jpg</CoverArtPattern>
		</CoverArtPatterns>
		<BackupPatterns>
			<BackupPattern>/*/WD SmartWare\.swstor/*</BackupPattern>
		</BackupPatterns>
		<SystemPatterns>
			<SystemPattern>/*/\.wdmc/*</SystemPattern>
		</SystemPatterns>
	</PathFilters>
	<!--
		AutoTranscodeMap specifies 'priority' and 'size' only.
		The Profile & Plugin GUIDs are only determined during run-time.
		This depends on information such as file extension/category/size etc.
	-->
	<AutoTranscodeMap>
		<!-- tn96s1 -->
		<AutoTranscode Size = "96x" Priority = "1"/>
		<!-- i1024s1 -->
		<AutoTranscode Size = "1024x" Priority = "2"/>
	</AutoTranscodeMap>
</wdmc>

The following is the plugins.xml WDMC default configuration file.

<?xml version = "1.0" encoding = "UTF-8"?>
<wdmc>
	<!--
		Profiles are divided into, ExtractorProfiles & TranscoderProfiles. Each profile references a plugin that has to be specified below.

		GUID:			Unique identifier for profile.

		Category:		Can be [video/image/music/other]

		Default:		Default plugin, for a given category, for those file types that do not have their extensions
						specified in the <MimeType> tags. A category should have one default plugin.

		Plugin:			This the GUID of the plugin referenced by this profile.

		MimeTypes:		List multiple preferred mimetypes supported by this profile where each one is specified by a <MimeType> tag.

		Size:			ONLY used for <TranscoderProfiles>, same as specified in the <AutotranscodeMap>.
						'Size', 'Priority', file extension, category are used to find right profile and plugin.

		Parameters:		(OPTIONAL) Not all profiles will specify <Parameters> to be passed in. These are command-line arguments
						that may be required by the referenced plugin. 	-->

	<ExtractorProfiles>
		<Profile GUID = "fa82f74dded94b0f86a4f67efcfe4c2f"
				 Category = "video"
				 Default = "yes"
				 Plugin = "091b212118e148259cca32465b6194dc">
			<MimeType>video/mp4</MimeType>
		</Profile>
		<Profile GUID = "4c2e6ce9965845c18aeaa8816765834c"
				 Category = "audio"
				 Default = "yes"
				 Plugin = "766e351ce6954326b3e7cac4aa6c4080">
			<MimeType>audio/mpeg</MimeType>
		</Profile>
		<Profile GUID = "43adef2173c0412fa908243885cd6f81"
				 Category = "image"
				 Default = "yes"
				 Plugin = "d1ca5301f918452692947865ce940a87">
			<MimeType>image/jpeg</MimeType>
		</Profile>
	</ExtractorProfiles>

	<TranscoderProfiles>
		<Profile GUID = "cb62bbdd389b48898f2e5244977cb2c5"
				 Category = "image"
				 Size = "96x"
				 Default = "yes"
				 Plugin = "d312a08daf7d4218a7d1222eb67d1906">
			<Parameter Name = "-define" Value = "jpeg:size=192x192"/>
			<Parameter Name = "source"/>
			<Parameter Name = "-auto-orient"/>
			<Parameter Name = "-strip"/>
			<Parameter Name = "-background" Value = "#000000"/>
			<Parameter Name = "-quality" Value = "80"/>
			<Parameter Name = "-filter" Value = "box"/>
			<Parameter Name = "-resize" Value = "x192"/>
			<Parameter Name = "destination"/>
			<MimeType>image/jpeg</MimeType>
		</Profile>
		<Profile GUID = "a76ef047d8d24c03823acdf41c4ee7c8"
				 Category = "image"
				 Size = "1024x"
				 Default = "yes"
				 Plugin = "944dfee7a3934671a1b910639b68a05a">
			<Parameter Name = "-size" Value = "1024x"/>
			<Parameter Name = "-quality" Value = "85"/>
			<MimeType>image/jpeg</MimeType>
		</Profile>
	</TranscoderProfiles>

	<!--
		This specifies all plugins that we use for metadata extraction or transcoding.

		GUID:			This is a unique identifier for the plugin. Plugins referenced by used profiles will be loaded.

		Categories:		Semicolon separated list of supported categories. In general, a plugin
						could extract metadata for more	than one category. Of course 'categories' can only be [video/image/music/other].

		Action:			"Extractor", "Transcoder", "ExtractorTranscoder"

		Description:	help string to describe the plugin.

		Path:			A relative path to the plugin. Our plugins are all in sub-folders of the /bin/ folder.
	-->
	<Plugins>
		<Plugin GUID = "091b212118e148259cca32465b6194dc"
				Name = "ffmpeg"
				Categories = "video"
				Action = "Extractor"
				Description = "Uses ffmpeg library for frame extraction"
				Path = "extractors/libwdmcffmpegplugin"/>
		<Plugin GUID = "766e351ce6954326b3e7cac4aa6c4080"
				Name = "TagLib"
				Categories = "audio"
				Action = "Extractor"
				Description = "Uses TagLib library to extract audio metadata."
				Path = "extractors/libwdmctaglibplugin"/>
		<Plugin GUID = "d1ca5301f918452692947865ce940a87"
				Name = "exif"
				Categories = "image"
				Action = "Extractor"
				Description = "Image Metadata extraction Library"
				Path = "extractors/libwdmcexifplugin"/>
		<Plugin GUID = "944dfee7a3934671a1b910639b68a05a"
				Name = "kthumb"
				Categories = "image"
				Action = "Transcoder"
				Description = ""
				Path = "transcoders/libkthumb"/>
		<Plugin GUID = "d312a08daf7d4218a7d1222eb67d1906"
				Name = "ImageMagick"
				Categories = "image"
				Action = "Transcoder"
				Description = "Uses ImageMagick Command line tool."
				Path = "transcoders/libwdmcimagemagickplugin"/>
	</Plugins>
</wdmc>

#15

Well now, look what I found… “Install, remove and manage Debian packages”. WD removed or excluded apt-get, but that’s easy enough to fix when the time comes.

# dpkg

BusyBox v1.20.2 (2014-10-30 15:29:55 CST) multi-call binary.

Usage: dpkg [-ilCPru] [-F OPT] PACKAGE

Install, remove and manage Debian packages

	-i,--install    Install the package
	-l,--list       List of installed packages
	--configure     Configure an unpackaged package
	-P,--purge      Purge all files of a package
	-r,--remove     Remove all but the configuration files for a package
	--unpack        Unpack a package, but don't configure it
	--force-depends Ignore dependency problems
	--force-confnew Overwrite existing config files when installing
	--force-confold Keep old config files when installing

And before anyone suggests the 64k page size issue, the WD PR4100 NAS uses standard 4k pages.

# getconf PAGE_SIZE
# getconf PAGESIZE

4096


#16

The OpenSSH daemon used by the WD My Cloud PR4100 NAS (firmware version 2.30.165 and prior) is ancient and very likely to be incompatible with other devices running newer SSH versions. The OpenSSL version is also old and riddled with bugs and security vulnerabilities.

# sshd -v

sshd: illegal option -- v
OpenSSH_5.0p1, OpenSSL 1.0.1m 19 Mar 2015
usage: sshd [-46Ddeiqt] [-b bits] [-f config_file] [-g login_grace_time]
            [-h host_key_file] [-k key_gen_time] [-o option] [-p port] [-u len]

# cat /etc/ssh/sshd_config

Port 22
Protocol 2
HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_rsa_key
SyslogFacility AUTHPRIV
AllowUsers root sshd
RSAAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords yes
PasswordAuthentication yes
ChallengeResponseAuthentication no
TCPKeepAlive yes
Subsystem sftp /usr/bin/sftp-server

Installed: openssh-5.0p1.tar.gz (4/3/2008)
Current: openssh-7.5p1.tar.gz (3/20/2017)
URL: https://www.openssh.com
URL: https://mirror.esc7.net/pub/OpenBSD/OpenSSH/portable

Note: The portable OpenSSH follows development of the official version, but releases are not synchronized. Portable releases are marked with a ‘p’ (e.g. 4.0p1). The official OpenBSD source will never use the ‘p’ suffix, but will instead increment the version number when they hit ‘stable spots’ in their development.

Installed: openssl-1.0.1m.tar.gz (3/19/2015)
Current: openssl-1.1.0e.tar.gz (2/16/2017)
URL: https://www.openssl.org

OpenSSL Vulnerabilities: https://www.openssl.org/news/vulnerabilities.html

# openssl version

OpenSSL 1.0.1m 19 Mar 2015

Standard commands
asn1parse         ca                ciphers           cms
crl               crl2pkcs7         dgst              dh
dhparam           dsa               dsaparam          ec
ecparam           enc               engine            errstr
gendh             gendsa            genpkey           genrsa
nseq              ocsp              passwd            pkcs12
pkcs7             pkcs8             pkey              pkeyparam
pkeyutl           prime             rand              req
rsa               rsautl            s_client          s_server
s_time            sess_id           smime             speed
spkac             srp               ts                verify
version           x509

Message Digest commands (see the `dgst' command for more details)
md4               md5               rmd160            sha
sha1

Cipher commands (see the `enc' command for more details)
aes-128-cbc       aes-128-ecb       aes-192-cbc       aes-192-ecb
aes-256-cbc       aes-256-ecb       base64            bf
bf-cbc            bf-cfb            bf-ecb            bf-ofb
camellia-128-cbc  camellia-128-ecb  camellia-192-cbc  camellia-192-ecb
camellia-256-cbc  camellia-256-ecb  cast              cast-cbc
cast5-cbc         cast5-cfb         cast5-ecb         cast5-ofb
des               des-cbc           des-cfb           des-ecb
des-ede           des-ede-cbc       des-ede-cfb       des-ede-ofb
des-ede3          des-ede3-cbc      des-ede3-cfb      des-ede3-ofb
des-ofb           des3              desx              rc2
rc2-40-cbc        rc2-64-cbc        rc2-cbc           rc2-cfb
rc2-ecb           rc2-ofb           rc4               rc4-40
seed              seed-cbc          seed-cfb          seed-ecb
seed-ofb          zlib

#17

The WD My Cloud PR4100 NAS (firmware version 2.30.165 and prior) have a very old version of rsync installed by default, yet this fact does not seem to be advertised. Portions of the My Cloud OS appear to use rsync extensively, yet no options to directly use or configure rsync appear to be made available via the dashboard.

Installed: rsync-3.0.7.tar.gz (12/31/2009)
Current: rsync-3.1.2.tar.gz (12/31/2015)
URL: http://rsync.samba.org
URL: https://download.samba.org/pub/rsync/src/

Rsync Security Advisories: https://rsync.samba.org/security.html

# rsync

rsync  version 3.0.7  protocol version 30
Copyright (C) 1996-2009 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
    64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
    socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
    append, no ACLs, xattrs, iconv, symtimes
Panic Action: "xterm -display :0 -T Panic -n Panic -e gdb /proc/%d/exe %d"

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.

rsync is a file transfer program capable of efficient remote update
via a fast differencing algorithm.

Usage: rsync [OPTION]... SRC [SRC]... DEST
  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
  or   rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST
  or   rsync [OPTION]... [USER@]HOST:SRC [DEST]
  or   rsync [OPTION]... [USER@]HOST::SRC [DEST]
  or   rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect
to an rsync daemon, and require SRC or DEST to start with a module name.

Options
 -v, --verbose               increase verbosity
 -q, --quiet                 suppress non-error messages
     --no-motd               suppress daemon-mode MOTD (see manpage caveat)
 -c, --checksum              skip based on checksum, not mod-time & size
 -a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
     --no-OPTION             turn off an implied OPTION (e.g. --no-D)
 -r, --recursive             recurse into directories
 -R, --relative              use relative path names
     --no-implied-dirs       don't send implied dirs with --relative
 -b, --backup                make backups (see --suffix & --backup-dir)
     --backup-dir=DIR        make backups into hierarchy based in DIR
     --suffix=SUFFIX         set backup suffix (default ~ w/o --backup-dir)
 -u, --update                skip files that are newer on the receiver
     --inplace               update destination files in-place (SEE MAN PAGE)
     --append                append data onto shorter files
     --append-verify         like --append, but with old data in file checksum
 -d, --dirs                  transfer directories without recursing
 -l, --links                 copy symlinks as symlinks
 -L, --copy-links            transform symlink into referent file/dir
     --copy-unsafe-links     only "unsafe" symlinks are transformed
     --safe-links            ignore symlinks that point outside the source tree
 -k, --copy-dirlinks         transform symlink to a dir into referent dir
 -K, --keep-dirlinks         treat symlinked dir on receiver as dir
 -H, --hard-links            preserve hard links
 -p, --perms                 preserve permissions
 -E, --executability         preserve the file's executability
     --chmod=CHMOD           affect file and/or directory permissions
 -X, --xattrs                preserve extended attributes
 -o, --owner                 preserve owner (super-user only)
 -g, --group                 preserve group
     --devices               preserve device files (super-user only)
     --specials              preserve special files
 -D                          same as --devices --specials
 -t, --times                 preserve modification times
 -O, --omit-dir-times        omit directories from --times
     --super                 receiver attempts super-user activities
     --fake-super            store/recover privileged attrs using xattrs
 -S, --sparse                handle sparse files efficiently
 -n, --dry-run               perform a trial run with no changes made
 -W, --whole-file            copy files whole (without delta-xfer algorithm)
 -x, --one-file-system       don't cross filesystem boundaries
 -B, --block-size=SIZE       force a fixed checksum block-size
 -e, --rsh=COMMAND           specify the remote shell to use
     --rsync-path=PROGRAM    specify the rsync to run on the remote machine
     --existing              skip creating new files on receiver
     --ignore-existing       skip updating files that already exist on receiver
     --remove-source-files   sender removes synchronized files (non-dirs)
     --del                   an alias for --delete-during
     --delete                delete extraneous files from destination dirs
     --delete-before         receiver deletes before transfer, not during
     --delete-during         receiver deletes during transfer (default)
     --delete-delay          find deletions during, delete after
     --delete-after          receiver deletes after transfer, not during
     --delete-excluded       also delete excluded files from destination dirs
     --ignore-errors         delete even if there are I/O errors
     --force                 force deletion of directories even if not empty
     --max-delete=NUM        don't delete more than NUM files
     --max-size=SIZE         don't transfer any file larger than SIZE
     --min-size=SIZE         don't transfer any file smaller than SIZE
     --partial               keep partially transferred files
     --partial-dir=DIR       put a partially transferred file into DIR
     --delay-updates         put all updated files into place at transfer's end
 -m, --prune-empty-dirs      prune empty directory chains from the file-list
     --numeric-ids           don't map uid/gid values by user/group name
     --timeout=SECONDS       set I/O timeout in seconds
     --contimeout=SECONDS    set daemon connection timeout in seconds
 -I, --ignore-times          don't skip files that match in size and mod-time
     --size-only             skip files that match in size
     --modify-window=NUM     compare mod-times with reduced accuracy
 -T, --temp-dir=DIR          create temporary files in directory DIR
 -y, --fuzzy                 find similar file for basis if no dest file
     --compare-dest=DIR      also compare destination files relative to DIR
     --copy-dest=DIR         ... and include copies of unchanged files
     --link-dest=DIR         hardlink to files in DIR when unchanged
 -z, --compress              compress file data during the transfer
     --compress-level=NUM    explicitly set compression level
     --skip-compress=LIST    skip compressing files with a suffix in LIST
 -C, --cvs-exclude           auto-ignore files the same way CVS does
 -f, --filter=RULE           add a file-filtering RULE
 -F                          same as --filter='dir-merge /.rsync-filter'
                             repeated: --filter='- .rsync-filter'
     --exclude=PATTERN       exclude files matching PATTERN
     --exclude-from=FILE     read exclude patterns from FILE
     --include=PATTERN       don't exclude files matching PATTERN
     --include-from=FILE     read include patterns from FILE
     --files-from=FILE       read list of source-file names from FILE
 -0, --from0                 all *-from/filter files are delimited by 0s
 -s, --protect-args          no space-splitting; only wildcard special-chars
     --address=ADDRESS       bind address for outgoing socket to daemon
     --port=PORT             specify double-colon alternate port number
     --sockopts=OPTIONS      specify custom TCP options
     --blocking-io           use blocking I/O for the remote shell
     --stats                 give some file-transfer stats
 -8, --8-bit-output          leave high-bit chars unescaped in output
 -h, --human-readable        output numbers in a human-readable format
     --progress              show progress during transfer
 -P                          same as --partial --progress
 -i, --itemize-changes       output a change-summary for all updates
     --out-format=FORMAT     output updates using the specified FORMAT
     --log-file=FILE         log what we're doing to the specified FILE
     --log-file-format=FMT   log updates using the specified FMT
     --password-file=FILE    read daemon-access password from FILE
     --list-only             list the files instead of copying them
     --bwlimit=KBPS          limit I/O bandwidth; KBytes per second
     --write-batch=FILE      write a batched update to FILE
     --only-write-batch=FILE like --write-batch but w/o updating destination
     --read-batch=FILE       read a batched update from FILE
     --protocol=NUM          force an older protocol version to be used
     --iconv=CONVERT_SPEC    request charset conversion of filenames
 -4, --ipv4                  prefer IPv4
 -6, --ipv6                  prefer IPv6
     --version               print version number
(-h) --help                  show this help (-h works with no other options)

Use "rsync --daemon --help" to see the daemon-mode command-line options.
Please see the rsync(1) and rsyncd.conf(5) man pages for full documentation.
See http://rsync.samba.org/ for updates, bug reports, and answers
rsync error: syntax or usage error (code 1) at main.c(1434) [client=3.0.7]

#18

Recently, I discovered a bug in the “Internal Backups” functionality of the WD My Cloud PR4100 dashboard which causes certain folders to be omitted from the destination. The bug is known to be present in firmware versions 2.21.126 and 2.30.165 but it’s likely present in older firmware versions too.

Further analysis revealed that the “Internal Backups” functionality calls a binary program named internal_backup, which appears to have been written specifically for the WD My Cloud OS.

# /./usr/sbin/internal_backup
# /./usr/local/modules/usrsbin/internal_backup

Internal_backup usage
storage:
  -a [task_name]
  -b [1/2] device 1:storage 2:mtp
  -c [jobadd/jobedit/jobdel/jobrun/jobstop/jobrs/jobrs_list]
  -m [1/2] mode 1:copy 2:sync
  -s [sour_path]
  -d [dest_path]
 add task    ex : internal_backup -a 1234 -m 1 -s "sour_path" -d "dest_path" -c jobadd
 modify task ex : internal_backup -a 1234 -m 1 -s "sour_path" -d "dest_path" -x 1234 -c jobedit
 get incremental list ex: internal_backup -o [output filename] -c jobrs_list
 recorvery incremental ex: internal_backup -a 1234 -F [increnebtal list name] -c jobrs

Interestingly, the internal_backup binary program appears to call rsync to perform the actual backup task. And despite the backups bug noted previously, rsync performs an identical internal backup job flawlessly. For example:

# rsync -v -a /shares/SHARE_1/TEST /shares/Public/RSYNC_TEST

Since rsync works as expected, the bug is clearly present in the internal_backup binary program, which begs the question… Why use a custom binary program as a means of calling a standard and commonly used Linux program? All it does is create added complexity and add additional points of failure, as evidenced by the backups bug.


#19

The WD My Cloud PR4100 NAS (firmware version 2.30.165 and prior) have an old version of nano GNU text editor installed by default. I guess the developers got sick of using vi… can’t say I blame them.

Installed: nano-2.2.6.tar.gz (11/22/2010)
Current: nano-2.8.1.tar.gz (4/12/2017)
URL: https://nano-editor.org
URL: https://nano-editor.org/dist/

# nano -V

 GNU nano version 2.2.6 (compiled 19:37:28, Oct 15 2014)
 (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
 2008, 2009 Free Software Foundation, Inc.
 Email: nano@nano-editor.org    Web: http://www.nano-editor.org/
 Compiled options: --enable-color --enable-extra --enable-multibuffer --enable-nanorc

# nano -h

Usage: nano [OPTIONS] [[+LINE,COLUMN] FILE]...

Option          GNU long option         Meaning
 -h, -?         --help                  Show this message
 +LINE,COLUMN                           Start at line LINE, column COLUMN
 -A             --smarthome             Enable smart home key
 -B             --backup                Save backups of existing files
 -C <dir>       --backupdir=<dir>       Directory for saving unique backup files
 -D             --boldtext              Use bold instead of reverse video text
 -E             --tabstospaces          Convert typed tabs to spaces
 -F             --multibuffer           Enable multiple file buffers
 -H             --historylog            Log & read search/replace string history
 -I             --ignorercfiles         Don't look at nanorc files
 -K             --rebindkeypad          Fix numeric keypad key confusion problem
 -L             --nonewlines            Don't add newlines to the ends of files
 -N             --noconvert             Don't convert files from DOS/Mac format
 -O             --morespace             Use one more line for editing
 -Q <str>       --quotestr=<str>        Quoting string
 -R             --restricted            Restricted mode
 -S             --smooth                Scroll by line instead of half-screen
 -T <#cols>     --tabsize=<#cols>       Set width of a tab to #cols columns
 -U             --quickblank            Do quick statusbar blanking
 -V             --version               Print version information and exit
 -W             --wordbounds            Detect word boundaries more accurately
 -Y <str>       --syntax=<str>          Syntax definition to use for coloring
 -c             --const                 Constantly show cursor position
 -d             --rebinddelete          Fix Backspace/Delete confusion problem
 -i             --autoindent            Automatically indent new lines
 -k             --cut                   Cut from cursor to end of line
 -l             --nofollow              Don't follow symbolic links, overwrite
 -m             --mouse                 Enable the use of the mouse
 -o <dir>       --operatingdir=<dir>    Set operating directory
 -p             --preserve              Preserve XON (^Q) and XOFF (^S) keys
 -q             --quiet                 Silently ignore startup issues like rc file errors
 -r <#cols>     --fill=<#cols>          Set wrapping point at column #cols
 -s <prog>      --speller=<prog>        Enable alternate speller
 -t             --tempfile              Auto save on exit, don't prompt
 -u             --undo                  Allow generic undo [EXPERIMENTAL]
 -v             --view                  View mode (read-only)
 -w             --nowrap                Don't wrap long lines
 -x             --nohelp                Don't show the two help lines
 -z             --suspend               Enable suspension
 -$             --softwrap              Enable soft line wrapping
 -a, -b, -e,
 -f, -g, -j                             (ignored, for Pico compatibility)

#20

This topic is sure a fun read. Thanks for the posts @dswv42. Sure digs out a lot of dirt. Worried about WD pulling a “Trump” on it…