吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 17654|回复: 18
收起左侧

[原创] docker-portainer Registries破解

[复制链接]
XueSec0re 发表于 2019-5-14 15:21
本帖最后由 XueSec0re 于 2019-5-14 15:32 编辑

Portainer是Docker的图形化管理工具,提供状态显示面板、应用模板快速部署、容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作)、事件日志显示、容器控制台操作、Swarm集群和服务等集中管理和操作、登录用户管理和控制等功能。功能十分全面,基本能满足中小型单位对容器管理的全部需求。

1.png

简单来说类似于一个docker的webui管理器,支持集群,非常方便,大部分功能是免费的,但是其中一个对于Registries的管理功能确实需要收费的。

你可以在docker里对其进行快速部署

[Bash shell] 纯文本查看 复制代码
1
2
$ docker volume create portainer_data
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer


这个镜像本身连bash都没有,所以无法直接在docker里进行查看

[Bash shell] 纯文本查看 复制代码
1
docker cp 1eac0076a7bb:/ 你的目录


使用命令把所有内容拷贝出来,因为镜像很小,全部拷贝即可。

2.png

文件的目录结构如下,其中Portainer是主服务,extension-registry-management-linux-amd64-1.0.0是其中收费功能的部分。

2个都是elf文件,Portainer对其进行逆向后会发现它的git仓库地址,实际上它是开源的这部分。

在注册页面,注册失败会提示Invalid extension license key ,直接在源代码里搜索Invalid extension license key,会发现函数实现的地方

[Golang] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
func validateLicense(binaryPath, licenseKey string) ([]string, error) {
        licenseCheckProcess := exec.Command(binaryPath, "-license", licenseKey, "-check")
        cmdOutput := &bytes.Buffer{}
        licenseCheckProcess.Stdout = cmdOutput
 
        err := licenseCheckProcess.Run()
        if err != nil {
                return nil, errors.New("Invalid extension license key")
        }
 
        output := string(cmdOutput.Bytes())
 
        return strings.Split(output, "|"), nil
}


其通过执行elf文件,获取输出判断是否注册成功,实际运行同理。

直接将extension-registry-management-linux-amd64-1.0.0 加载进ida, 你会发现ida没有识别任何函数,全部都是sub_xxxx, 搜索字符串发现,所有字符串都黏在一起。

这样的代码很难入手

代码是go写的于是谷歌了一波go的逆向技巧,找到了一篇非常棒的文章。

https://rednaga.io/2016/09/21/reversing_go_binaries_like_a_pro/

文章还有包含一个写好的ida脚本,不过运行会出错,看文章发现作者是6.95上写的

将出错的MAKETEXT删除后,工作正常,足够使用

[Python] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
"""golang_loader_assist.py: Help IDA Pro do some golang reversing."""
 
__author__ = "Tim 'diff' Strazzere"
__copyright__ = "Copyright 2016, Red Naga"
__license__ = "GPL"
__version__ = "1.2"
__email__ = ["strazz@gmail.com"]
 
from idautils import *
from idc import *
import idaapi
import sys
import string
 
#
# Constants
#
DEBUG = False
 
#
# Utility functions
#
def info(formatted_string):
    print formatted_string
 
def error(formatted_string):
    print 'ERROR - %s' % formatted_string
 
def debug(formatted_string):
    if DEBUG:
        print 'DEBUG - %s' % formatted_string
 
#
# String defining fuctionality
#
 
# Indicators of string loads
# mov     ebx, offset aWire ; "wire" # Get string
# mov     [esp], ebx
# mov     dword ptr [esp+4], 4 # String length
 
# mov     ebx, offset unk_8608FD5 # Get string
# mov     [esp+8], ebx
# mov     dword ptr [esp+0Ch], 0Eh # String length
 
# mov     ebx, offset unk_86006E6 # Get string
# mov     [esp+10h], ebx
# mov     dword ptr [esp+14h], 5 # String length
 
# mov     ebx, 861143Ch
# mov     dword ptr [esp+0F0h+var_E8+4], ebx
# mov     [esp+0F0h+var_E0], 19h
 
# Found in newer versions of golang binaries
 
# lea     rax, unk_8FC736
# mov     [rsp+38h+var_18], rax
# mov     [rsp+38h+var_10], 1Dh
 
# lea     rdx, unk_8F6E82
# mov     [rsp+40h+var_38], rdx
# mov     [rsp+40h+var_30], 13h
 
# lea     eax, unk_82410F0
# mov     [esp+94h+var_8C], eax
# mov     [esp+94h+var_88], 2
 
 
# Currently it's normally ebx, but could in theory be anything - seen ebp
VALID_REGS = ['eax', 'ebx', 'ebp', 'rax', 'rcx', 'r10', 'rdx']
 
# Currently it's normally esp, but could in theory be anything - seen eax
VALID_DEST = ['esp', 'eax', 'ecx', 'edx', 'rsp']
 
# TODO : Extract patterns
def is_string_load(addr):
    patterns = []
    # Check for first parts instruction and what it is loading -- also ignore function pointers we may have renamed
    if (GetMnem(addr) != 'mov' and GetMnem(addr) != 'lea') and (GetOpType(addr, 1) != 2 or GetOpType(addr, 1) != 5) or GetOpnd(addr, 1)[-4:] == '_ptr':
        return False
 
    # Validate that the string offset actually exists inside the binary
    if idaapi.get_segm_name(GetOperandValue(addr, 1)) is None:
        return False
 
    # Could be unk_, asc_, 'offset ', XXXXh, ignored ones are loc_ or inside []
    if GetOpnd(addr, 0) in VALID_REGS and not ('[' in GetOpnd(addr, 1) or 'loc_' in GetOpnd(addr, 1)) and (('offset ' in GetOpnd(addr, 1) or 'h' in GetOpnd(addr, 1)) or ('unk' == GetOpnd(addr, 1)[:3])):
        from_reg = GetOpnd(addr, 0)
        # Check for second part
        addr_2 = FindCode(addr, SEARCH_DOWN)
        try:
            dest_reg = GetOpnd(addr_2, 0)[GetOpnd(addr_2, 0).index('[') + 1:GetOpnd(addr_2, 0).index('[') + 4]
        except ValueError:
            return False
        if GetMnem(addr_2) == 'mov' and dest_reg in VALID_DEST and ('[%s' % dest_reg) in GetOpnd(addr_2, 0) and GetOpnd(addr_2, 1) == from_reg:
            # Check for last part, could be improved
            addr_3 = FindCode(addr_2, SEARCH_DOWN)
            # GetOpType 1 is a register, potentially we can just check that GetOpType returned 5?
            if GetMnem(addr_3) == 'mov' and (('[%s+' % dest_reg) in GetOpnd(addr_3, 0) or GetOpnd(addr_3, 0) in VALID_DEST) and 'offset ' not in GetOpnd(addr_3, 1) and 'dword ptr ds' not in GetOpnd(addr_3, 1) and GetOpType(addr_3, 1) != 1 and GetOpType(addr_3, 1) != 2 and GetOpType(addr_3, 1) != 4:
                try:
                    dumb_int_test = GetOperandValue(addr_3, 1)
                    if dumb_int_test > 0 and dumb_int_test < sys.maxsize:
                        return True
                except ValueError:
                    return False
 
    return False
 
def create_string(addr, string_len):
    if idaapi.get_segm_name(addr) is None:
        debug('Cannot load a string which has no segment - not creating string @ 0x%02x' % addr)
        return False
 
    debug('Found string load @ 0x%x with length of %d' % (addr, string_len))
    # This may be overly aggressive if we found the wrong area...
    if GetStringType(addr) is not None and GetString(addr) is not None and len(GetString(addr)) != string_len:
        debug('It appears that there is already a string present @ 0x%x' % addr)
        MakeUnknown(addr, string_len, DOUNK_SIMPLE)
 
    if GetString(addr) is None and MakeStr(addr, addr + string_len):
        return True
    else:
        # If something is already partially analyzed (incorrectly) we need to MakeUnknown it
        MakeUnknown(addr, string_len, DOUNK_SIMPLE)
        if MakeStr(addr, addr + string_len):
            return True
        debug('Unable to make a string @ 0x%x with length of %d' % (addr, string_len))
 
    return False
 
def create_offset(addr):
    if OpOff(addr, 1, 0):
        return True
    else:
        debug('Unable to make an offset for string @ 0x%x ' % addr)
 
    return False
 
def strings_init():
    strings_added = 0
    retry = []
    text_seg = get_text_seg()
    if text_seg is None:
        debug('Failed to get text segment')
        return strings_added
 
    # This may be inherently flawed as it will only search for defined functions
    # and as of IDA Pro 6.95 it fails to autoanalyze many GO functions, currently
    # this works well since we redefine/find (almost) all the functions prior to
    # this being used. Could be worth a strategy rethink later one or on diff archs
    for addr in Functions(text_seg.startEA, text_seg.endEA):
        name = GetFunctionName(addr)
 
        end_addr = Chunks(addr).next()[1]
        if(end_addr < addr):
            error('Unable to find good end for the function %s' % name)
            pass
 
        debug('Found function %s starting/ending @ 0x%x 0x%x' %  (name, addr, end_addr))
 
        while addr <= end_addr:
            if is_string_load(addr):
                if 'rodata' not in idaapi.get_segm_name(addr) and 'text' not in idaapi.get_segm_name(addr):
                    debug('Should a string be in the %s section?' % idaapi.get_segm_name(addr))
                string_addr = GetOperandValue(addr, 1)
                addr_3 = FindCode(FindCode(addr, SEARCH_DOWN), SEARCH_DOWN)
                string_len = GetOperandValue(addr_3, 1)
                if create_string(string_addr, string_len):
                    if create_offset(addr):
                        strings_added += 1
                else:
                    # There appears to be something odd that goes on with IDA making some strings, always works
                    # the second time, so lets just force a retry...
                   retry.append((addr, string_addr, string_len))
 
                # Skip the extra mov lines since we know it won't be a load on any of them
                addr = FindCode(addr_3, SEARCH_DOWN)
            else:
                addr = FindCode(addr, SEARCH_DOWN)
 
    for instr_addr, string_addr, string_len in retry:
        if create_string(string_addr, string_len):
            if create_offset(instr_addr):
                strings_added += 1
        else:
            error('Unable to make a string @ 0x%x with length of %d for usage in function @ 0x%x' % (string_addr, string_len, instr_addr))
 
    return strings_added
 
#
# Function defining methods
#
 
 
def get_text_seg():
    #   .text found in PE & ELF binaries, __text found in macho binaries
    return _get_seg(['.text', '__text'])
 
def get_gopclntab_seg():
    #   .gopclntab found in PE & ELF binaries, __gopclntab found in macho binaries
    return _get_seg(['.gopclntab', '__gopclntab'])
 
def _get_seg(possible_seg_names):
    seg = None
    for seg_name in possible_seg_names:
        seg = idaapi.get_segm_by_name(seg_name)
        if seg:
            return seg
 
    return seg
 
# Indicators of runtime_morestack
# mov     large dword ptr ds:1003h, 0 # most I've seen
# mov     qword ptr ds:1003h, 0 # some
 
def is_simple_wrapper(addr):
    if GetMnem(addr) == 'xor' and GetOpnd(addr, 0) == 'edx' and  GetOpnd(addr, 1) == 'edx':
        addr = FindCode(addr, SEARCH_DOWN)
        if GetMnem(addr) == 'jmp' and GetOpnd(addr, 0) == 'runtime_morestack':
            return True
 
    return False
 
def create_runtime_ms():
    debug('Attempting to find runtime_morestack function for hooking on...')
 
    text_seg = get_text_seg()
    if text_seg is None:
        debug('Failed to get text segment')
        return None
 
    #   Opcodes for "mov     large dword ptr ds:1003h, 0", binary search is faster than text search
    opcodes = 'c7 05 03 10 00 00 00 00 00 00'
    if idaapi.get_inf_structure().is_64bit():
        #   Opcodes for "mov     qword ptr ds:dword_1000+3, 0"
        opcodes = '48 c7 04 25 03 10 00 00 00 00 00 00'
 
    runtime_ms_end = idaapi.find_binary(text_seg.startEA, text_seg.endEA, opcodes, 0, SEARCH_DOWN)
    if runtime_ms_end == BADADDR:
        debug('Failed to find opcodes associated with runtime_morestack: %s' % opcodes)
        return None
 
    runtime_ms = idaapi.get_func(runtime_ms_end)
    if runtime_ms is None:
        debug('Failed to get runtime_morestack function from address @ 0x%x' % runtime_ms_end)
        return None
 
    if idc.MakeNameEx(runtime_ms.startEA, "runtime_morestack", SN_PUBLIC):
        debug('Successfully found runtime_morestack')
    else:
        debug('Failed to rename function @ 0x%x to runtime_morestack' % runtime_ms.startEA)
 
    return runtime_ms
 
def traverse_xrefs(func):
    func_created = 0
 
    if func is None:
        return func_created
 
    # First
    func_xref = idaapi.get_first_cref_to(func.startEA)
    # Attempt to go through crefs
    while func_xref != BADADDR:
        # See if there is a function already here
        if idaapi.get_func(func_xref) is None:
            # Ensure instruction bit looks like a jump
            func_end = FindCode(func_xref, SEARCH_DOWN)
            if GetMnem(func_end) == "jmp":
                # Ensure we're jumping back "up"
                func_start = GetOperandValue(func_end, 0)
                if func_start < func_xref:
                    if idc.MakeFunction(func_start, func_end):
                        func_created += 1
                    else:
                        # If this fails, we should add it to a list of failed functions
                        # Then create small "wrapper" functions and backtrack through the xrefs of this
                        error('Error trying to create a function @ 0x%x - 0x%x' %(func_start, func_end))
        else:
            xref_func = idaapi.get_func(func_xref)
            # Simple wrapper is often runtime_morestack_noctxt, sometimes it isn't though...
            if is_simple_wrapper(xref_func.startEA):
                debug('Stepping into a simple wrapper')
                func_created += traverse_xrefs(xref_func)
            if idaapi.get_func_name(xref_func.startEA) is not None and 'sub_' not in idaapi.get_func_name(xref_func.startEA):
                debug('Function @0x%x already has a name of %s; skipping...' % (func_xref, idaapi.get_func_name(xref_func.startEA)))
            else:
                debug('Function @ 0x%x already has a name %s' % (xref_func.startEA, idaapi.get_func_name(xref_func.startEA)))
 
        func_xref = idaapi.get_next_cref_to(func.startEA, func_xref)
 
    return func_created
 
def find_func_by_name(name):
    text_seg = get_text_seg()
    if text_seg is None:
        return None
 
    for addr in Functions(text_seg.startEA, text_seg.endEA):
        if name == idaapi.get_func_name(addr):
            return idaapi.get_func(addr)
 
    return None
 
def runtime_init():
    func_created = 0
 
    if find_func_by_name('runtime_morestack') is not None:
        func_created += traverse_xrefs(find_func_by_name('runtime_morestack'))
        func_created += traverse_xrefs(find_func_by_name('runtime_morestack_noctxt'))
    else:
        runtime_ms = create_runtime_ms()
        func_created = traverse_xrefs(runtime_ms)
 
 
    return func_created
 
 
#
# Function renaming fuctionality
#
 
def create_pointer(addr, force_size=None):
    if force_size is not 4 and (idaapi.get_inf_structure().is_64bit() or force_size is 8):
        MakeQword(addr)
        return Qword(addr), 8
    else:
        MakeDword(addr)
        return Dword(addr), 4
 
STRIP_CHARS = [ '(', ')', '[', ']', '{', '}', ' ', '"' ]
REPLACE_CHARS = ['.', '*', '-', ',', ';', ':', '/', '\xb7' ]
def clean_function_name(str):
    # Kill generic 'bad' characters
    str = filter(lambda x: x in string.printable, str)
 
    for c in STRIP_CHARS:
        str = str.replace(c, '')
 
    for c in REPLACE_CHARS:
        str = str.replace(c, '_')
 
    return str
 
def renamer_init():
    renamed = 0
 
    gopclntab = get_gopclntab_seg()
    if gopclntab is not None:
        # Skip unimportant header and goto section size
        addr = gopclntab.startEA + 8
        size, addr_size = create_pointer(addr)
        addr += addr_size
 
        # Unsure if this end is correct
        early_end = addr + (size * addr_size * 2)
        while addr < early_end:
            func_offset, addr_size = create_pointer(addr)
            name_offset, addr_size = create_pointer(addr + addr_size)
            addr += addr_size * 2
 
            func_name_addr = Dword(name_offset + gopclntab.startEA + addr_size) + gopclntab.startEA
            func_name = GetString(func_name_addr)
 
            appended = clean_func_name = clean_function_name(func_name)
            debug('Going to remap function at 0x%x with %s - cleaned up as %s' % (func_offset, func_name, clean_func_name))
 
            if idaapi.get_func_name(func_offset) is not None:
                if MakeName(func_offset, clean_func_name):
                    renamed += 1
                else:
                    error('clean_func_name error %s' % clean_func_name)
 
    return renamed
 
 
# Function pointers are often used instead of passing a direct address to the
# function -- this function names them based off what they're currently named
# to ease reading
#
# lea     rax, main_GetExternIP_ptr <-- pointer to actual function
# mov     [rsp+1C0h+var_1B8], rax <-- loaded as arg for next function
# call    runtime_newproc <-- function is used inside a new process
 
def pointer_renamer():
    renamed = 0
 
    text_seg = get_text_seg()
    if text_seg is None:
        debug('Failed to get text segment')
        return renamed
 
    for addr in Functions(text_seg.startEA, text_seg.endEA):
        name = GetFunctionName(addr)
 
        # Look at data xrefs to the function - find the pointer that is located in .rodata
        data_ref = idaapi.get_first_dref_to(addr)
        while data_ref != BADADDR:
            if 'rodata' in idaapi.get_segm_name(data_ref):
                # Only rename things that are currently listed as an offset; eg. off_9120B0
                if 'off_' in GetTrueName(data_ref):
                    if MakeName(data_ref, ('%s_ptr' % name)):
                        renamed += 1
                    else:
                        error('error attempting to name pointer @ 0x%02x for %s' % (data_ref, name))
 
            data_ref = idaapi.get_next_dref_to(addr, data_ref)
 
    return renamed
 
def main():
 
    # This should be run before the renamer, as it will find and help define more functions
    func_added = runtime_init()
    info('Found and successfully created %d functions!' % func_added)
 
    # This should prevent the script from locking up due to the auto initalizer
    idaapi.autoWait()
 
    # Should be run after the function initializer,
    renamed = renamer_init()
    info('Found and successfully renamed %d functions!' % renamed)
 
    # Attempt to rename all function pointers after we have all the functions and proper function names
    pointers_renamed = pointer_renamer()
    info('Found and successfully renamed %d function pointers!' % pointers_renamed)
 
    # Attempt to find all string loading idioms
    strings_added = strings_init()
    info('Found and successfully created %d strings!' % strings_added)
 
if __name__ == "__main__":    main()


3.png

函数重命名后如上,函数逻辑清晰可见

根据之前的源代,在linux下测试运行该程序。

4.png

直接搜索main函数,即可发现函数主要处理的部分

参数为 binaryPath, "-license", licenseKey, "-check" 程序检查key是否正确,用户注册时候key使用
参数为 binaryPath, "-license", extension.License.LicenseKey 检查key是否正确,正确即启动服务,正常工作时候使用。,

这里逻辑已经很简单了,直接在linux上进行动态调试,慢慢根据线索修改跳转即可完成。

5.png

破解好的elf文件重新复制回docker,注册使用即可。

6.png

成品如下太大权限不够上传,按着步骤做一遍应该即可。

免费评分

参与人数 8威望 +1 吾爱币 +14 热心值 +7 收起 理由
有个小村民 + 1 我很赞同!
kaixiangyw + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Hmily + 1 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
YasudaKoudou + 1 + 1 谢谢@Thanks!
hxw0204 + 1 + 1 热心回复!
笙若 + 1 + 1 谢谢@Thanks!
zuoteng + 1 + 1 谢谢@Thanks!
wueryi + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Naylor 发表于 2019-5-14 18:25
靠谱么?  这么几部就破解了? 人家开发这个东西的程序员吃干饭的?  不会是瞎猫遇到死耗子了吧?
xinghun12552 发表于 2019-5-14 16:13
Juggler 发表于 2019-5-14 18:09
Misscitel 发表于 2019-5-14 19:46
大佬牛逼
ph31545 发表于 2019-5-14 19:48
我有个注册机。反汇编有些地方解密不了,哪个大佬帮我看看 付费酬劳
ytfrdfiw 发表于 2019-5-14 20:58
非常不错。感谢分享。
bluesky4485 发表于 2019-5-15 14:49
搞个云上传一下呗,能大到哪去哦
jacken2019 发表于 2019-5-15 17:03
好厉害的样子,膜拜~~~
YasudaKoudou 发表于 2019-5-15 21:42
大佬厉害!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-22 03:05

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表