AtomSwingを少し改造して遊んでみた


## はじめに

送料込みで約5000円で買えるPan/Tilt CameraのAtomSwingを購入したので遊んでみました。

この記事ではAtomSwingのアプリであるiCamera_appとliblocalsdk.so, liblocalsdk_motor.soを解析して改造するための手順を説明します。

:::note alert
※ 以下の内容を実行する場合は各自の責任において行ってください。  当然ながらメーカーへの問い合わせは厳に慎んでください。
:::

## 準備

#### AtomSwing

AtomCam2では既に色々と見ていたので（下記Qiitaの記事）AtomSwingもほぼ同じだろうと当たりをつけて見てみました。

WiFiのDriverが変更になっていたのとmotorのdriverが追加になった以外はAtomCam2と同じ構成で簡単にloginできる状態までいきました。

#### 関連記事

https://qiita.com/mnakada/items/7d0fbcb6e629e1ddbd0c

https://qiita.com/mnakada/items/eddbf8b6f0095e279095

githubに展開用のイメージも置いてあります。

https://github.com/mnakada/atomcam_tools

上記のイメージのauthorized_keysにsshの公開鍵を設定したSD-Cardを作成、AtomSwingにセットして起動します。

#### build環境

[build.md](https://github.com/mnakada/atomcam_tools/blob/main/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します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159374/9bac1d2e-304f-bfea-62a7-8ccfbbcbcad4.jpeg" alt="symbol_text.jpeg" width="50%"/>


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を実行すると呼び出してる箇所が出てきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159374/215b5d87-db32-cb2a-fe8c-36feec9b22fe.jpeg" alt="search_program.jpeg" width="50%"/>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159374/8a24d697-e7da-07d5-ebf4-03635bc493b0.jpeg" alt="search_text.jpeg" width="50%"/>

後半のliblocalsdk_motor.so::*の方はlibrary側なので無視して、呼び出し側のFUN_\*の方を見て使っている関数を確認していきます。

move_track, move, move_rel_step, move_abs_angleが呼ばれてます。

同様にget_positionも確認、こちらも４箇所で使われてました。

全部追いかけるのは大変なので、絶対角で移動させられそうな
locak_sdk_motor_move_abs_angle
と現在の方向を取れそうな
locak_sdk_motor_get_position
に目星をつけて追ってみます。

（実際には他にもmoveとかmove_trackとかも途中まで追ったのですが、上の２つで目的果たせそうだったので放棄しました）

#### local_sdk_motor_get_position

まず、値を返してくる方のget_positionの方から攻めてみます。

Ghidraでliblocalsdk_motor.soのSymbol Treeでlocal_sdk_motor_get_positionをクリックします。

Decompile windowに関数のコードが表示されます。

``` c
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);

を返しているようです。

なので関数としては

``` c
int local_sdk_motor_get_position(float *pan,float *tilt);
```

で返り値は成功時は０失敗時に−１が返り、\*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を見ていきます。

``` c
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()はなんかおかしいですね。

``` C
int change_angleX_to_step(float param_1)
{
  return (int)((param_1 * fRam00002980) / fRam00002984);
}
```

中をみるとfloatの引数を変換してintで返しています。

``` asm
      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としてみます。

``` c
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()の中を見ていきます。

``` c
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だけどどこでも定義されてないようです。

逆アセンブルを確認してみるけどなんか使われてる感じがないです。中を追っていきます。

``` c
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()を開いてみます。

``` c
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で検索します。

``` c
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;
}
```

引数が合わないので逆アセンブルを確認

``` asm
                             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を設定しているので第１、第２引数はやはりfloat pan, float tilt, 第３引数はmotor_speed,第４引数は正常時のcallback, 第５引数はキャンセル時のcallback,第６引数はpriorityと想定します。

その想定で呼び出している関数の引数を設定し直すとこんな感じになります。

``` c
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です。本来の関数は

``` c
void FUN_00431164(float pan,float tilt, int speed,void *done, void *cancel,uint priority)
```

ですが、Ghidraの仕様で仮にdummyを置いています。

local_sdk_motor_move_abs_angleの呼び出し部分も本来は下記になります。

``` c
local_sdk_motor_move_abs_angle(pan,tilt,speed,FUN_00430944,FUN_00430580,2);
```

このFUN_00430944,FUN_00430580を確認していきます。

``` c
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命令がありますので修正

``` c
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も見てみます。

``` c
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;
}
```

これらをまとめると

``` c
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を起動する時に

``` bash
LD_PRELOAD=/tmp/system/lib/modules/libcallback.so /system/bin/iCamera_app
```

としてlibcallback.soをPRELOADさせています。

libcallbackのコードは

[github.com mnakada/atomcam_tools/lib/callback](https://github.com/mnakada/atomcam_tools/tree/main/libcallback)

にあります。上に書いたbuild環境でbuildすることができます。

この中のcommand.cでiCamera_appに外部コマンドを受け付けるthreadを追加しています。

このthreadではlocalhostからのsocketをport 4000で受け付けます。

shell scriptからはncを使って繋ぐために/scripts/cmdを用意していますが中身は

``` bash
#!/bin/sh
echo "$*" | /usr/bin/nc localhost 4000
```

とone linerです。

``` bash
/scripts/cmd move
```

を実行するとiCamera_appの中でlocal_sdk_motor_get_posを呼び出して値をsocket経由で返します。

アプリで端までAtomSwingを移動させ返り値を確認して、反対側の端まで移動して再度返り値を確認します。

移動範囲としてpan 0~355, tilt 0-180であることが確認でき、パラメータの単位がdegreeであることがわかりました。

また、同様に引数をつけて実行するとlocal_sdk_motor_abs_angleを実行できるようにしたので動作確認していきます。

``` bash
/scripts/cmd move 355 90
```

を実行するとpan=355, tilt=90の位置まで移動することを確認できました。

とりあえずサンプルとしてmotorの両側の端点に当てることで座標系を補正するshell scriptを作っていみました。

``` bash
/scripts/motor_init
```

これの中身は

``` bash
#!/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をかけることができるので大抵のことはできそうです。

ぜひメーカーに迷惑をかけない範囲で遊んでみてください。