_(:3」∠)_
巷では、コンピュータを使って「年収を如何に上げるか」とか「すごい存在になるためにはどうしたらいいか」が流行っている様だけれど、 私には、この社会のつまらない感覚をブチ破ってくれる、サスティナブルな熱狂をください。
という気分でパラパラを本を読んでいたら、Binaryの世界が目に止まった。
私はほら、BigDataにときめくのが本職(現職はまた異なる)で、低レイヤーは別にそんなに興味あるわけじゃないんだからね!なのだけれど、沼感が良くて最近低レイヤーに入門しそう。
ptrace(2)というシステムコールを使うと、なんと実行中の他プロセスのレジスタの書き換えやメモリ上のデータ書き換えが出来るらしく、デバッガで使われているとのこと。面白そう〜🌟
多分知っている人は知っているのかな?
上のbkブログさんのコードとほぼ変わらないのだけど、こんな感じのコードを作って、ブログの通りにやる。
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int
main (int argc, char **argv)
{
assert(argc == 4);
pid_t pid = atoi(argv[1]);
void *addr = (void *)strtol(argv[2], NULL, 0);
void *word = (void *)strtol(argv[3], NULL, 0);
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
perror("ATTACH");
wait(NULL);
ptrace(PTRACE_POKEDATA, pid, addr, word);
perror("POKEDATA");
ptrace(PTRACE_DETACH, pid, NULL, NULL);
perror("DETACH");
return 0;
}
perror(3)は前職の人々が教えてくれた。
#include <stdio.h>
int
main ()
{
while (1) {
printf("hello, world\n");
}
return 0;
}
$ gcc -o poke poke.c
$ gcc -o hello hello.c
$ objdump -s hello | grep -C1 hello
hello: file format elf64-littleaarch64
--
Contents of section .rodata:
0820 01000200 00000000 68656c6c 6f2c2077 ........hello, w
0830 6f726c64 00 orld.
=> hの位置は0820から8個先なので、アドレスは0x0828か?(後に解決)
$ ./hello => 別シェルで起動
$ ps aux | grep hello
root 940 45.6 0.0 1924 416 pts/1 S+ 11:17 0:01 ./hello
=> 対象のプロセス番号は940番であることを確認
[Docker on M1Mac]
$ ./poke 940 0x0828 0x285f333a
attach: Operation not permitted
poke: No such process
dettach: No such process
[ Ubuntu 21.なんとか ]
$ ./poke <プロセス番号> <開始位置> <書き換えたい文字列>
ATTACH: Success
POKEDATA: Input/output error
DETACH: Input/output error
できない
上記のように手元にあったDocker on M1MacのUbuntuイメージとUbuntuのマシンでやってみたら実行に失敗したが、どうもEC2(Amazon Linux2)で実行すると上手くいくらしい。何の差?と、しばらくトライしたら解決した。
まず、Docker環境のOperation not permitted
のエラーはdocker run
実行時に--cap-add=ALL
を付けると無くなった。前職の人が教えてくれた。
Linux capabilitiesというものを知る。root権限ってガチで無敵らしい。
次に、Ubuntuで実行出来なかったのは、アドレスが動的リンク時のアドレスになっているからだった。
「開始位置を決めるのはリンカの仕事」と教えてもらったのがヒントになった。
gccでコンパイルするときに--static
を付けると静的リンクのアドレス(0x455968)が表示されるので、このアドレスに対して実行したら書き換えることができた。
$ objdump -s hello | grep -C1 hello
hello: file format elf64-littleaarch64
--
Contents of section .rodata:
0820 01000200 00000000 68656c6c 6f2c2077 ........hello, w
0830 6f726c64 00 orld.
$ gcc -o hello hello.c --static
$ objdump -s hello | grep -C1 hello
hello: file format elf64-littleaarch64
--
455960 60434500 00000000 68656c6c 6f2c2077 `CE.....hello, w
455970 6f726c64 00000000 2e2e2f63 73752f6c orld....../csu/l
できたけど
えっじゃあ、Amazon Linux2のgccまたはリンカは--static
つけなくても静的リンクしているってこと?と思いかけたけれど、lddの結果は明らかに動的リンクに見えるよね。
初心者で調査手段もあまりよくわかっていない感じがある。
[Amazon Linux2]
$ gcc -o hello hello.c
$ ldd hello =>
linux-vdso.so.1 (0x00007fff8ebe5000)
libc.so.6 => /lib64/libc.so.6 (0x00007f16a9d95000)
/lib64/ld-linux-x86-64.so.2 (0x00007f16aa140000)
[Ubuntu on Docker]
[動的リンク]
$ gcc -o hello hello.c
$ ldd hello
linux-vdso.so.1 (0x0000ffff9fc13000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffff9fa5e000)
/lib/ld-linux-aarch64.so.1 (0x0000ffff9fbe3000)
$ size hello
text data bss dec hex filename
1795 656 8 2459 99b hello
[静的リンク]
$ gcc -o hello hello.c --static
$ ldd hello
not a dynamic executable
$ size hello
text data bss dec hex filename
495626 20428 5688 521742 7f60e hello
ファイルサイズ結構違うね👀
topコマンドに潜むたかざわじゅんすけ
とにかくtopコマンドの表示にたかざわじゅんすけ_(:3」∠)_を忍ばせてみる。
[たかじゅんの16進数表記]
$ echo -n '_(:3 L)_' | od -x
0000000 285f 333a 4c20 5f29
$ objdump -s `which top` | grep -C1 Tasks
413ee0 004d6942 00476942 00546942 00506942 .MiB.GiB.TiB.PiB
413ef0 00456942 00546173 6b730043 70752873 .EiB.Tasks.Cpu(s <-こっち?
413f00 293a0043 7075252d 33643a00 616e6f74 ):.Cpu%-3d:.anot
--
415a40 75736572 732c2020 6c6f6164 20617665 users, load ave
415a50 72616765 3a0a2020 20546173 6b733a7e rage:. Tasks:~
415a60 33202036 34207e32 746f7461 6c2c7e33 3 64 ~2total,~3
$ ./poke 6764 0x0413ef5 0x5f294c20333a285f <- リトルエンディアン
ATTACH: Success
POKEDATA: Success
DETACH: Success
$ top
top - 14:27:29 up 1 day, 48 min, 2 users, load average: 0.00, 0.25, 0.27
top - 14:39:00 up 1 day, 1:00, 2 users, load average: 0.00, 0.01, 0.10
_(:3 L)_u(s):: 95 total, 1 running, 52 sleeping, 0 stopped, 0 zombie
いる
[一つのアドレスから8bit以上差し込めないの図]
$ ./poke 6764 0x0413ef5 0x005f29204c20333a285f
ATTACH: Numerical result out of range
POKEDATA: Numerical result out of range
DETACH: Numerical result out of range
→アドレス番号を+8して8bit書き込めば良いみたい。
バイナリの世界であれば、言語に依らず、プロプライエタリだろうと関係なく、好き勝手が出来る自由があるんだね。 あと、プロセスのイメージを少し勘違いしていたかもしれない。思ったより柔軟。
そういえば
才知溢れる(笑)大学生の時分に、妙なSF小説が思い浮かんで「たかざわじゅんすけは考えた_(:3」∠)_」を執筆した。
大学四年生の当時の理解は正しいかはさておき、教授へのメールの文章に紛れ込んでしまったたかざわじゅんすけ_(:3」∠)_が、自らのバイナリを書き換えることを思案するという内容がある。
当時はptraceのことを知らなかったのだけど、自プロセスの書き換えでも.rodataセクションに置かれた定数を書き換える場合、ptraceを発行する必要がありそうだよね?
残念ながら、ストーリーとしては既にケーブル上を走っている間の出来事なので、魔法の力の話でした。5年経った今になって自分の作品について思考できる、嬉しい。