はじめに
送料込みで約5000円で買えるPan/Tilt CameraのAtomSwingを購入したので遊んでみました。
この記事ではAtomSwingのアプリであるiCamera_appとliblocalsdk.so, liblocalsdk_motor.soを解析して改造するための手順を説明します。
※ 以下の内容を実行する場合は各自の責任において行ってください。 当然ながらメーカーへの問い合わせは厳に慎んでください。
準備
AtomSwing
AtomCam2では既に色々と見ていたので(下記Qiitaの記事)AtomSwingもほぼ同じだろうと当たりをつけて見てみました。
WiFiのDriverが変更になっていたのとmotorのdriverが追加になった以外はAtomCam2と同じ構成で簡単にloginできる状態までいきました。
関連記事
githubに展開用のイメージも置いてあります。
上記のイメージのauthorized_keysにsshの公開鍵を設定したSD-Cardを作成、AtomSwingにセットして起動します。
build環境
build.md
の中の
- 必要なbuild環境
- build方法
の手順で一度イメージをbuildした環境を作ります。
後々このdocker環境内でMIPSのobjdumpをしたり、iCamera_appのhookを追加するのに利用します。
Ghidra
アプリやライブラリの中をざっと探すのに非常に便利なツールです。
Ghidraの使い方はGoogleで検索すると解説されている方達がいますのでここでは割愛します。
ただし、以下の点でMIPSへの対応はあまりちゃんと実装されてないようです。
-
FPUを含むcalling conventionが正しく解釈されない
floatを引数に含む関数の引数がおかしくなる
-
S0~S3レジスタをベースとして計算されるoffset計算の場合の解釈が正しくない
これらの問題もあるので、ちゃんと意味を解釈したい関数の場合は逆アセンブルを読んだほうがいいです。
de-compile結果だと意味不明なことが多々あり嵌まります。
大まかな流れを掴むために使うのが良いかと思います。
gcc一式
上でbuildしたdocker環境内にMIPSのgcc関連一式があります。
製品のAtomCamのアプリはuClibc用にbuildされているので解析にはこちらを使います。
/openmiko/build/mips-gcc472-glibc216-64bit/bin/mips-linux-uclibc-gnu-*
hackしている方のlinux環境は色々とbuildを通す都合上、一般的なlibc環境にしています。
解析手順
以下、AtomSwingにsshでloginできる状態から説明します。
ここではAtomSwingの新機能であるpan/tiltのmotor制御を解析していきます。
sshでloginすると/atom以下に元々のAtomSwingの/(root partition)がmountされています。
/atom/system/bin/iCamera_appがAtomCamのアプリケーション本体です。
/atom/system/lib/liblocalsdk.soがingenic T31用のvideo/audio系のライブラリ
/atom/system/lib/liblocalsdk_motor.soがmotor制御のライブラリ
他にもありますが、motorに関するところをメインに見ていきます。
まず、motor制御関連と思われるliblocalsdk_motor.soをghidraに読み込ませて見ていきます。
SymbolTreeのwindowでFunctionsをみてそれらしい関数をpickupします。
APIとして出ていそうなlocak_sdk_motor_*あたりが怪しそうです。
その中でもmove, move_abs_angle, move_abs_step, move_rel_angle, move_rel_step, move_track, get_positionあたりが何か使えそうです。
GhidraでiCammera_appも開いてみます。
MenuからSearch->ProgramTextを開いてSearch for: にlocal_sdk_motor_move、FieldsをInstruction OperandsにしてSearchAllを実行すると呼び出してる箇所が出てきます。
後半のliblocalsdk_motor.so::*の方はlibrary側なので無視して、呼び出し側のFUN_*の方を見て使っている関数を確認していきます。
move_track, move, move_rel_step, move_abs_angleが呼ばれてます。
同様にget_positionも確認、こちらも4箇所で使われてました。
全部追いかけるのは大変なので、絶対角で移動させられそうな
locak_sdk_motor_move_abs_angle
と現在の方向を取れそうな
locak_sdk_motor_get_position
に目星をつけて追ってみます。
(実際には他にもmoveとかmove_trackとかも途中まで追ったのですが、上の2つで目的果たせそうだったので放棄しました)
local_sdk_motor_get_position
まず、値を返してくる方のget_positionの方から攻めてみます。
Ghidraでliblocalsdk_motor.soのSymbol Treeでlocal_sdk_motor_get_positionをクリックします。
Decompile windowに関数のコードが表示されます。
undefined4 local_sdk_motor_get_position(float *param_1,float *param_2)
{
int iVar1;
undefined4 uVar2;
float fVar3;
if (g_sdkMotorInitOk == 0) {
uVar2 = 0xffffffff;
}
else {
control_motor_inner(motorport_fd,4,&DAT_00022b04);
fVar3 = change_stepX_to_angle(DAT_00022b04);
iVar1 = DAT_00022b08;
*param_1 = fVar3;
fVar3 = change_stepY_to_angle(iVar1);
*param_2 = fVar3;
uVar2 = 0;
}
return uVar2;
}
returnしている値は-1か0なのでint型と推測できます。
motorの初期化が完了していなければ-1を返し、それ以外なら
*(float *)param_1にchange_stepX_to_angle(DAT_00022b04);
*(float *)param_2にchange_stepY_to_angle(iVar1);
を返しているようです。
なので関数としては
int local_sdk_motor_get_position(float *pan,float *tilt);
で返り値は成功時は0失敗時に−1が返り、*panと*tiltにfloatで方向が返ると思われます。
値の範囲とかはこの後、motor_move_abs_angleと一緒に確認します。
local_sdk_motor_move_abs_angle
こちらもGhidraで見ていきます。
get_positionと同様にSymbol Treeでlocal_sdk_motor_move_abs_angleをクリックしDecompile windowを見ていきます。
undefined4 local_sdk_motor_move_abs_angle(undefined8 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,undefined4 param_5,undefined4 param_6)
{
undefined4 uVar1;
undefined4 uVar2;
if (g_sdkMotorInitOk != 0) {
uVar1 = change_angleX_to_step();
uVar2 = change_angleY_to_step(param_2);
/* WARNING: Could not recover jumptable at 0x0001212c. Too many branches */
/* WARNING: Treating indirect jump as call */
uVar1 = local_sdk_motor_move_abs_step(uVar1,uVar2,param_5,param_6);
return uVar1;
}
return 0xffffffff;
}
こちらも順番に見ていきますが、何やらパラメータが沢山あります。
まず返り値はmotorの初期化前の場合は-1を返すようです。
正常時はlocal_sdk_motor_move_abs_step()の返り値をそのまま返します。
change_angleX_to_step()はなんかおかしいですね。
int change_angleX_to_step(float param_1)
{
return (int)((param_1 * fRam00002980) / fRam00002984);
}
中をみるとfloatの引数を変換してintで返しています。
local_sdk_motor_move_abs_angle:0
00010f20 02 00 1c 3c lui gp,0x2
00010f24 90 9a 9c 27 addiu gp,gp,-0x6570
00010f28 21 e0 99 03 addu gp,gp,t9
00010f2c 28 80 82 8f lw v0,-0x7fd8(gp)=>PTR_000229d8 = 00000000
00010f30 28 80 83 8f lw v1,-0x7fd8(gp)=>PTR_000229d8 = 00000000
00010f34 80 29 40 c4 lwc1 f0,0x2980(v0)
00010f38 02 63 00 46 mul.S f12,f12,f0
00010f3c 84 29 60 c4 lwc1 f0,0x2984(v1)
00010f40 03 60 00 46 div.S f0,f12,f0
00010f44 0d 00 00 46 trunc.w.S f0,f0
00010f48 08 00 e0 03 jr ra
00010f4c 00 00 02 44 _mfc1 v0,f0
MIPSのcalling conventionだと整数値の引数はa0~a3とstack、floatの場合f12~f14を使用しますがsingleの場合はf12, f14のみ使用するので、この関数の引数はf12で渡ってきていています。
なので、呼び出し側でf12にセットしているのが見えなくなっているようです。
これは、local_sdk_motor_move_abs_angleのparam1がそのまま渡ってきているためlocal_sdk_motor_move_abs_angleの解析時にparam1が現れなかったものと思われます。
これらを元にlocal_sdk_motor_move_abs_abgleをGhidra上で編集します。
関数名のところで右clockしてEdit Function Signatureで関数の定義を編集します。
ついでにlocal変数も関数名を元にint stepXとint stepYとしてみます。
int local_sdk_motor_move_abs_angle(float pan,float tilt,undefined4 param_3,undefined4 param_4,undefined4 param_5,undefined4 param_6)
{
int stepX;
int stepY;
if (g_sdkMotorInitOk != 0) {
stepX = change_angleX_to_step(pan);
stepY = change_angleY_to_step(tilt);
stepX = local_sdk_motor_move_abs_step(stepX,stepY,param_5,param_6);
return stepX;
}
return -1;
}
次に、local_sdk_motor_move_abs_step()の中を見ていきます。
int local_sdk_motor_move_abs_step(int param_1,int param_2,undefined4 param_3,undefined4 param_4,undefined4 param_5,undefined4 param_6)
{
int iVar1;
undefined4 local_30;
int local_2c;
int local_28;
if (g_sdkMotorInitOk == 0) {
iVar1 = -1;
}
else {
iVar1 = paras_check_abs();
if (-1 < iVar1) {
iVar1 = schedule_new_task(1,param_6,param_4,param_5,local_30);
if (iVar1 == 0) {
puts((char *)0x28ac);
local_sdk_motor_speed(param_3);
control_motor_inner(motorport_fd,4,&DAT_00022b04);
local_2c = param_1 - DAT_00022b04;
local_28 = param_2 - DAT_00022b08;
printf((char *)0x28bc,local_2c,local_28,param_3);
iVar1 = control_motor_inner(motorport_fd,3,&local_2c);
return iVar1;
}
}
}
return iVar1;
}
なんか引数の数が合わないです。中を読んでいきます。
param_1はstepX, param_2はstepYです。
param_3はmotor_speedのようです。
param_4~6はschedule_new_task()を見ていきます。
schedule_new_taskの引数1つ目は1、2,3,4番目は上からきた引数、5番目はlocal_30だけどどこでも定義されてないようです。
逆アセンブルを確認してみるけどなんか使われてる感じがないです。中を追っていきます。
undefined4 schedule_new_task(undefined4 param_1,uint param_2,undefined4 param_3,undefined4 param_4,undefined4 param_5)
{
undefined4 uVar1;
if (motor_module_status == 0) {
puts((char *)0x2690);
uVar1 = 0xffffffff;
}
else {
if (param_2 < 4) {
if (0 < curr_task._0_4_) {
if (curr_task._4_4_ <= param_2) {
printf((char *)0x2734,curr_task._4_4_,param_2);
uVar1 = 0xfffffffb;
goto LAB_00011630;
}
stop_curr_task();
}
pthread_mutex_lock((pthread_mutex_t *)mutex_task);
puts((char *)0x2780);
curr_task._0_4_ = param_1;
curr_task._4_4_ = param_2;
curr_task._8_4_ = param_3;
curr_task._12_4_ = param_4;
pthread_mutex_unlock((pthread_mutex_t *)mutex_task);
return 0;
}
puts((char *)0x2714);
uVar1 = 0xfffffffd;
}
LAB_00011630:
uVar1 = cancel_new_task(param_1,param_2,param_3,param_4,param_5,uVar1);
return uVar1;
}
param_2は4以下の値で、その先でcure_task._4_4_と比較して大きかったらエラーを返しています。
正常時にはcur_task._4_4_に入れてるのでpriorityの設定のようにみえます。
priorityの比較を通った場合にはcurr_taskを停止させる処理をしているようなので、cure_task._0_4_は実行中taskのフラグかもしれません。
となると残るparam_3,4はcallback関数かもしれないです。
cancel_new_task()を開いてみます。
undefined4 cancel_new_task(void)
{
code *in_a3;
undefined4 in_stack_00000014;
if (in_a3 != (code *)0x0) {
(*in_a3)(in_stack_00000014);
}
return in_stack_00000014;
}
あれ?引数がないですね。
逆アセンブルを確認
cancel_new_task
00011364 e0 ff bd 27 addiu sp,sp,-0x20
00011368 18 00 b0 af sw s0,local_8(sp)
0001136c 1c 00 bf af sw ra,local_4(sp)
00011370 20 00 a4 af sw param_1,local_res0(sp)
00011374 24 00 a5 af sw param_2,local_res4(sp)
00011378 28 00 a6 af sw param_3,local_res8(sp)
0001137c 2c 00 a7 af sw param_4,local_resc(sp)
00011380 04 00 e0 10 beq param_4,zero,LAB_00011394
00011384 34 00 b0 8f _lw s0,param_6(sp)
00011388 21 c8 e0 00 move t9,param_4
0001138c 09 f8 20 03 jalr t9
00011390 21 20 00 02 _move param_1,s0
LAB_00011394 XREF[1]: 00011380(j)
00011394 1c 00 bf 8f lw ra,local_4(sp)
00011398 21 10 00 02 move v0,s0
0001139c 18 00 b0 8f lw s0,local_8(sp)
000113a0 08 00 e0 03 jr ra
000113a4 20 00 bd 27 _addiu sp,sp,0x20
param_4をcallしているのでparam4は関数ポインタ。
param_1~3を引数にparam_4を呼び出しているようなので、param4はcancel時のcallback関数。
だとするとparam_3は正常終了時のcallback関数かな。
今度はiCamera_appの方でlocal_sdk_motor_move_abs_angle()を呼び出している側を確認してみます。
MenuのSearch->Program TextでSearch for :local_sdk_motor_move_abs_angle,FieldsでInstruction Operandsで検索します。
void FUN_00431164(undefined4 param_1,undefined4 param_2)
{
FUN_00430f34();
FUN_0045034c(DAT_00576304);
local_sdk_motor_move_abs_angle(param_1,param_2);
return;
}
引数が合わないので逆アセンブルを確認
FUN_00431164
00431164 c8 ff bd 27 addiu sp,sp,-0x38
00431168 34 00 bf af sw ra,local_4(sp)
0043116c 28 00 a6 af sw a2,local_10(sp)
00431170 20 00 ac e7 swc1 f12,local_18(sp)
00431174 cd c3 10 0c jal FUN_00430f34 undefined FUN_00430f34()
00431178 24 00 ae e7 _swc1 f14,local_14(sp)
0043117c 57 00 02 3c lui v0,0x57
00431180 d3 40 11 0c jal FUN_0045034c undefined FUN_0045034c()
00431184 04 63 44 8c _lw a0,offset DAT_00576304(v0) = FFFFFFFFh
00431188 43 00 02 3c lui v0,0x43
0043118c 20 00 ac c7 lwc1 f12,local_18(sp)
00431190 24 00 ae c7 lwc1 f14,local_14(sp)
00431194 28 00 a6 8f lw a2,local_10(sp)
00431198 80 05 42 24 addiu v0,v0,0x580
0043119c 10 00 a2 af sw v0=>FUN_00430580,local_28(sp)
004311a0 43 00 07 3c lui a3,0x43
004311a4 02 00 02 24 li v0,0x2
004311a8 14 00 a2 af sw v0,local_24(sp)
004311ac 78 46 15 0c jal liblocalsdk_motor.so::local_sdk_motor_move_abs undefined local_sdk_motor_move_a
004311b0 44 09 e7 24 _addiu a3=>FUN_00430944,a3,0x944
004311b4 34 00 bf 8f lw ra,local_4(sp)
004311b8 08 00 e0 03 jr ra
004311bc 38 00 bd 27 _addiu sp,sp,0x38
f12, f14を設定しているので第1、第2引数はやはりfloat pan, float tilt, 第3引数はmotor_speed,第4引数は正常時のcallback, 第5引数はキャンセル時のcallback,第6引数はpriorityと想定します。
その想定で呼び出している関数の引数を設定し直すとこんな感じになります。
void FUN_00431164(float pan,float tilt,undefined4 dummy1,uint dummy2,int speed,void *done, void *cancel,uint priority)
{
uint dummy1_00;
FUN_00430f34();
dummy1_00 = DAT_00576304;
FUN_0045034c();
local_sdk_motor_move_abs_angle(pan,tilt,dummy1_00,dummy2,speed,FUN_00430944,FUN_00430580,2);
return;
}
dummy1, dummy2はcalling conventionでfloat使用時に無視されるa0, a1です。本来の関数は
void FUN_00431164(float pan,float tilt, int speed,void *done, void *cancel,uint priority)
ですが、Ghidraの仕様で仮にdummyを置いています。
local_sdk_motor_move_abs_angleの呼び出し部分も本来は下記になります。
local_sdk_motor_move_abs_angle(pan,tilt,speed,FUN_00430944,FUN_00430580,2);
このFUN_00430944,FUN_00430580を確認していきます。
undefined4 FUN_00430944(undefined4 param_1,undefined4 param_2)
{
DAT_0057630c = param_2;
DAT_00576310 = param_1;
FUN_004308f0();
if ((DAT_005762e4 == -1) && (DAT_005762e4 = FUN_004500e0(), DAT_005762e4 < 0)) {
return 0xffffffff;
}
FUN_0045034c(DAT_005762e4);
DAT_00644270 = 0;
return 0;
}
これも逆アセンブルをみるとFPU命令がありますので修正
int FUN_00430944(float pan,float tilt)
{
DAT_0057630c = tilt;
DAT_00576310 = pan;
FUN_004308f0(pan,tilt);
if ((DAT_005762e4 == -1) && (DAT_005762e4 = FUN_004500e0(), DAT_005762e4 < 0)) {
return -1;
}
FUN_0045034c(DAT_005762e4);
DAT_00644270 = 0;
return 0;
}
こんな感じかと思います。
こちらは正常動作完了で最後の位置情報を保存しているようです。
同じようにFUN_00430580も見てみます。
int FUN_00430580(void)
{
if ((DAT_005762e4 == -1) && (DAT_005762e4 = FUN_004500e0(), DAT_005762e4 < 0)) {
return -1;
}
FUN_0045034c(DAT_005762e4);
DAT_00644270 = 0;
return 0;
}
これらをまとめると
int local_sdk_motor_move_abs_angle(float pan, float tilt, int speed, void (*done)(float a, float b), void (*canceled)(void), uint priority)
と思われます。
各関数の引数の範囲確認
iCamera_appを起動する時に
LD_PRELOAD=/tmp/system/lib/modules/libcallback.so /system/bin/iCamera_app
としてlibcallback.soをPRELOADさせています。
libcallbackのコードは
github.com mnakada/atomcam_tools/lib/callback
にあります。上に書いたbuild環境でbuildすることができます。
この中のcommand.cでiCamera_appに外部コマンドを受け付けるthreadを追加しています。
このthreadではlocalhostからのsocketをport 4000で受け付けます。
shell scriptからはncを使って繋ぐために/scripts/cmdを用意していますが中身は
#!/bin/sh
echo "$*" | /usr/bin/nc localhost 4000
とone linerです。
/scripts/cmd move
を実行するとiCamera_appの中でlocal_sdk_motor_get_posを呼び出して値をsocket経由で返します。
アプリで端までAtomSwingを移動させ返り値を確認して、反対側の端まで移動して再度返り値を確認します。
移動範囲としてpan 0~355, tilt 0-180であることが確認でき、パラメータの単位がdegreeであることがわかりました。
また、同様に引数をつけて実行するとlocal_sdk_motor_abs_angleを実行できるようにしたので動作確認していきます。
/scripts/cmd move 355 90
を実行するとpan=355, tilt=90の位置まで移動することを確認できました。
とりあえずサンプルとしてmotorの両側の端点に当てることで座標系を補正するshell scriptを作っていみました。
/scripts/motor_init
これの中身は
#!/bin/sh
# move [<pan(0-355 deg)> <tilt(0-180 deg)> [<pri 0:top, 1:high, 2:normal, 3:weak>]]
# move -> <pan(deg)> <tilt(deg)>
pos=`/scripts/cmd move`
/scripts/cmd move 0 0 1
/scripts/cmd move 355 180 1
/scripts/cmd move $pos 1
で、最初にposに初期値のpan/tiltを取得、0,0に移動、355,180に移動、最初の位置に戻すというものです。
まとめ
AtomSwingをGhidraを使って解析してコマンドを追加するところまでをやってみました。
他にも色々と遊べそうな関数がshared libraryに出ています。殆どの一般的な関数もhookをかけることができるので大抵のことはできそうです。
ぜひメーカーに迷惑をかけない範囲で遊んでみてください。